mirror of
https://github.com/coracle-social/coracle.git
synced 2024-10-01 17:31:24 +00:00
Get util/store checking
This commit is contained in:
parent
906384f27a
commit
64a36f6b2a
@ -17,6 +17,8 @@
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^4.7.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.1.0",
|
||||
"@types/ramda": "^0.29.3",
|
||||
"@types/throttle-debounce": "^5.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
@ -44,7 +46,7 @@
|
||||
"classnames": "^2.3.2",
|
||||
"compressorjs": "^1.1.1",
|
||||
"fuse.js": "^6.6.2",
|
||||
"hurdak": "^0.2.2",
|
||||
"hurdak": "^0.2.3",
|
||||
"husky": "^8.0.3",
|
||||
"insane": "^2.6.2",
|
||||
"lru-cache": "^7.18.3",
|
||||
|
@ -1,173 +1,204 @@
|
||||
import {is, reject, filter, map, findIndex, equals} from "ramda"
|
||||
import {ensurePlural} from "hurdak"
|
||||
|
||||
export type Readable<T> = {
|
||||
notify: () => void
|
||||
get: () => T
|
||||
subscribe: (f: (v: T) => void) => () => void
|
||||
derived: (f: (v: T) => void) => Readable<T>
|
||||
type Derivable = Readable<any> | Readable<any>[]
|
||||
type Unsubscriber = () => void
|
||||
type Subscriber = <T>(v: T) => void | Unsubscriber
|
||||
type R = Record<string, any>
|
||||
type M = Map<string, R>
|
||||
|
||||
export interface Readable<T> {
|
||||
get: () => T | undefined
|
||||
subscribe: (f: Subscriber) => () => void
|
||||
derived: <U>(f: <T>(v: T) => U) => Readable<U>
|
||||
}
|
||||
|
||||
export type Writable<T> = Readable<T> & {
|
||||
set: (v: T) => void
|
||||
update: (f: (v: T) => T) => void
|
||||
}
|
||||
export class Writable<T> implements Readable<T> {
|
||||
private value: T
|
||||
private subs: Subscriber[] = []
|
||||
|
||||
export const writable = <T>(defaultValue = null): Writable<T> => {
|
||||
let value = defaultValue
|
||||
const subs = []
|
||||
constructor(defaultValue: T) {
|
||||
this.value = defaultValue
|
||||
}
|
||||
|
||||
const notify = () => {
|
||||
for (const sub of subs) {
|
||||
sub(value)
|
||||
notify() {
|
||||
for (const sub of this.subs) {
|
||||
sub(this.value)
|
||||
}
|
||||
}
|
||||
|
||||
const get = () => value
|
||||
|
||||
const set = newValue => {
|
||||
value = newValue
|
||||
|
||||
notify()
|
||||
get() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
const update = f => {
|
||||
value = f(value)
|
||||
|
||||
notify()
|
||||
set(newValue: T) {
|
||||
this.value = newValue
|
||||
this.notify()
|
||||
}
|
||||
|
||||
const subscribe = f => {
|
||||
subs.push(f)
|
||||
|
||||
notify()
|
||||
|
||||
return () => subs.splice(findIndex(equals(f), subs), 1)
|
||||
update(f: (v: T) => T) {
|
||||
this.value = f(this.value)
|
||||
this.notify()
|
||||
}
|
||||
|
||||
const derived_ = f => derived<T>({get, subscribe}, f)
|
||||
|
||||
return {notify, get, set, update, subscribe, derived: derived_}
|
||||
}
|
||||
|
||||
export const derived = <T>(stores, getValue): Readable<T> => {
|
||||
const callerSubs = []
|
||||
const mySubs = []
|
||||
|
||||
const get = () => getValue(Array.isArray(stores) ? stores.map(s => s.get()) : stores.get())
|
||||
|
||||
const notify = () => callerSubs.forEach(f => f(get()))
|
||||
|
||||
const subscribe = f => {
|
||||
if (callerSubs.length === 0) {
|
||||
for (const s of ensurePlural(stores)) {
|
||||
mySubs.push(s.subscribe(v => notify()))
|
||||
}
|
||||
}
|
||||
|
||||
callerSubs.push(f)
|
||||
|
||||
notify()
|
||||
subscribe(f: Subscriber) {
|
||||
this.subs.push(f)
|
||||
this.notify()
|
||||
|
||||
return () => {
|
||||
callerSubs.splice(findIndex(equals(f), callerSubs), 1)
|
||||
const idx = findIndex(equals(f), this.subs)
|
||||
|
||||
if (callerSubs.length == 0) {
|
||||
for (const unsub of mySubs.splice(0)) {
|
||||
this.subs.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
derived<U>(f: (v: T) => U): Derived<U> {
|
||||
return new Derived<U>([this], f)
|
||||
}
|
||||
}
|
||||
|
||||
export class Derived<T> implements Readable<T> {
|
||||
private callerSubs: Subscriber[] = []
|
||||
private mySubs: Unsubscriber[] = []
|
||||
private stores: Derivable
|
||||
private getValue: (values: any) => T
|
||||
|
||||
constructor(stores: Derivable, getValue: (values: any) => T) {
|
||||
this.stores = stores
|
||||
this.getValue = getValue
|
||||
}
|
||||
|
||||
notify() {
|
||||
this.callerSubs.forEach(f => f(this.get()))
|
||||
}
|
||||
|
||||
get() {
|
||||
const isMulti = is(Array, this.stores)
|
||||
const inputs = ensurePlural(this.stores).map(s => s.get())
|
||||
|
||||
return this.getValue(isMulti ? inputs : inputs[0])
|
||||
}
|
||||
|
||||
subscribe(f: Subscriber) {
|
||||
if (this.callerSubs.length === 0) {
|
||||
for (const s of ensurePlural(this.stores)) {
|
||||
this.mySubs.push(s.subscribe(() => this.notify()))
|
||||
}
|
||||
}
|
||||
|
||||
this.callerSubs.push(f)
|
||||
this.notify()
|
||||
return () => {
|
||||
const idx = findIndex(equals(f), this.callerSubs)
|
||||
|
||||
this.callerSubs.splice(idx, 1)
|
||||
|
||||
if (this.callerSubs.length === 0) {
|
||||
for (const unsub of this.mySubs.splice(0)) {
|
||||
unsub()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const derived_ = f => derived<T>({get, subscribe}, f)
|
||||
|
||||
return {notify, get, subscribe, derived: derived_}
|
||||
derived<U>(f: (v: T) => U): Readable<U> {
|
||||
return new Derived([this], f) as Readable<U>
|
||||
}
|
||||
}
|
||||
|
||||
export class Key<T> {
|
||||
export class Key implements Readable<R> {
|
||||
readonly pk: string
|
||||
readonly key: string
|
||||
#base: Writable<Map<string, T>>
|
||||
#store: Readable<T[]>
|
||||
private base: Writable<M>
|
||||
private store: Readable<R>
|
||||
|
||||
constructor(base, pk, key) {
|
||||
constructor(base: Writable<M>, pk: string, key: string) {
|
||||
if (!is(Map, base.get())) {
|
||||
throw new Error("`key` can only be used on map collections")
|
||||
}
|
||||
|
||||
this.pk = pk
|
||||
this.key = key
|
||||
this.#base = base
|
||||
this.#store = derived(base, m => m.get(key))
|
||||
this.base = base
|
||||
this.store = base.derived<R>(m => m.get(key) as R)
|
||||
}
|
||||
|
||||
get = () => this.#base.get().get(this.key)
|
||||
get = () => this.base.get().get(this.key)
|
||||
|
||||
subscribe = f => this.#store.subscribe(f)
|
||||
subscribe = (f: Subscriber) => this.store.subscribe(f)
|
||||
|
||||
derived = f => this.#store.derived(f)
|
||||
derived = <U>(f: <V>(v: V) => U) => this.store.derived<U>(f)
|
||||
|
||||
exists = () => this.#base.get().has(this.key)
|
||||
exists = () => this.base.get().has(this.key)
|
||||
|
||||
update = f =>
|
||||
this.#base.update(m => {
|
||||
update = (f: (v: R) => R) =>
|
||||
this.base.update((m: M) => {
|
||||
if (!this.key) {
|
||||
throw new Error(`Cannot set key: "${this.key}"`)
|
||||
}
|
||||
|
||||
const v = f(m.get(this.key))
|
||||
const v = f(m.get(this.key) as R) as Record<string, any>
|
||||
|
||||
// Make sure the pk always get set on the record
|
||||
if (v) {
|
||||
v[this.pk] = this.key
|
||||
|
||||
m.set(this.key, v)
|
||||
m.set(this.key, v as R)
|
||||
}
|
||||
|
||||
return m
|
||||
})
|
||||
|
||||
set = v => this.update(() => v)
|
||||
set = (v: R) => this.update(() => v)
|
||||
|
||||
merge = d => this.update(v => ({...v, ...d}))
|
||||
merge = (d: R) => this.update(v => ({...v, ...d}))
|
||||
|
||||
remove = () =>
|
||||
this.#base.update(m => {
|
||||
this.base.update(m => {
|
||||
m.delete(this.key)
|
||||
|
||||
return m
|
||||
})
|
||||
}
|
||||
|
||||
export class Collection<T> {
|
||||
export class Collection implements Readable<R[]> {
|
||||
readonly pk: string
|
||||
#map: Writable<Map<string, T>>
|
||||
#list: Readable<T[]>
|
||||
#map: Writable<M>
|
||||
#list: Readable<R[]>
|
||||
|
||||
constructor(pk) {
|
||||
constructor(pk: string) {
|
||||
this.pk = pk
|
||||
this.#map = writable(new Map())
|
||||
this.#list = derived(this.#map, m => Array.from(m.values())) as Readable<T[]>
|
||||
this.#list = this.#map.derived<R[]>((m: M) => Array.from(m.values()))
|
||||
}
|
||||
|
||||
get = () => this.#list.get()
|
||||
|
||||
getMap = () => this.#map.get()
|
||||
|
||||
subscribe = f => this.#list.subscribe(f)
|
||||
subscribe = (f: Subscriber) => this.#list.subscribe(f)
|
||||
|
||||
derived = f => this.#list.derived(f)
|
||||
derived = <U>(f: <V>(v: V) => U) => this.#list.derived<U>(f)
|
||||
|
||||
key = k => new Key<T>(this.#map, this.pk, k)
|
||||
key = (k: string) => new Key(this.#map, this.pk, k)
|
||||
|
||||
set = xs => this.#map.set(new Map(xs.map(x => [x[this.pk], x])))
|
||||
set = (xs: R[]) => this.#map.set(new Map(xs.map(x => [x[this.pk], x])))
|
||||
|
||||
update = f => this.#map.update(m => new Map(f(Array.from(m.values())).map(x => [x[this.pk], x])))
|
||||
update = (f: (v: R[]) => R[]) =>
|
||||
this.#map.update(m => new Map(f(Array.from(m.values())).map(x => [x[this.pk], x])))
|
||||
|
||||
reject = f => this.update(reject(f))
|
||||
reject = (f: (v: R) => boolean) => this.update(reject(f))
|
||||
|
||||
filter = f => this.update(filter(f))
|
||||
filter = (f: (v: R) => boolean) => this.update(filter(f))
|
||||
|
||||
map = f => this.update(map(f))
|
||||
map = (f: (v: R) => R) => this.update(map(f))
|
||||
}
|
||||
|
||||
export const collection = <T>(pk) => new Collection<T>(pk)
|
||||
export const writable = <T>(v: T) => new Writable(v)
|
||||
|
||||
export const derived = <U>(stores: Derivable, getValue: (values: any) => U) =>
|
||||
new Derived(stores, getValue) as Readable<U>
|
||||
|
||||
export const key = (base: Writable<M>, pk: string, key: string) => new Key(base, pk, key)
|
||||
|
||||
export const collection = (pk: string) => new Collection(pk)
|
||||
|
1
src/types.d.ts
vendored
Normal file
1
src/types.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'fuse.js/dist/fuse.min.js'
|
@ -1,25 +1,25 @@
|
||||
import {bech32, utf8} from "@scure/base"
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {mergeDeepRight, pluck} from "ramda"
|
||||
import {Storage, seconds, tryFunc, sleep, isObject, round} from "hurdak"
|
||||
import {pluck} from "ramda"
|
||||
import {Storage, seconds, tryFunc, sleep, round} from "hurdak"
|
||||
import Fuse from "fuse.js/dist/fuse.min.js"
|
||||
import {writable} from "svelte/store"
|
||||
import {warn} from "src/util/logger"
|
||||
|
||||
export const fuzzy = (data, opts = {}) => {
|
||||
const fuse = new Fuse(data, opts)
|
||||
export const fuzzy = <T>(data: T[], opts = {}) => {
|
||||
const {search} = new Fuse(data, opts) as {search: (q: string) => {item: T}[]}
|
||||
|
||||
// Slice pattern because the docs warn that it"ll crash if too long
|
||||
return q => (q ? pluck("item", fuse.search(q.slice(0, 32))) : data)
|
||||
return (q: string) => (q ? pluck("item", search(q.slice(0, 32))) : data)
|
||||
}
|
||||
|
||||
export const now = () => Math.round(new Date().valueOf() / 1000)
|
||||
|
||||
export const getTimeZone = () => new Date().toString().match(/GMT[^\s]+/)
|
||||
|
||||
export const createLocalDate = dateString => new Date(`${dateString} ${getTimeZone()}`)
|
||||
export const createLocalDate = (dateString: string) => new Date(`${dateString} ${getTimeZone()}`)
|
||||
|
||||
export const formatTimestamp = ts => {
|
||||
export const formatTimestamp = (ts: number) => {
|
||||
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
@ -28,7 +28,7 @@ export const formatTimestamp = ts => {
|
||||
return formatter.format(new Date(ts * 1000))
|
||||
}
|
||||
|
||||
export const formatTimestampAsDate = ts => {
|
||||
export const formatTimestampAsDate = (ts: number) => {
|
||||
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
@ -38,7 +38,7 @@ export const formatTimestampAsDate = ts => {
|
||||
return formatter.format(new Date(ts * 1000))
|
||||
}
|
||||
|
||||
export const formatTimestampRelative = ts => {
|
||||
export const formatTimestampRelative = (ts: number) => {
|
||||
let unit
|
||||
let delta = now() - ts
|
||||
if (delta < seconds(1, "minute")) {
|
||||
@ -61,7 +61,7 @@ export const formatTimestampRelative = ts => {
|
||||
return formatter.format(-delta, unit as Intl.RelativeTimeFormatUnit)
|
||||
}
|
||||
|
||||
export const formatTimestampAsLocalISODate = ts => {
|
||||
export const formatTimestampAsLocalISODate = (ts: number) => {
|
||||
const date = new Date(ts * 1000)
|
||||
const offset = date.getTimezoneOffset() * 60000
|
||||
const datetime = new Date(date.getTime() - offset).toISOString()
|
||||
@ -69,12 +69,10 @@ export const formatTimestampAsLocalISODate = ts => {
|
||||
return datetime
|
||||
}
|
||||
|
||||
export const createScroller = (
|
||||
loadMore,
|
||||
{threshold = 2000, reverse = false, element = null} = {}
|
||||
export const createScroller = <T>(
|
||||
loadMore: () => Promise<T>,
|
||||
{threshold = 2000, reverse = false, element = document.body} = {}
|
||||
) => {
|
||||
element = element || document.body
|
||||
|
||||
let done = false
|
||||
const check = async () => {
|
||||
// While we have empty space, fill it
|
||||
@ -108,33 +106,16 @@ export const createScroller = (
|
||||
}
|
||||
}
|
||||
|
||||
export const synced = (key, defaultValue = null) => {
|
||||
// If it's an object, merge defaults
|
||||
const store = writable(
|
||||
isObject(defaultValue)
|
||||
? mergeDeepRight(defaultValue, Storage.getJson(key) || {})
|
||||
: Storage.getJson(key) || defaultValue
|
||||
)
|
||||
export const synced = (key: string, defaultValue: any) => {
|
||||
const store = writable(Storage.getJson(key) || defaultValue)
|
||||
|
||||
store.subscribe(debounce(1000, $value => Storage.setJson(key, $value)))
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
// DANGER: don't use this if it's disposable, it does not clean up subscriptions,
|
||||
// and will cause a memory leak
|
||||
export const getter = store => {
|
||||
let value
|
||||
|
||||
store.subscribe(_value => {
|
||||
value = _value
|
||||
})
|
||||
|
||||
return () => value
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/21682946
|
||||
export const stringToHue = value => {
|
||||
export const stringToHue = (value: string) => {
|
||||
let hash = 0
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
hash = value.charCodeAt(i) + ((hash << 5) - hash)
|
||||
@ -144,28 +125,39 @@ export const stringToHue = value => {
|
||||
return hash % 360
|
||||
}
|
||||
|
||||
export const hsl = (hue, {saturation = 100, lightness = 50, opacity = 1} = {}) =>
|
||||
export const hsl = (hue: string, {saturation = 100, lightness = 50, opacity = 1} = {}) =>
|
||||
`hsl(${hue}, ${saturation}%, ${lightness}%, ${opacity})`
|
||||
|
||||
export const tryJson = f => tryFunc(f, e => e.toString().includes("JSON") || warn(e))
|
||||
export const tryJson = (f: <T>() => T) =>
|
||||
tryFunc(f, (e: Error) => {
|
||||
if (!e.toString().includes("JSON")) {
|
||||
warn(e)
|
||||
}
|
||||
})
|
||||
|
||||
export const tryFetch = f => tryFunc(f, e => e.toString().includes("fetch") || warn(e))
|
||||
export const tryFetch = (f: <T>() => T) =>
|
||||
tryFunc(f, (e: Error) => {
|
||||
if (!e.toString().includes("fetch")) {
|
||||
warn(e)
|
||||
}
|
||||
})
|
||||
|
||||
export const hexToBech32 = (prefix, url) =>
|
||||
export const hexToBech32 = (prefix: string, url: string) =>
|
||||
bech32.encode(prefix, bech32.toWords(utf8.decode(url)), false)
|
||||
|
||||
export const bech32ToHex = b32 => utf8.encode(bech32.fromWords(bech32.decode(b32, false).words))
|
||||
export const bech32ToHex = (b32: string) =>
|
||||
utf8.encode(bech32.fromWords(bech32.decode(b32, false).words))
|
||||
|
||||
export const numberFmt = new Intl.NumberFormat()
|
||||
|
||||
export const formatSats = sats => {
|
||||
export const formatSats = (sats: number) => {
|
||||
if (sats < 1_000) return numberFmt.format(sats)
|
||||
if (sats < 1_000_000) return numberFmt.format(round(1, sats / 1000)) + "K"
|
||||
if (sats < 100_000_000) return numberFmt.format(round(1, sats / 1_000_000)) + "MM"
|
||||
return numberFmt.format(round(2, sats / 100_000_000)) + "BTC"
|
||||
}
|
||||
|
||||
export const annotateMedia = url => {
|
||||
export const annotateMedia = (url: string) => {
|
||||
if (url.match(/\.(jpg|jpeg|png|gif|webp)/)) {
|
||||
return {type: "image", url}
|
||||
} else if (url.match(/\.(mov|webm|mp4)/)) {
|
||||
@ -175,7 +167,7 @@ export const annotateMedia = url => {
|
||||
}
|
||||
}
|
||||
|
||||
export const shadeColor = (color, percent) => {
|
||||
export const shadeColor = (color: string, percent: number) => {
|
||||
let R = parseInt(color.substring(1, 3), 16)
|
||||
let G = parseInt(color.substring(3, 5), 16)
|
||||
let B = parseInt(color.substring(5, 7), 16)
|
||||
@ -227,7 +219,7 @@ export const webSocketURLToPlainOrBase64 = (url: string): string => {
|
||||
return url
|
||||
}
|
||||
|
||||
export const pushToKey = (xs, k, v) => {
|
||||
export const pushToKey = (xs: any[], k: number, v: any) => {
|
||||
xs[k] = xs[k] || []
|
||||
xs[k].push(v)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type {DisplayEvent} from "src/engine/types"
|
||||
import type {Filter, Event, DisplayEvent} from "src/engine/types"
|
||||
import {is, fromPairs, mergeLeft, last, identity, prop, flatten, uniq} from "ramda"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {ensurePlural, avg, first} from "hurdak"
|
||||
@ -16,15 +16,15 @@ export const appDataKeys = {
|
||||
}
|
||||
|
||||
export class Tags {
|
||||
tags: Array<any>
|
||||
constructor(tags) {
|
||||
tags: any[]
|
||||
constructor(tags: any[]) {
|
||||
this.tags = tags
|
||||
}
|
||||
static from(events) {
|
||||
static from(events: Event | Event[]) {
|
||||
return new Tags(ensurePlural(events).flatMap(prop("tags")))
|
||||
}
|
||||
static wrap(tags) {
|
||||
return new Tags((tags || []).filter(identity))
|
||||
static wrap(tags: any[]) {
|
||||
return new Tags(tags.filter(identity))
|
||||
}
|
||||
all() {
|
||||
return this.tags
|
||||
@ -38,7 +38,7 @@ export class Tags {
|
||||
first() {
|
||||
return first(this.tags)
|
||||
}
|
||||
nth(i) {
|
||||
nth(i: number) {
|
||||
return this.tags[i]
|
||||
}
|
||||
last() {
|
||||
@ -62,35 +62,35 @@ export class Tags {
|
||||
asMeta() {
|
||||
return fromPairs(this.tags)
|
||||
}
|
||||
getMeta(k) {
|
||||
getMeta(k: string) {
|
||||
return this.type(k).values().first()
|
||||
}
|
||||
values() {
|
||||
return new Tags(this.tags.map(t => t[1]))
|
||||
}
|
||||
filter(f) {
|
||||
filter(f: (t: any) => boolean) {
|
||||
return new Tags(this.tags.filter(f))
|
||||
}
|
||||
reject(f) {
|
||||
reject(f: (t: any) => boolean) {
|
||||
return new Tags(this.tags.filter(t => !f(t)))
|
||||
}
|
||||
any(f) {
|
||||
any(f: (t: any) => boolean) {
|
||||
return this.filter(f).exists()
|
||||
}
|
||||
type(type) {
|
||||
type(type: string) {
|
||||
const types = ensurePlural(type)
|
||||
|
||||
return new Tags(this.tags.filter(t => types.includes(t[0])))
|
||||
}
|
||||
equals(value) {
|
||||
equals(value: string) {
|
||||
return new Tags(this.tags.filter(t => t[1] === value))
|
||||
}
|
||||
mark(mark) {
|
||||
mark(mark: string) {
|
||||
return new Tags(this.tags.filter(t => last(t) === mark))
|
||||
}
|
||||
}
|
||||
|
||||
export const findReplyAndRoot = e => {
|
||||
export const findReplyAndRoot = (e: Event) => {
|
||||
const tags = Tags.from(e)
|
||||
.type("e")
|
||||
.filter(t => last(t) !== "mention")
|
||||
@ -110,22 +110,19 @@ export const findReplyAndRoot = e => {
|
||||
return {reply: reply || root, root}
|
||||
}
|
||||
|
||||
export const findReply = e => prop("reply", findReplyAndRoot(e))
|
||||
export const findReply = (e: Event) => prop("reply", findReplyAndRoot(e))
|
||||
|
||||
export const findReplyId = e => findReply(e)?.[1]
|
||||
export const findReplyId = (e: Event) => findReply(e)?.[1]
|
||||
|
||||
export const findRoot = e => prop("root", findReplyAndRoot(e))
|
||||
export const findRoot = (e: Event) => prop("root", findReplyAndRoot(e))
|
||||
|
||||
export const findRootId = e => findRoot(e)?.[1]
|
||||
export const findRootId = (e: Event) => findRoot(e)?.[1]
|
||||
|
||||
export const isLike = content => ["", "+", "🤙", "👍", "❤️", "😎", "🏅"].includes(content)
|
||||
export const isLike = (content: string) => ["", "+", "🤙", "👍", "❤️", "😎", "🏅"].includes(content)
|
||||
|
||||
export const isRelay = url =>
|
||||
typeof url === "string" &&
|
||||
// It should have the protocol included
|
||||
url.match(/^wss:\/\/.+/)
|
||||
export const isRelay = (url: string) => url.match(/^wss:\/\/.+/)
|
||||
|
||||
export const isShareableRelay = url =>
|
||||
export const isShareableRelay = (url: string) =>
|
||||
isRelay(url) &&
|
||||
// Don't match stuff with a port number
|
||||
!url.slice(6).match(/:\d+/) &&
|
||||
@ -134,7 +131,7 @@ export const isShareableRelay = url =>
|
||||
// Skip nostr.wine's virtual relays
|
||||
!url.slice(6).match(/\/npub/)
|
||||
|
||||
export const normalizeRelayUrl = url => {
|
||||
export const normalizeRelayUrl = (url: string) => {
|
||||
url = url.replace(/\/+$/, "").toLowerCase().trim()
|
||||
|
||||
if (!url.startsWith("ws")) {
|
||||
@ -146,8 +143,12 @@ export const normalizeRelayUrl = url => {
|
||||
|
||||
export const channelAttrs = ["name", "about", "picture"]
|
||||
|
||||
export const asDisplayEvent = event =>
|
||||
({replies: [], reactions: [], zaps: [], ...event} as DisplayEvent)
|
||||
export const asDisplayEvent = (event: Event): DisplayEvent => ({
|
||||
replies: [],
|
||||
reactions: [],
|
||||
zaps: [],
|
||||
...event,
|
||||
})
|
||||
|
||||
export const toHex = (data: string): string | null => {
|
||||
if (data.match(/[a-zA-Z0-9]{64}/)) {
|
||||
@ -161,15 +162,18 @@ export const toHex = (data: string): string | null => {
|
||||
}
|
||||
}
|
||||
|
||||
export const mergeFilter = (filter, extra) =>
|
||||
export const mergeFilter = (filter: Filter | Filter[], extra: Filter) =>
|
||||
is(Array, filter) ? filter.map(mergeLeft(extra)) : {...filter, ...extra}
|
||||
|
||||
export const fromNostrURI = s => s.replace(/^[\w\+]+:\/?\/?/, "")
|
||||
export const fromNostrURI = (s: string) => s.replace(/^[\w\+]+:\/?\/?/, "")
|
||||
|
||||
export const toNostrURI = s => `web+nostr://${s}`
|
||||
export const toNostrURI = (s: string) => `web+nostr://${s}`
|
||||
|
||||
export const getLabelQuality = (label, event) =>
|
||||
tryJson(() => JSON.parse(last(Tags.from(event).type("l").equals(label).first())).quality)
|
||||
export const getLabelQuality = (label: string, event: Event) => {
|
||||
const json = tryJson(() => JSON.parse(last(Tags.from(event).type("l").equals(label).first())))
|
||||
|
||||
export const getAvgQuality = (label, events) =>
|
||||
return (json as {quality?: number})?.quality || 0
|
||||
}
|
||||
|
||||
export const getAvgQuality = (label: string, events: Event[]) =>
|
||||
avg(events.map(e => getLabelQuality(label, e)).filter(identity))
|
||||
|
@ -1,10 +1,11 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"src/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user