forked from coracle/flotilla
Migrate to new welshman stores
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
|
||||
import {randomInt, map, displayList, TIMEZONE, identity} from "@welshman/lib"
|
||||
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
|
||||
import type {Filter} from "@welshman/util"
|
||||
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
|
||||
@@ -13,7 +13,7 @@
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import {alerts, userSpaceUrls} from "@app/core/state"
|
||||
import {alertsById, userSpaceUrls} from "@app/core/state"
|
||||
import {requestRelayClaim} from "@app/core/requests"
|
||||
import {createAlert} from "@app/core/commands"
|
||||
import {canSendPushNotifications} from "@app/util/push"
|
||||
@@ -45,7 +45,9 @@
|
||||
|
||||
let loading = $state(false)
|
||||
let cron = $state(WEEKLY)
|
||||
let email = $state($alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "")
|
||||
let email = $state(
|
||||
map(a => getTagValue("email", a.tags), $alertsById.values()).filter(identity)[0] || "",
|
||||
)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {sleep} from "@welshman/lib"
|
||||
import {sleep, filter} from "@welshman/lib"
|
||||
import {getTagValue, getAddress, RelayMode} from "@welshman/util"
|
||||
import {isRelayFeed, findFeed} from "@welshman/feeds"
|
||||
import {getPubkeyRelays, pubkey} from "@welshman/app"
|
||||
@@ -13,8 +13,8 @@
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {
|
||||
alerts,
|
||||
dmAlert,
|
||||
alertsById,
|
||||
deriveAlertStatus,
|
||||
getAlertFeed,
|
||||
userSettingsValues,
|
||||
@@ -33,7 +33,7 @@
|
||||
const dmStatus = $derived($dmAlert ? deriveAlertStatus(getAddress($dmAlert.event)) : undefined)
|
||||
|
||||
const filteredAlerts = $derived(
|
||||
$alerts.filter(alert => {
|
||||
filter(alert => {
|
||||
const feed = getAlertFeed(alert)
|
||||
|
||||
// Skip non-feeds and DM alerts
|
||||
@@ -43,7 +43,7 @@
|
||||
if (url) return findFeed(feed, f => isRelayFeed(f) && f.includes(url))
|
||||
|
||||
return true
|
||||
}),
|
||||
}, $alertsById.values()),
|
||||
)
|
||||
|
||||
const startAlert = () => pushModal(AlertAdd, {url, channel, hideSpaceField})
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
import {
|
||||
INDEXER_RELAYS,
|
||||
userSettingsValues,
|
||||
deriveChat,
|
||||
splitChatId,
|
||||
PLATFORM_NAME,
|
||||
deriveChat,
|
||||
} from "@app/core/state"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {prependParent} from "@app/core/commands"
|
||||
@@ -151,7 +151,7 @@
|
||||
let compose: ChatCompose | undefined = $state()
|
||||
let parent: TrustedEvent | undefined = $state()
|
||||
let chatCompose: HTMLElement | undefined = $state()
|
||||
let dlists: HTMLElelist | undefined = $state()
|
||||
let dynamicPadding: HTMLElement | undefined = $state()
|
||||
|
||||
const elements = $derived.by(() => {
|
||||
const elements = []
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {max, formatTimestampRelative} from "@welshman/lib"
|
||||
import {COMMENT} from "@welshman/util"
|
||||
import {load} from "@welshman/net"
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import {deriveArray, deriveEventsById} from "@welshman/store"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {repository} from "@welshman/app"
|
||||
import {notifications} from "@app/util/notifications"
|
||||
@@ -13,7 +13,7 @@
|
||||
const {url, path, event}: {url: string; path: string; event: TrustedEvent} = $props()
|
||||
|
||||
const filters = [{kinds: [COMMENT], "#E": [event.id]}]
|
||||
const replies = deriveEvents(repository, {filters})
|
||||
const replies = deriveArray(deriveEventsById({repository, filters}))
|
||||
const lastActive = $derived(max([...$replies, event].map(e => e.created_at)))
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {LOCALE, secondsToDate} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {tracker} from "@welshman/app"
|
||||
import FileText from "@assets/icons/file-text.svg?dataurl"
|
||||
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||
@@ -11,7 +12,6 @@
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import {trackerStore} from "@app/core/state"
|
||||
import {clip} from "@app/util/toast"
|
||||
|
||||
type Props = {
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
const relays = url ? [url] : Router.get().Event(event).getUrls()
|
||||
const nevent1 = nip19.neventEncode({...event, relays})
|
||||
const seenOn = $trackerStore.getRelays(event.id)
|
||||
const seenOn = tracker.getRelays(event.id)
|
||||
const npub1 = nip19.npubEncode(event.pubkey)
|
||||
const json = JSON.stringify(event, null, 2)
|
||||
const copyLink = () => clip(nevent1)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {now, DAY, uniq, sum} from "@welshman/lib"
|
||||
import type {Zap, TrustedEvent} from "@welshman/util"
|
||||
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
|
||||
import {deriveEventsMapped} from "@welshman/store"
|
||||
import {deriveItemsByKey, deriveArray} from "@welshman/store"
|
||||
import {repository, getValidZap} from "@welshman/app"
|
||||
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
const {url, event, ...props}: Props = $props()
|
||||
|
||||
const zaps = deriveEventsMapped<Zap>(repository, {
|
||||
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}],
|
||||
itemToEvent: item => item.response,
|
||||
eventToItem: (response: TrustedEvent) => getValidZap(response, event),
|
||||
})
|
||||
const zaps = deriveArray(
|
||||
deriveItemsByKey<Zap>({
|
||||
repository,
|
||||
getKey: zap => zap.response.id,
|
||||
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}],
|
||||
eventToItem: (response: TrustedEvent) => getValidZap(response, event),
|
||||
}),
|
||||
)
|
||||
|
||||
const goalAmount = parseInt(getTagValue("amount", event.tags) || "0")
|
||||
const zapAmount = $derived(fromMsats(sum($zaps.map(zap => zap.invoiceAmount))))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {formatTimestamp} from "@welshman/lib"
|
||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {userMutes} from "@welshman/app"
|
||||
import {userMuteList} from "@welshman/app"
|
||||
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -32,7 +32,7 @@
|
||||
muted = false
|
||||
}
|
||||
|
||||
let muted = $state(getPubkeyTagValues(getListTags($userMutes)).includes(event.pubkey))
|
||||
let muted = $state(getPubkeyTagValues(getListTags($userMuteList)).includes(event.pubkey))
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 {restProps.class}">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {sum} from "@welshman/lib"
|
||||
import type {Zap, TrustedEvent} from "@welshman/util"
|
||||
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
|
||||
import {deriveEventsMapped} from "@welshman/store"
|
||||
import {deriveItemsByKey, deriveArray} from "@welshman/store"
|
||||
import {repository, getValidZap} from "@welshman/app"
|
||||
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -14,11 +14,14 @@
|
||||
const content = getTagValue("summary", props.event.tags)
|
||||
const fakeEvent = {content, tags: props.event.tags}
|
||||
|
||||
const zaps = deriveEventsMapped<Zap>(repository, {
|
||||
filters: [{kinds: [ZAP_RESPONSE], "#e": [props.event.id]}],
|
||||
itemToEvent: item => item.response,
|
||||
eventToItem: (response: TrustedEvent) => getValidZap(response, props.event),
|
||||
})
|
||||
const zaps = deriveArray(
|
||||
deriveItemsByKey<Zap>({
|
||||
repository,
|
||||
getKey: zap => zap.response.id,
|
||||
filters: [{kinds: [ZAP_RESPONSE], "#e": [props.event.id]}],
|
||||
eventToItem: (response: TrustedEvent) => getValidZap(response, props.event),
|
||||
}),
|
||||
)
|
||||
|
||||
const goalAmount = parseInt(getTagValue("amount", props.event.tags) || "0")
|
||||
const zapAmount = $derived(fromMsats(sum($zaps.map(zap => zap.invoiceAmount))))
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import {load} from "@welshman/net"
|
||||
import {Router} from "@welshman/router"
|
||||
import type {Filter} from "@welshman/util"
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import {deriveArray, deriveEventsById} from "@welshman/store"
|
||||
import {formatTimestampRelative} from "@welshman/lib"
|
||||
import {NOTE, ROOMS, COMMENT} from "@welshman/util"
|
||||
import {repository, loadRelayList} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
|
||||
import {deriveGroupList, getSpaceUrlsFromGroupLists, MESSAGE_KINDS} from "@app/core/state"
|
||||
import {deriveGroupList, getSpaceUrlsFromGroupList, MESSAGE_KINDS} from "@app/core/state"
|
||||
import {goToEvent} from "@app/util/routes"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
const {pubkey, url}: Props = $props()
|
||||
const filters: Filter[] = [{authors: [pubkey], limit: 1}]
|
||||
const events = deriveEvents(repository, {filters})
|
||||
const events = deriveArray(deriveEventsById({repository, filters}))
|
||||
const groupList = deriveGroupList(pubkey)
|
||||
const spaceUrls = $derived(getSpaceUrlsFromGroupList($groupList))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import cx from "classnames"
|
||||
import {onMount} from "svelte"
|
||||
import type {Snippet} from "svelte"
|
||||
import {groupBy, sum, uniq, uniqBy, batch, displayList} from "@welshman/lib"
|
||||
import {groupBy, map, sum, uniq, uniqBy, batch, displayList} from "@welshman/lib"
|
||||
import {
|
||||
REPORT,
|
||||
REACTION,
|
||||
@@ -15,7 +15,7 @@
|
||||
DELETE,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, EventContent, Zap} from "@welshman/util"
|
||||
import {deriveEvents, deriveEventsMapped} from "@welshman/store"
|
||||
import {deriveArray, deriveEventsById, deriveItemsByKey} from "@welshman/store"
|
||||
import {load} from "@welshman/net"
|
||||
import {pubkey, repository, getValidZap, displayProfileByPubkey} from "@welshman/app"
|
||||
import {isMobile, preventDefault, stopPropagation} from "@lib/html"
|
||||
@@ -46,19 +46,22 @@
|
||||
children,
|
||||
}: Props = $props()
|
||||
|
||||
const reports = deriveEvents(repository, {
|
||||
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
||||
})
|
||||
const reports = deriveArray(
|
||||
deriveEventsById({repository, filters: [{kinds: [REPORT], "#e": [event.id]}]}),
|
||||
)
|
||||
|
||||
const reactions = deriveEvents(repository, {
|
||||
filters: [{kinds: [REACTION], "#e": [event.id]}],
|
||||
})
|
||||
const reactions = deriveArray(
|
||||
deriveEventsById({repository, filters: [{kinds: [REACTION], "#e": [event.id]}]}),
|
||||
)
|
||||
|
||||
const zaps = deriveEventsMapped<Zap>(repository, {
|
||||
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}],
|
||||
itemToEvent: item => item.response,
|
||||
eventToItem: (response: TrustedEvent) => getValidZap(response, event),
|
||||
})
|
||||
const zaps = deriveArray(
|
||||
deriveItemsByKey<Zap>({
|
||||
repository,
|
||||
getKey: zap => zap.response.id,
|
||||
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}],
|
||||
eventToItem: (response: TrustedEvent) => getValidZap(response, event),
|
||||
}),
|
||||
)
|
||||
|
||||
const onReactionClick = (events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
@@ -77,18 +80,18 @@
|
||||
|
||||
const onReportClick = () => pushModal(ReportDetails, {url, event})
|
||||
|
||||
const reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
|
||||
const reportReasons = $derived(uniq(map(e => getTag("e", e.tags)?.[2], $reports.values())))
|
||||
|
||||
const getReactionKey = (e: TrustedEvent) => getEmojiTag(e.content, e.tags)?.join("") || e.content
|
||||
|
||||
const groupedReactions = $derived(
|
||||
groupBy(
|
||||
getReactionKey,
|
||||
uniqBy(e => `${e.pubkey}${getReactionKey(e)}`, $reactions),
|
||||
uniqBy(e => `${e.pubkey}${getReactionKey(e)}`, $reactions.values()),
|
||||
),
|
||||
)
|
||||
|
||||
const groupedZaps = $derived(groupBy(e => getReactionKey(e.request), $zaps))
|
||||
const groupedZaps = $derived(groupBy(e => getReactionKey(e.request), $zaps.values()))
|
||||
|
||||
onMount(() => {
|
||||
const controller = new AbortController()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {REPORT} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import {deriveEventsById} from "@welshman/store"
|
||||
import {repository} from "@welshman/app"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -14,14 +14,15 @@
|
||||
|
||||
const {url, event}: Props = $props()
|
||||
|
||||
const reports = deriveEvents(repository, {
|
||||
const reports = deriveEventsById({
|
||||
repository,
|
||||
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
||||
})
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const onDelete = () => {
|
||||
if ($reports.length === 0) {
|
||||
if ($reports.size === 0) {
|
||||
back()
|
||||
}
|
||||
}
|
||||
@@ -36,7 +37,7 @@
|
||||
<div>All reports for this event are shown below.</div>
|
||||
{/snippet}
|
||||
</ModalHeader>
|
||||
{#each $reports as report (report.id)}
|
||||
{#each $reports.values() as report (report.id)}
|
||||
<div class="card2 card2-sm bg-alt">
|
||||
<ReportItem {url} event={report} {onDelete} />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {uniqBy, prop, ifLet} from "@welshman/lib"
|
||||
import {ifLet} from "@welshman/lib"
|
||||
import type {RelayProfile} from "@welshman/util"
|
||||
import {displayRelayUrl, ManagementMethod} from "@welshman/util"
|
||||
import {manageRelay, relays, fetchRelayDirectly} from "@welshman/app"
|
||||
import {manageRelay, relaysByUrl, notifyRelay, fetchRelayDirectly} from "@welshman/app"
|
||||
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
|
||||
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
@@ -78,6 +78,8 @@
|
||||
|
||||
return new Map($relaysByUrl)
|
||||
})
|
||||
|
||||
notifyRelay(relay)
|
||||
})
|
||||
|
||||
pushToast({message: "Your changes have been saved!"})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {derived} from "svelte/store"
|
||||
import {some} from "@welshman/lib"
|
||||
import {displayRelayUrl, getTagValue, EVENT_TIME, ZAP_GOAL, THREAD, REPORT} from "@welshman/util"
|
||||
import {deriveRelay, pubkey} from "@welshman/app"
|
||||
import {fly} from "@lib/transition"
|
||||
@@ -40,16 +41,15 @@
|
||||
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
|
||||
import {
|
||||
ENABLE_ZAPS,
|
||||
CONTENT_KINDS,
|
||||
deriveSpaceMembers,
|
||||
deriveEventsForUrl,
|
||||
deriveUserRooms,
|
||||
deriveOtherRooms,
|
||||
userSpaceUrls,
|
||||
hasNip29,
|
||||
alerts,
|
||||
alertsById,
|
||||
deriveUserCanCreateRoom,
|
||||
deriveUserIsSpaceAdmin,
|
||||
deriveEventsForUrl,
|
||||
} from "@app/core/state"
|
||||
import {notifications} from "@app/util/notifications"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
@@ -67,10 +67,12 @@
|
||||
const members = deriveSpaceMembers(url)
|
||||
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||
const reports = deriveEventsForUrl(url, [{kinds: [REPORT]}])
|
||||
const hasAlerts = $derived($alerts.some(a => getTagValue("feed", a.tags)?.includes(url)))
|
||||
const hasAlerts = $derived(
|
||||
some(a => getTagValue("feed", a.tags)?.includes(url), $alertsById.values()),
|
||||
)
|
||||
|
||||
const spaceKinds = derived(
|
||||
deriveEventsForUrl(url, [{kinds: CONTENT_KINDS}]),
|
||||
deriveEventsForUrl(url, [{kinds: [REPORT]}]),
|
||||
$events => new Set($events.map(e => e.kind)),
|
||||
)
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ import {
|
||||
APP_DATA,
|
||||
isSignedEvent,
|
||||
makeEvent,
|
||||
displayProfile,
|
||||
normalizeRelayUrl,
|
||||
makeList,
|
||||
addToListPublicly,
|
||||
@@ -79,7 +78,6 @@ import {
|
||||
session,
|
||||
repository,
|
||||
publishThunk,
|
||||
profilesByPubkey,
|
||||
tagEvent,
|
||||
tagEventForReaction,
|
||||
userRelayList,
|
||||
@@ -90,7 +88,7 @@ import {
|
||||
tagEventForQuote,
|
||||
waitForThunkError,
|
||||
getPubkeyRelays,
|
||||
userBlossomServers,
|
||||
userBlossomServerList,
|
||||
shouldUnwrap,
|
||||
} from "@welshman/app"
|
||||
import {compressFile} from "@lib/html"
|
||||
@@ -106,7 +104,6 @@ import {
|
||||
userSpaceUrls,
|
||||
userSettingsValues,
|
||||
getSetting,
|
||||
userMessagingRelays,
|
||||
userGroupList,
|
||||
shouldIgnoreError,
|
||||
} from "@app/core/state"
|
||||
@@ -122,13 +119,6 @@ export const getPubkeyHints = (pubkey: string) => {
|
||||
return hints
|
||||
}
|
||||
|
||||
export const getPubkeyPetname = (pubkey: string) => {
|
||||
const profile = profilesByPubkey.get().get(pubkey)
|
||||
const display = displayProfile(profile)
|
||||
|
||||
return display
|
||||
}
|
||||
|
||||
export const prependParent = (parent: TrustedEvent | undefined, {content, tags}: EventContent) => {
|
||||
if (parent) {
|
||||
const nevent = nip19.neventEncode({
|
||||
@@ -538,11 +528,13 @@ export const createDmAlert = async () => {
|
||||
shouldUnwrap.set(true)
|
||||
}
|
||||
|
||||
const $pubkey = pubkey.get()!
|
||||
|
||||
return createAlert({
|
||||
description: `for direct messages.`,
|
||||
feed: makeIntersectionFeed(
|
||||
feedFromFilters([{kinds: [WRAP], "#p": [pubkey.get()!]}]),
|
||||
makeRelayFeed(...get(userMessagingRelays)),
|
||||
feedFromFilters([{kinds: [WRAP], "#p": [$pubkey]}]),
|
||||
makeRelayFeed(...getPubkeyRelays($pubkey, RelayMode.Messaging)),
|
||||
),
|
||||
})
|
||||
}
|
||||
@@ -651,7 +643,7 @@ export const getBlossomServer = async (options: GetBlossomServerOptions = {}) =>
|
||||
}
|
||||
}
|
||||
|
||||
const userUrls = getTagValues("server", getListTags(userBlossomServers.get()))
|
||||
const userUrls = getTagValues("server", getListTags(userBlossomServerList.get()))
|
||||
|
||||
for (const url of userUrls) {
|
||||
return normalizeBlossomUrl(url)
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
int,
|
||||
YEAR,
|
||||
DAY,
|
||||
assoc,
|
||||
insertAt,
|
||||
sortBy,
|
||||
now,
|
||||
@@ -32,7 +33,7 @@ import {load, request} from "@welshman/net"
|
||||
import {repository, makeFeedController, loadRelay, tracker} from "@welshman/app"
|
||||
import {createScroller} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
import {NOTIFIER_RELAY, getEventsForUrl} from "@app/core/state"
|
||||
import {NOTIFIER_RELAY} from "@app/core/state"
|
||||
|
||||
// Utils
|
||||
|
||||
@@ -47,7 +48,9 @@ export const makeFeed = ({
|
||||
element: HTMLElement
|
||||
onExhausted?: () => void
|
||||
}) => {
|
||||
const initialEvents = getEventsForUrl(url, filters)
|
||||
const initialIds = Array.from(tracker.getIds(url))
|
||||
const initialFilters = filters.map(assoc("ids", initialIds))
|
||||
const initialEvents = repository.query(initialFilters)
|
||||
const seen = new Set(initialEvents.map(e => e.id))
|
||||
const controller = new AbortController()
|
||||
const buffer = writable(initialEvents)
|
||||
@@ -144,7 +147,9 @@ export const makeCalendarFeed = ({
|
||||
}) => {
|
||||
const interval = int(5, DAY)
|
||||
const controller = new AbortController()
|
||||
const initialEvents = getEventsForUrl(url, filters)
|
||||
const initialIds = Array.from(tracker.getIds(url))
|
||||
const initialFilters = filters.map(assoc("ids", initialIds))
|
||||
const initialEvents = repository.query(initialFilters)
|
||||
|
||||
let exhaustedScrollers = 0
|
||||
let backwardWindow = [now() - interval, now()]
|
||||
|
||||
+224
-276
@@ -1,33 +1,35 @@
|
||||
import twColors from "tailwindcss/colors"
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import {get, derived, writable} from "svelte/store"
|
||||
import {get, derived, readable, writable} from "svelte/store"
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {
|
||||
on,
|
||||
gt,
|
||||
max,
|
||||
find,
|
||||
spec,
|
||||
call,
|
||||
first,
|
||||
assoc,
|
||||
remove,
|
||||
uniqBy,
|
||||
sortBy,
|
||||
append,
|
||||
sort,
|
||||
prop,
|
||||
uniq,
|
||||
indexBy,
|
||||
partition,
|
||||
pushToMapKey,
|
||||
shuffle,
|
||||
parseJson,
|
||||
memoize,
|
||||
addToMapKey,
|
||||
identity,
|
||||
groupBy,
|
||||
always,
|
||||
tryCatch,
|
||||
fromPairs,
|
||||
} from "@welshman/lib"
|
||||
import type {Socket} from "@welshman/net"
|
||||
import type {Override} from "@welshman/lib"
|
||||
import type {RepositoryUpdate} from "@welshman/net"
|
||||
import {
|
||||
Pool,
|
||||
load,
|
||||
@@ -37,7 +39,17 @@ import {
|
||||
SocketEvent,
|
||||
netContext,
|
||||
} from "@welshman/net"
|
||||
import {collection, custom, throttled, deriveEvents, deriveEventsMapped} from "@welshman/store"
|
||||
import {
|
||||
getter,
|
||||
throttled,
|
||||
deriveArray,
|
||||
makeDeriveEvent,
|
||||
makeLoadItem,
|
||||
makeDeriveItem,
|
||||
deriveItemsByKey,
|
||||
deriveEventsByIdByUrl,
|
||||
deriveEventsByIdForUrl,
|
||||
} from "@welshman/store"
|
||||
import {isKindFeed, findFeed} from "@welshman/feeds"
|
||||
import {
|
||||
ALERT_ANDROID,
|
||||
@@ -72,16 +84,14 @@ import {
|
||||
ROOMS,
|
||||
THREAD,
|
||||
WRAP,
|
||||
PROFILE,
|
||||
ZAP_GOAL,
|
||||
ZAP_REQUEST,
|
||||
ZAP_RESPONSE,
|
||||
asDecryptedEvent,
|
||||
displayProfile,
|
||||
getGroupTags,
|
||||
getIdFilters,
|
||||
getListTags,
|
||||
getPubkeyTagValues,
|
||||
getRelaysFromList,
|
||||
getRelayTagValues,
|
||||
getTagValue,
|
||||
getTagValues,
|
||||
@@ -89,47 +99,33 @@ import {
|
||||
makeEvent,
|
||||
normalizeRelayUrl,
|
||||
readList,
|
||||
RelayMode,
|
||||
verifyEvent,
|
||||
readRoomMeta,
|
||||
makeRoomMeta,
|
||||
ManagementMethod,
|
||||
} from "@welshman/util"
|
||||
import type {
|
||||
TrustedEvent,
|
||||
RelayProfile,
|
||||
PublishedList,
|
||||
PublishedRoomMeta,
|
||||
List,
|
||||
Filter,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, RelayProfile, PublishedRoomMeta, List, Filter} from "@welshman/util"
|
||||
import {decrypt} from "@welshman/signer"
|
||||
import {routerContext, Router} from "@welshman/router"
|
||||
import {
|
||||
pubkey,
|
||||
repository,
|
||||
profilesByPubkey,
|
||||
tracker,
|
||||
makeTrackerStore,
|
||||
makeRepositoryStore,
|
||||
createSearch,
|
||||
userFollows,
|
||||
userFollowList,
|
||||
ensurePlaintext,
|
||||
thunks,
|
||||
sign,
|
||||
signer,
|
||||
makeOutboxLoader,
|
||||
appContext,
|
||||
getThunkError,
|
||||
publishThunk,
|
||||
userRelayList,
|
||||
userMessagingRelayList,
|
||||
deriveRelay,
|
||||
makeUserData,
|
||||
makeUserLoader,
|
||||
manageRelay,
|
||||
displayProfileByPubkey,
|
||||
} from "@welshman/app"
|
||||
import type {Thunk} from "@welshman/app"
|
||||
|
||||
export const fromCsv = (s: string) => (s || "").split(",").filter(identity)
|
||||
|
||||
@@ -208,87 +204,28 @@ export const entityLink = (entity: string) => `https://coracle.social/${entity}`
|
||||
export const pubkeyLink = (pubkey: string, relays = Router.get().FromPubkeys([pubkey]).getUrls()) =>
|
||||
entityLink(nip19.nprofileEncode({pubkey, relays}))
|
||||
|
||||
export const bootstrapPubkeys = derived(userFollows, $userFollows => {
|
||||
export const bootstrapPubkeys = derived(userFollowList, $userFollowList => {
|
||||
const appPubkeys = DEFAULT_PUBKEYS.split(",")
|
||||
const userPubkeys = shuffle(getPubkeyTagValues(getListTags($userFollows)))
|
||||
const userPubkeys = shuffle(getPubkeyTagValues(getListTags($userFollowList)))
|
||||
|
||||
return userPubkeys.length > 5 ? userPubkeys : [...userPubkeys, ...appPubkeys]
|
||||
})
|
||||
|
||||
export const trackerStore = makeTrackerStore()
|
||||
|
||||
export const repositoryStore = makeRepositoryStore()
|
||||
|
||||
export const deriveEvent = (idOrAddress: string, hints: string[] = []) => {
|
||||
let attempted = false
|
||||
|
||||
const filters = getIdFilters([idOrAddress])
|
||||
const relays = [...hints, ...INDEXER_RELAYS]
|
||||
|
||||
return derived(
|
||||
deriveEvents(repository, {filters, includeDeleted: true}),
|
||||
(events: TrustedEvent[]) => {
|
||||
if (!attempted && events.length === 0) {
|
||||
load({relays, filters})
|
||||
attempted = true
|
||||
}
|
||||
|
||||
return events[0]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export const getUrlsForEvent = derived([trackerStore, thunks], ([$tracker, $thunks]) => {
|
||||
const getThunksByEventId = memoize(() => {
|
||||
const thunksByEventId = new Map<string, Thunk[]>()
|
||||
|
||||
for (const thunk of $thunks) {
|
||||
pushToMapKey(thunksByEventId, thunk.event.id, thunk)
|
||||
}
|
||||
|
||||
return thunksByEventId
|
||||
})
|
||||
|
||||
return (id: string) => {
|
||||
const urls = $tracker.getRelays(id)
|
||||
|
||||
for (const thunk of getThunksByEventId().get(id) || []) {
|
||||
for (const url of thunk.options.relays) {
|
||||
urls.add(url)
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(urls)
|
||||
}
|
||||
export const deriveEvent = makeDeriveEvent({
|
||||
repository,
|
||||
includeDeleted: true,
|
||||
onDerive: (filters: Filter[], relays: string[]) => load({filters, relays}),
|
||||
})
|
||||
|
||||
export const getEventsForUrl = (url: string, filters: Filter[]) => {
|
||||
const ids = uniq([
|
||||
...tracker.getIds(url),
|
||||
...get(thunks)
|
||||
.filter(t => t.options.relays.includes(url))
|
||||
.map(t => t.event.id),
|
||||
])
|
||||
|
||||
return repository.query(filters.map(assoc("ids", ids)))
|
||||
}
|
||||
|
||||
export const deriveEventsForUrl = (url: string, filters: Filter[]) =>
|
||||
derived([trackerStore, thunks], ([$tracker, $thunks]) => {
|
||||
const ids = uniq([
|
||||
...$tracker.getIds(url),
|
||||
...$thunks.filter(t => t.options.relays.includes(url)).map(t => t.event.id),
|
||||
])
|
||||
deriveArray(deriveEventsByIdForUrl({url, tracker, repository, filters}))
|
||||
|
||||
return repository.query(filters.map(assoc("ids", ids)))
|
||||
})
|
||||
|
||||
export const deriveSignedEventsForUrl = (url: string, filters: Filter[]) =>
|
||||
export const deriveRelaySignedEvents = (url: string, filters: Filter[]) =>
|
||||
derived(
|
||||
[deriveEventsForUrl(url, filters), deriveRelay(url)],
|
||||
([$events, $relay]) => $events,
|
||||
// Disable this check for now since khatru doesn't support self
|
||||
// $relay?.self ? $events.filter(spec({pubkey: $relay.self})) : [],
|
||||
[deriveRelay(url), deriveEventsForUrl(url, filters)],
|
||||
([relay, events]) => events,
|
||||
// khatru doesn't support relay.self, uncomment when it's ready
|
||||
// filter(spec({pubkey: relay.self}), events)
|
||||
)
|
||||
|
||||
// Context
|
||||
@@ -351,25 +288,25 @@ export const defaultSettings = {
|
||||
show_notifications_badge: true,
|
||||
}
|
||||
|
||||
export const settings = deriveEventsMapped<Settings>(repository, {
|
||||
export const settingsByPubkey = deriveItemsByKey({
|
||||
repository,
|
||||
getKey: settings => settings.event.pubkey,
|
||||
filters: [{kinds: [APP_DATA], "#d": [SETTINGS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async (event: TrustedEvent) => ({
|
||||
event,
|
||||
values: {...defaultSettings, ...parseJson(await ensurePlaintext(event))},
|
||||
}),
|
||||
eventToItem: async (event: TrustedEvent) => {
|
||||
const values = {...defaultSettings, ...parseJson(await ensurePlaintext(event))}
|
||||
|
||||
return {event, values}
|
||||
},
|
||||
})
|
||||
|
||||
export const {
|
||||
indexStore: settingsByPubkey,
|
||||
deriveItem: deriveSettings,
|
||||
loadItem: loadSettings,
|
||||
} = collection({
|
||||
name: "settings",
|
||||
store: settings,
|
||||
getKey: settings => settings.event.pubkey,
|
||||
load: makeOutboxLoader(APP_DATA, {"#d": [SETTINGS]}),
|
||||
})
|
||||
export const getSettingsByPubkey = getter(settingsByPubkey)
|
||||
|
||||
export const getSettings = (pubkey: string) => getSettingsByPubkey().get(pubkey)
|
||||
|
||||
export const loadSettings = makeLoadItem(
|
||||
makeOutboxLoader(APP_DATA, {"#d": [SETTINGS]}),
|
||||
getSettings,
|
||||
)
|
||||
|
||||
export const userSettings = makeUserData({
|
||||
mapStore: settingsByPubkey,
|
||||
@@ -397,9 +334,10 @@ export type Alert = {
|
||||
tags: string[][]
|
||||
}
|
||||
|
||||
export const alerts = deriveEventsMapped<Alert>(repository, {
|
||||
export const alertsById = deriveItemsByKey<Alert>({
|
||||
repository,
|
||||
getKey: alert => alert.event.id,
|
||||
filters: [{kinds: [ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async event => {
|
||||
const $signer = signer.get()
|
||||
|
||||
@@ -414,13 +352,13 @@ export const alerts = deriveEventsMapped<Alert>(repository, {
|
||||
export const getAlertFeed = (alert: Alert) =>
|
||||
tryCatch(() => JSON.parse(getTagValue("feed", alert.tags)!))
|
||||
|
||||
export const dmAlert = derived(alerts, $alerts =>
|
||||
$alerts.find(alert => {
|
||||
const feed = getAlertFeed(alert)
|
||||
|
||||
return findFeed(feed, f => isKindFeed(f) && f.includes(WRAP))
|
||||
}),
|
||||
)
|
||||
export const dmAlert = derived(alertsById, $alertsById => {
|
||||
for (const alert of $alertsById.values()) {
|
||||
if (findFeed(getAlertFeed(alert), f => isKindFeed(f) && f.includes(WRAP))) {
|
||||
return alert
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Alert Statuses
|
||||
|
||||
@@ -429,9 +367,10 @@ export type AlertStatus = {
|
||||
tags: string[][]
|
||||
}
|
||||
|
||||
export const alertStatuses = deriveEventsMapped<AlertStatus>(repository, {
|
||||
export const alertStatusesByAddress = deriveItemsByKey<AlertStatus>({
|
||||
repository,
|
||||
filters: [{kinds: [ALERT_STATUS]}],
|
||||
itemToEvent: item => item.event,
|
||||
getKey: alertStatus => getTagValue("d", alertStatus.event.tags)!,
|
||||
eventToItem: async event => {
|
||||
const $signer = signer.get()
|
||||
|
||||
@@ -443,15 +382,10 @@ export const alertStatuses = deriveEventsMapped<AlertStatus>(repository, {
|
||||
},
|
||||
})
|
||||
|
||||
export const deriveAlertStatus = (address: string) =>
|
||||
derived(alertStatuses, statuses => statuses.find(s => getTagValue("d", s.event.tags) === address))
|
||||
export const deriveAlertStatus = makeDeriveItem(alertStatusesByAddress)
|
||||
|
||||
// Chats
|
||||
|
||||
export const chatMessages = deriveEvents(repository, {
|
||||
filters: [{kinds: [DIRECT_MESSAGE, DIRECT_MESSAGE_FILE]}],
|
||||
})
|
||||
|
||||
export type Chat = {
|
||||
id: string
|
||||
pubkeys: string[]
|
||||
@@ -460,66 +394,77 @@ export type Chat = {
|
||||
search_text: string
|
||||
}
|
||||
|
||||
export const makeChatId = (pubkeys: string[]) => sort(uniq(pubkeys.concat(pubkey.get()!))).join(",")
|
||||
export const makeChatId = (pubkeys: string[]) => sort(uniq(pubkeys)).join(",")
|
||||
|
||||
export const splitChatId = (id: string) => id.split(",")
|
||||
|
||||
export const chats = derived(
|
||||
[pubkey, chatMessages, profilesByPubkey],
|
||||
([$pubkey, $messages, $profilesByPubkey]) => {
|
||||
const messagesByChatId = new Map<string, TrustedEvent[]>()
|
||||
export const chatsById = call(() => {
|
||||
const chatsById = new Map<string, Chat>()
|
||||
const chatsByPubkey = new Map<string, Chat[]>()
|
||||
|
||||
for (const message of $messages) {
|
||||
const chatId = makeChatId(getPubkeyTagValues(message.tags).concat(message.pubkey))
|
||||
const addSearchText = (chat: Override<Chat, {search_text?: string}>) => {
|
||||
chat.search_text =
|
||||
chat.pubkeys.length === 1
|
||||
? displayProfileByPubkey(chat.pubkeys[0]) + " note to self"
|
||||
: chat.pubkeys.map(displayProfileByPubkey).join(" ")
|
||||
|
||||
pushToMapKey(messagesByChatId, chatId, message)
|
||||
}
|
||||
return chat as Chat
|
||||
}
|
||||
|
||||
const displayPubkey = (pubkey: string) => {
|
||||
const profile = $profilesByPubkey.get(pubkey)
|
||||
return readable(chatsById, set => {
|
||||
const unsubscribers = [
|
||||
on(repository, "update", ({added}: RepositoryUpdate) => {
|
||||
let dirty = false
|
||||
for (const event of added) {
|
||||
if ([DIRECT_MESSAGE, DIRECT_MESSAGE_FILE].includes(event.kind)) {
|
||||
const pubkeys = getPubkeyTagValues(event.tags).concat(event.pubkey)
|
||||
const id = makeChatId(pubkeys)
|
||||
const chat = chatsById.get(id)
|
||||
const messages = append(event, chat?.messages || [])
|
||||
const last_activity = Math.max(chat?.last_activity || 0, event.created_at)
|
||||
const updatedChat = addSearchText({id, pubkeys, messages, last_activity})
|
||||
|
||||
return profile ? displayProfile(profile) : ""
|
||||
}
|
||||
chatsById.set(id, updatedChat)
|
||||
|
||||
return sortBy(
|
||||
c => -c.last_activity,
|
||||
Array.from(messagesByChatId.entries()).map(([id, events]): Chat => {
|
||||
const pubkeys = remove($pubkey!, splitChatId(id))
|
||||
const messages = sortBy(e => -e.created_at, uniqBy(prop("id"), events))
|
||||
const last_activity = messages[0].created_at
|
||||
const search_text =
|
||||
pubkeys.length === 0
|
||||
? displayPubkey($pubkey!) + " note to self"
|
||||
: pubkeys.map(displayPubkey).join(" ")
|
||||
for (const pubkey of pubkeys) {
|
||||
const pubkeyChats = chatsByPubkey.get(pubkey) || []
|
||||
const uniqueChats = uniqBy(chat => chat.id, append(updatedChat, pubkeyChats))
|
||||
|
||||
return {id, pubkeys, messages, last_activity, search_text}
|
||||
chatsByPubkey.set(pubkey, uniqueChats)
|
||||
}
|
||||
|
||||
dirty = true
|
||||
}
|
||||
|
||||
if (event.kind === PROFILE) {
|
||||
for (const chat of chatsByPubkey.get(event.pubkey) || []) {
|
||||
addSearchText(chat)
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
set(chatsById)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
export const {
|
||||
indexStore: chatsById,
|
||||
deriveItem: deriveChat,
|
||||
loadItem: loadChat,
|
||||
} = collection({
|
||||
name: "chats",
|
||||
store: chats,
|
||||
getKey: chat => chat.id,
|
||||
load: always(Promise.resolve()),
|
||||
return () => unsubscribers.forEach(call)
|
||||
})
|
||||
})
|
||||
|
||||
export const chatSearch = derived(chats, $chats =>
|
||||
createSearch($chats, {
|
||||
export const deriveChat = makeDeriveItem(chatsById)
|
||||
|
||||
export const chatSearch = derived(throttled(800, chatsById), $chatsByPubkey => {
|
||||
return createSearch(Array.from($chatsByPubkey.values()), {
|
||||
getValue: (chat: Chat) => chat.id,
|
||||
fuseOptions: {keys: ["search_text"]},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Rooms
|
||||
|
||||
export const messages = deriveEvents(repository, {filters: [{kinds: [MESSAGE]}]})
|
||||
|
||||
export type Room = PublishedRoomMeta & {
|
||||
id: string
|
||||
url: string
|
||||
@@ -532,95 +477,94 @@ export const splitRoomId = (id: string) => id.split("'")
|
||||
export const hasNip29 = (relay?: RelayProfile) =>
|
||||
relay?.supported_nips?.map?.(String)?.includes?.("29")
|
||||
|
||||
export const roomMetas = deriveEventsMapped<PublishedRoomMeta>(repository, {
|
||||
filters: [{kinds: [ROOM_META]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: readRoomMeta,
|
||||
export const roomMetaEventsByIdByUrl = deriveEventsByIdByUrl({
|
||||
tracker,
|
||||
repository,
|
||||
filters: [{kinds: [ROOM_META, ROOM_DELETE]}],
|
||||
})
|
||||
|
||||
export const roomDeletes = deriveEvents(repository, {
|
||||
filters: [{kinds: [ROOM_DELETE]}],
|
||||
})
|
||||
export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByUrl => {
|
||||
const result = new Map<string, Room[]>()
|
||||
|
||||
export const rooms = derived(
|
||||
[roomMetas, roomDeletes, getUrlsForEvent],
|
||||
([$roomMetas, $roomDeletes, $getUrlsForEvent]) => {
|
||||
const result = new Map<string, Room>()
|
||||
for (const [url, events] of roomMetaEventsByIdByUrl.entries()) {
|
||||
const [metaEvents, deleteEvents] = partition(spec({kind: ROOM_META}), events.values())
|
||||
const deletedByH = new Map<string, number>()
|
||||
|
||||
for (const event of $roomDeletes) {
|
||||
for (const event of deleteEvents) {
|
||||
for (const h of getTagValues("h", event.tags)) {
|
||||
deletedByH.set(h, max([deletedByH.get(h), event.created_at]))
|
||||
}
|
||||
}
|
||||
|
||||
for (const meta of $roomMetas) {
|
||||
for (const event of metaEvents) {
|
||||
const meta = readRoomMeta(event)
|
||||
|
||||
if (gt(deletedByH.get(meta.h), meta.event.created_at)) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const url of $getUrlsForEvent(meta.event.id)) {
|
||||
const id = makeRoomId(url, meta.h)
|
||||
|
||||
result.set(id, {...meta, url, id})
|
||||
}
|
||||
pushToMapKey(result, url, {...meta, url, id: makeRoomId(url, meta.h)})
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(result.values())
|
||||
},
|
||||
return result
|
||||
})
|
||||
|
||||
export const roomsById = derived(roomsByUrl, roomsByUrl =>
|
||||
indexBy(room => room.id, Array.from(roomsByUrl.values()).flatMap(identity)),
|
||||
)
|
||||
|
||||
export const roomsByUrl = derived(rooms, $rooms => groupBy(c => c.url, $rooms))
|
||||
export const getRoomsById = getter(roomsById)
|
||||
|
||||
export const {
|
||||
indexStore: roomsById,
|
||||
deriveItem: _deriveRoom,
|
||||
loadItem: _loadRoom,
|
||||
} = collection({
|
||||
name: "rooms",
|
||||
store: rooms,
|
||||
getKey: room => room.id,
|
||||
load: async (id: string) => {
|
||||
export const getRoom = (id: string) => getRoomsById().get(id)
|
||||
|
||||
export const loadRoom = call(() => {
|
||||
const _fetchRoom = async (id: string) => {
|
||||
const [url, h] = splitRoomId(id)
|
||||
|
||||
await load({
|
||||
relays: [url],
|
||||
filters: [{kinds: [ROOM_META], "#d": [h]}],
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const _loadRoom = makeLoadItem(_fetchRoom, getRoom)
|
||||
|
||||
return (url: string, h: string) => _loadRoom(makeRoomId(url, h))
|
||||
})
|
||||
|
||||
export const deriveRoom = (url: string, h: string) =>
|
||||
derived(_deriveRoom(makeRoomId(url, h)), $meta => $meta || makeRoomMeta({h}))
|
||||
export const deriveRoom = call(() => {
|
||||
const _deriveRoom = makeDeriveItem(roomsById, loadRoom)
|
||||
|
||||
export const displayRoom = (url: string, h: string) =>
|
||||
roomsById.get().get(makeRoomId(url, h))?.name || h
|
||||
return (url: string, h: string) =>
|
||||
derived(_deriveRoom(makeRoomId(url, h)), room => room || makeRoomMeta({h}))
|
||||
})
|
||||
|
||||
export const displayRoom = (url: string, h: string) => getRoom(makeRoomId(url, h))?.name || h
|
||||
|
||||
export const roomComparator = (url: string) => (h: string) => displayRoom(url, h).toLowerCase()
|
||||
|
||||
// User space/room lists
|
||||
|
||||
export const groupLists = deriveEventsMapped<PublishedList>(repository, {
|
||||
export const groupListsByPubkey = deriveItemsByKey({
|
||||
repository,
|
||||
filters: [{kinds: [ROOMS]}],
|
||||
itemToEvent: item => item.event,
|
||||
getKey: list => list.event.pubkey,
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
|
||||
export const {
|
||||
indexStore: groupListsByPubkey,
|
||||
deriveItem: deriveGroupList,
|
||||
loadItem: loadGroupList,
|
||||
} = collection({
|
||||
name: "groupLists",
|
||||
store: groupLists,
|
||||
getKey: list => list.event.pubkey,
|
||||
load: makeOutboxLoader(ROOMS),
|
||||
})
|
||||
export const getGroupListsByPubkey = getter(groupListsByPubkey)
|
||||
|
||||
export const groupListsPubkeysByUrl = derived(groupLists, $groupLists => {
|
||||
export const getGroupList = (pubkey: string) => getGroupListsByPubkey().get(pubkey)
|
||||
|
||||
export const loadGroupList = makeLoadItem(makeOutboxLoader(ROOMS), getGroupList)
|
||||
|
||||
export const deriveGroupList = makeDeriveItem(groupListsByPubkey, loadGroupList)
|
||||
|
||||
export const groupListsPubkeysByUrl = derived(groupListsByPubkey, $groupListsByPubkey => {
|
||||
const result = new Map<string, Set<string>>()
|
||||
|
||||
for (const list of $groupLists) {
|
||||
for (const list of $groupListsByPubkey.values()) {
|
||||
const tags = getListTags(list)
|
||||
|
||||
for (const url of getRelayTagValues(tags)) {
|
||||
@@ -639,8 +583,8 @@ export const groupListsPubkeysByUrl = derived(groupLists, $groupLists => {
|
||||
return result
|
||||
})
|
||||
|
||||
export const getSpaceUrlsFromGroupList = ($groupLists: List | undefined) => {
|
||||
const tags = getListTags($groupLists)
|
||||
export const getSpaceUrlsFromGroupList = (groupList: List | undefined) => {
|
||||
const tags = getListTags(groupList)
|
||||
const urls = getRelayTagValues(tags)
|
||||
|
||||
for (const tag of getGroupTags(tags)) {
|
||||
@@ -654,10 +598,10 @@ export const getSpaceUrlsFromGroupList = ($groupLists: List | undefined) => {
|
||||
return uniq(urls.map(normalizeRelayUrl))
|
||||
}
|
||||
|
||||
export const getSpaceRoomsFromGroupList = (url: string, $groupList: List | undefined) => {
|
||||
export const getSpaceRoomsFromGroupList = (url: string, groupList: List | undefined) => {
|
||||
const rooms: string[] = []
|
||||
|
||||
for (const [_, h, relay] of getGroupTags(getListTags($groupList))) {
|
||||
for (const [_, h, relay] of getGroupTags(getListTags(groupList))) {
|
||||
if (url === relay) {
|
||||
rooms.push(h)
|
||||
}
|
||||
@@ -673,7 +617,7 @@ export const userGroupList = makeUserData({
|
||||
|
||||
export const loadUserGroupList = makeUserLoader(loadGroupList)
|
||||
|
||||
export const userSpaceUrls = derived(userGroupList, getSpaceUrlsFromGroupLists)
|
||||
export const userSpaceUrls = derived(userGroupList, getSpaceUrlsFromGroupList)
|
||||
|
||||
export const deriveUserRooms = (url: string) =>
|
||||
derived([userGroupList, roomsById], ([$userGroupList, $roomsById]) => {
|
||||
@@ -705,9 +649,7 @@ export const deriveOtherRooms = (url: string) =>
|
||||
|
||||
export const deriveSpaceMembers = (url: string) =>
|
||||
derived(
|
||||
deriveSignedEventsForUrl(url, [
|
||||
{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER, RELAY_MEMBERS]},
|
||||
]),
|
||||
deriveRelaySignedEvents(url, [{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER, RELAY_MEMBERS]}]),
|
||||
$events => {
|
||||
const membersEvent = $events.find(spec({kind: RELAY_MEMBERS}))
|
||||
|
||||
@@ -755,43 +697,45 @@ export const deriveSpaceBannedPubkeyItems = (url: string) => {
|
||||
return store
|
||||
}
|
||||
|
||||
export const deriveRoomMembers = (url: string, h: string) =>
|
||||
derived(
|
||||
deriveEventsForUrl(url, [
|
||||
{kinds: [ROOM_MEMBERS], "#d": [h]},
|
||||
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]},
|
||||
]),
|
||||
$events => {
|
||||
const membersEvent = $events.find(spec({kind: ROOM_MEMBERS}))
|
||||
export const deriveRoomMembers = (url: string, h: string) => {
|
||||
const filters: Filter[] = [
|
||||
{kinds: [ROOM_MEMBERS], "#d": [h]},
|
||||
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]},
|
||||
]
|
||||
|
||||
if (membersEvent) {
|
||||
return uniq(getPubkeyTagValues(membersEvent.tags))
|
||||
}
|
||||
return derived(deriveEventsForUrl(url, filters), $events => {
|
||||
const membersEvent = find(spec({kind: ROOM_MEMBERS}), $events)
|
||||
|
||||
const members = new Set<string>()
|
||||
if (membersEvent) {
|
||||
return uniq(getPubkeyTagValues(membersEvent.tags))
|
||||
}
|
||||
|
||||
for (const event of sortBy(e => -e.created_at, $events)) {
|
||||
const pubkeys = getPubkeyTagValues(event.tags)
|
||||
const members = new Set<string>()
|
||||
|
||||
if (event.kind === ROOM_ADD_MEMBER) {
|
||||
for (const pubkey of pubkeys) {
|
||||
members.add(pubkey)
|
||||
}
|
||||
}
|
||||
for (const event of sortBy(e => -e.created_at, $events)) {
|
||||
const pubkeys = getPubkeyTagValues(event.tags)
|
||||
|
||||
if (event.kind === ROOM_REMOVE_MEMBER) {
|
||||
for (const pubkey of pubkeys) {
|
||||
members.delete(pubkey)
|
||||
}
|
||||
if (event.kind === ROOM_ADD_MEMBER) {
|
||||
for (const pubkey of pubkeys) {
|
||||
members.add(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(members)
|
||||
},
|
||||
)
|
||||
if (event.kind === ROOM_REMOVE_MEMBER) {
|
||||
for (const pubkey of pubkeys) {
|
||||
members.delete(pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveRoomAdmins = (url: string, h: string) =>
|
||||
derived(deriveEventsForUrl(url, [{kinds: [ROOM_ADMINS], "#d": [h]}]), $events => {
|
||||
return Array.from(members)
|
||||
})
|
||||
}
|
||||
|
||||
export const deriveRoomAdmins = (url: string, h: string) => {
|
||||
const filters: Filter[] = [{kinds: [ROOM_ADMINS], "#d": [h]}]
|
||||
|
||||
return derived(deriveEventsForUrl(url, filters), $events => {
|
||||
const adminsEvent = first($events)
|
||||
|
||||
if (adminsEvent) {
|
||||
@@ -800,6 +744,7 @@ export const deriveRoomAdmins = (url: string, h: string) =>
|
||||
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
// User membership status
|
||||
|
||||
@@ -821,12 +766,14 @@ export const deriveUserIsSpaceAdmin = memoize((url?: string) => {
|
||||
return store
|
||||
})
|
||||
|
||||
export const deriveUserSpaceMembershipStatus = (url: string) =>
|
||||
derived(
|
||||
export const deriveUserSpaceMembershipStatus = (url: string) => {
|
||||
const filters: Filter[] = [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]
|
||||
|
||||
return derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveSpaceMembers(url),
|
||||
deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
|
||||
deriveEventsForUrl(url, filters),
|
||||
deriveUserIsSpaceAdmin(url),
|
||||
],
|
||||
([$pubkey, $members, $events, $isAdmin]) => {
|
||||
@@ -849,6 +796,7 @@ export const deriveUserSpaceMembershipStatus = (url: string) =>
|
||||
return isMember ? MembershipStatus.Granted : MembershipStatus.Initial
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export const deriveUserIsRoomAdmin = (url: string, h: string) =>
|
||||
derived(
|
||||
@@ -856,12 +804,14 @@ export const deriveUserIsRoomAdmin = (url: string, h: string) =>
|
||||
([$pubkey, $admins, $isSpaceAdmin]) => $isSpaceAdmin || $admins.includes($pubkey!),
|
||||
)
|
||||
|
||||
export const deriveUserRoomMembershipStatus = (url: string, h: string) =>
|
||||
derived(
|
||||
export const deriveUserRoomMembershipStatus = (url: string, h: string) => {
|
||||
const filters: Filter[] = [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]
|
||||
|
||||
return derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveRoomMembers(url, h),
|
||||
deriveEventsForUrl(url, [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]),
|
||||
deriveEventsForUrl(url, filters),
|
||||
deriveUserIsRoomAdmin(url, h),
|
||||
],
|
||||
([$pubkey, $members, $events, $isAdmin]) => {
|
||||
@@ -884,14 +834,13 @@ export const deriveUserRoomMembershipStatus = (url: string, h: string) =>
|
||||
return isMember ? MembershipStatus.Granted : MembershipStatus.Initial
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export const deriveUserCanCreateRoom = (url: string) =>
|
||||
derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveEventsForUrl(url, [{kinds: [ROOM_CREATE_PERMISSION]}]),
|
||||
deriveUserIsSpaceAdmin(url),
|
||||
],
|
||||
export const deriveUserCanCreateRoom = (url: string) => {
|
||||
const filters: Filter[] = [{kinds: [ROOM_CREATE_PERMISSION]}]
|
||||
|
||||
return derived(
|
||||
[pubkey, deriveEventsForUrl(url, filters), deriveUserIsSpaceAdmin(url)],
|
||||
([$pubkey, $events, $isAdmin]) => {
|
||||
for (const event of $events) {
|
||||
if (getPubkeyTagValues(event.tags).includes($pubkey!)) {
|
||||
@@ -902,6 +851,7 @@ export const deriveUserCanCreateRoom = (url: string) =>
|
||||
return $isAdmin
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Other utils
|
||||
|
||||
@@ -920,13 +870,10 @@ export const displayReaction = (content: string) => {
|
||||
return content
|
||||
}
|
||||
|
||||
export const deriveSocket = (url: string) =>
|
||||
custom<Socket>(set => {
|
||||
const pool = Pool.get()
|
||||
const socket = pool.get(url)
|
||||
|
||||
set(socket)
|
||||
export const deriveSocket = (url: string) => {
|
||||
const socket = Pool.get().get(url)
|
||||
|
||||
return readable(socket, set => {
|
||||
const subs = [
|
||||
on(socket, SocketEvent.Error, () => set(socket)),
|
||||
on(socket, SocketEvent.Status, () => set(socket)),
|
||||
@@ -935,6 +882,7 @@ export const deriveSocket = (url: string) =>
|
||||
|
||||
return () => subs.forEach(call)
|
||||
})
|
||||
}
|
||||
|
||||
export const deriveSocketStatus = (url: string) =>
|
||||
throttled(
|
||||
|
||||
+12
-13
@@ -24,15 +24,16 @@ import {request, load, pull} from "@welshman/net"
|
||||
import {
|
||||
pubkey,
|
||||
loadRelay,
|
||||
userFollows,
|
||||
userFollowList,
|
||||
userRelayList,
|
||||
userMessagingRelayList,
|
||||
loadRelayList,
|
||||
loadMessagingRelayList,
|
||||
loadBlossomServers,
|
||||
loadFollows,
|
||||
loadMutes,
|
||||
loadBlossomServerList,
|
||||
loadFollowList,
|
||||
loadMuteList,
|
||||
loadProfile,
|
||||
tracker,
|
||||
repository,
|
||||
shouldUnwrap,
|
||||
hasNegentropy,
|
||||
@@ -48,7 +49,6 @@ import {
|
||||
userGroupList,
|
||||
bootstrapPubkeys,
|
||||
decodeRelay,
|
||||
getUrlsForEvent,
|
||||
getSpaceUrlsFromGroupList,
|
||||
getSpaceRoomsFromGroupList,
|
||||
makeCommentFilter,
|
||||
@@ -65,7 +65,6 @@ type PullOpts = {
|
||||
}
|
||||
|
||||
const pullWithFallback = ({relays, filters, signal}: PullOpts) => {
|
||||
const $getUrlsForEvent = get(getUrlsForEvent)
|
||||
const [smart, dumb] = partition(hasNegentropy, relays)
|
||||
const events = repository.query(filters, {shouldSort: false}).filter(isSignedEvent)
|
||||
const promises: Promise<TrustedEvent[]>[] = [pull({relays: smart, filters, signal, events})]
|
||||
@@ -73,7 +72,7 @@ const pullWithFallback = ({relays, filters, signal}: PullOpts) => {
|
||||
// Since pulling from relays without negentropy is expensive, limit how many
|
||||
// duplicates we repeatedly download
|
||||
for (const url of dumb) {
|
||||
const urlEvents = events.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
const urlEvents = events.filter(e => tracker.getRelays(e.id).has(url))
|
||||
|
||||
if (urlEvents.length >= 100) {
|
||||
filters = filters.map(assoc("since", sortBy(e => -e.created_at, urlEvents)[10]!.created_at))
|
||||
@@ -212,16 +211,16 @@ const syncUserData = () => {
|
||||
if ($pubkey) {
|
||||
loadAlerts($pubkey)
|
||||
loadAlertStatuses($pubkey)
|
||||
loadBlossomServers($pubkey)
|
||||
loadFollows($pubkey)
|
||||
loadBlossomServerList($pubkey)
|
||||
loadFollowList($pubkey)
|
||||
loadGroupList($pubkey)
|
||||
loadMutes($pubkey)
|
||||
loadMuteList($pubkey)
|
||||
loadProfile($pubkey)
|
||||
loadSettings($pubkey)
|
||||
}
|
||||
})
|
||||
|
||||
const unsubscribeFollows = userFollows.subscribe(async $l => {
|
||||
const unsubscribeFollows = userFollowList.subscribe(async $l => {
|
||||
for (const pubkeys of chunk(10, get(bootstrapPubkeys))) {
|
||||
// This isn't urgent, avoid clogging other stuff up
|
||||
await sleep(1000)
|
||||
@@ -231,8 +230,8 @@ const syncUserData = () => {
|
||||
await loadRelayList(pk)
|
||||
await loadGroupList(pk)
|
||||
await loadProfile(pk)
|
||||
await loadFollows(pk)
|
||||
await loadMutes(pk)
|
||||
await loadFollowList(pk)
|
||||
await loadMuteList(pk)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
+158
-140
@@ -1,9 +1,10 @@
|
||||
import {derived, get} from "svelte/store"
|
||||
import {Badge} from "@capawesome/capacitor-badge"
|
||||
import {synced, throttled} from "@welshman/store"
|
||||
import {pubkey, relaysByUrl} from "@welshman/app"
|
||||
import {prop, spec, identity, now, groupBy} from "@welshman/lib"
|
||||
import {pubkey, tracker, repository, relaysByUrl} from "@welshman/app"
|
||||
import {prop, find, call, spec, first, identity, now, groupBy} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {deriveEventsByIdByUrl} from "@welshman/store"
|
||||
import {ZAP_GOAL, EVENT_TIME, MESSAGE, THREAD, COMMENT, getTagValue} from "@welshman/util"
|
||||
import {
|
||||
makeSpacePath,
|
||||
@@ -15,10 +16,8 @@ import {
|
||||
makeRoomPath,
|
||||
} from "@app/util/routes"
|
||||
import {
|
||||
chats,
|
||||
chatsById,
|
||||
hasNip29,
|
||||
getUrlsForEvent,
|
||||
repositoryStore,
|
||||
userSettingsValues,
|
||||
userGroupList,
|
||||
getSpaceUrlsFromGroupList,
|
||||
@@ -40,151 +39,170 @@ export const setChecked = (key: string) => checked.update(state => ({...state, [
|
||||
|
||||
// Derived notifications state
|
||||
|
||||
export const notifications = derived(
|
||||
throttled(
|
||||
1000,
|
||||
derived(
|
||||
[pubkey, checked, chats, userGroupList, repositoryStore, getUrlsForEvent, relaysByUrl],
|
||||
identity,
|
||||
export const notifications = call(() => {
|
||||
const goalCommentFilters = [{kinds: [COMMENT], "#K": [String(ZAP_GOAL)]}]
|
||||
const threadCommentFilters = [{kinds: [COMMENT], "#K": [String(THREAD)]}]
|
||||
const calendarCommentFilters = [{kinds: [COMMENT], "#K": [String(EVENT_TIME)]}]
|
||||
const messageFilters = [{kinds: [MESSAGE, THREAD, ZAP_GOAL, EVENT_TIME]}]
|
||||
|
||||
return derived(
|
||||
throttled(
|
||||
1000,
|
||||
derived(
|
||||
[
|
||||
pubkey,
|
||||
checked,
|
||||
chatsById,
|
||||
userGroupList,
|
||||
relaysByUrl,
|
||||
deriveEventsByIdByUrl({tracker, repository, filters: goalCommentFilters}),
|
||||
deriveEventsByIdByUrl({tracker, repository, filters: threadCommentFilters}),
|
||||
deriveEventsByIdByUrl({tracker, repository, filters: calendarCommentFilters}),
|
||||
deriveEventsByIdByUrl({tracker, repository, filters: messageFilters}),
|
||||
],
|
||||
identity,
|
||||
),
|
||||
),
|
||||
),
|
||||
([$pubkey, $checked, $chats, $userGroupList, $repository, $getUrlsForEvent, $relaysByUrl]) => {
|
||||
const hasNotification = (path: string, latestEvent: TrustedEvent | undefined) => {
|
||||
if (!latestEvent || latestEvent.pubkey === $pubkey) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const [entryPath, ts] of Object.entries($checked)) {
|
||||
const isMatch =
|
||||
entryPath === "*" ||
|
||||
entryPath.startsWith(path) ||
|
||||
(entryPath === "/chat/*" && path.startsWith("/chat/"))
|
||||
|
||||
if (isMatch && ts > latestEvent.created_at) {
|
||||
([
|
||||
$pubkey,
|
||||
$checked,
|
||||
$chatsById,
|
||||
$userGroupList,
|
||||
$relaysByUrl,
|
||||
goalCommentsByUrl,
|
||||
threadCommentsByUrl,
|
||||
calendarCommentsByUrl,
|
||||
messagesByUrl,
|
||||
]) => {
|
||||
const hasNotification = (path: string, latestEvent: TrustedEvent | undefined) => {
|
||||
if (!latestEvent || latestEvent.pubkey === $pubkey) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
for (const [entryPath, ts] of Object.entries($checked)) {
|
||||
const isMatch =
|
||||
entryPath === "*" ||
|
||||
entryPath.startsWith(path) ||
|
||||
(entryPath === "/chat/*" && path.startsWith("/chat/"))
|
||||
|
||||
const paths = new Set<string>()
|
||||
|
||||
for (const {pubkeys, messages} of $chats) {
|
||||
const chatPath = makeChatPath(pubkeys)
|
||||
|
||||
if (hasNotification(chatPath, messages[0])) {
|
||||
paths.add("/chat")
|
||||
paths.add(chatPath)
|
||||
}
|
||||
}
|
||||
|
||||
const allGoalComments = $repository.query([{kinds: [COMMENT], "#K": [String(ZAP_GOAL)]}])
|
||||
|
||||
const allThreadComments = $repository.query([{kinds: [COMMENT], "#K": [String(THREAD)]}])
|
||||
|
||||
const allCalendarComments = $repository.query([{kinds: [COMMENT], "#K": [String(EVENT_TIME)]}])
|
||||
|
||||
const allMessages = $repository.query([{kinds: [MESSAGE, THREAD, ZAP_GOAL, EVENT_TIME]}])
|
||||
|
||||
for (const url of getSpaceUrlsFromGroupList($userGroupList)) {
|
||||
const spacePath = makeSpacePath(url)
|
||||
const spacePathMobile = spacePath + ":mobile"
|
||||
const goalPath = makeGoalPath(url)
|
||||
const threadPath = makeThreadPath(url)
|
||||
const calendarPath = makeCalendarPath(url)
|
||||
const messagesPath = makeSpaceChatPath(url)
|
||||
const goalComments = allGoalComments.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
const threadComments = allThreadComments.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
const calendarComments = allCalendarComments.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
const messages = allMessages.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
|
||||
const commentsByGoalId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
goalComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [goalId, [comment]] of commentsByGoalId.entries()) {
|
||||
const goalItemPath = makeGoalPath(url, goalId)
|
||||
|
||||
if (hasNotification(spacePathMobile, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
}
|
||||
|
||||
if (hasNotification(goalPath, comment)) {
|
||||
paths.add(goalPath)
|
||||
}
|
||||
|
||||
if (hasNotification(goalItemPath, comment)) {
|
||||
paths.add(goalItemPath)
|
||||
}
|
||||
}
|
||||
|
||||
const commentsByThreadId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
threadComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [threadId, [comment]] of commentsByThreadId.entries()) {
|
||||
const threadItemPath = makeThreadPath(url, threadId)
|
||||
|
||||
if (hasNotification(spacePathMobile, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
}
|
||||
|
||||
if (hasNotification(threadPath, comment)) {
|
||||
paths.add(threadPath)
|
||||
}
|
||||
|
||||
if (hasNotification(threadItemPath, comment)) {
|
||||
paths.add(threadItemPath)
|
||||
}
|
||||
}
|
||||
|
||||
const commentsByEventId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
calendarComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [eventId, [comment]] of commentsByEventId.entries()) {
|
||||
const calendarItemPath = makeCalendarPath(url, eventId)
|
||||
|
||||
if (hasNotification(spacePathMobile, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
}
|
||||
|
||||
if (hasNotification(calendarPath, comment)) {
|
||||
paths.add(calendarPath)
|
||||
}
|
||||
|
||||
if (hasNotification(calendarItemPath, comment)) {
|
||||
paths.add(calendarItemPath)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNip29($relaysByUrl.get(url))) {
|
||||
for (const h of getSpaceRoomsFromGroupList(url, $userGroupList)) {
|
||||
const roomPath = makeRoomPath(url, h)
|
||||
const latestEvent = messages.find(e => e.tags.some(spec(["h", h])))
|
||||
|
||||
if (hasNotification(roomPath, latestEvent)) {
|
||||
paths.add(spacePathMobile)
|
||||
paths.add(spacePath)
|
||||
paths.add(roomPath)
|
||||
if (isMatch && ts > latestEvent.created_at) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasNotification(messagesPath, messages[0])) {
|
||||
paths.add(spacePathMobile)
|
||||
paths.add(spacePath)
|
||||
paths.add(messagesPath)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const paths = new Set<string>()
|
||||
|
||||
for (const {pubkeys, messages} of $chatsById.values()) {
|
||||
const chatPath = makeChatPath(pubkeys)
|
||||
|
||||
if (hasNotification(chatPath, messages[0])) {
|
||||
paths.add("/chat")
|
||||
paths.add(chatPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
},
|
||||
)
|
||||
for (const url of getSpaceUrlsFromGroupList($userGroupList)) {
|
||||
const spacePath = makeSpacePath(url)
|
||||
const spacePathMobile = spacePath + ":mobile"
|
||||
const goalPath = makeGoalPath(url)
|
||||
const threadPath = makeThreadPath(url)
|
||||
const calendarPath = makeCalendarPath(url)
|
||||
const messagesPath = makeSpaceChatPath(url)
|
||||
const goalComments = goalCommentsByUrl.get(url)?.values() || []
|
||||
const threadComments = threadCommentsByUrl.get(url)?.values() || []
|
||||
const calendarComments = calendarCommentsByUrl.get(url)?.values() || []
|
||||
const messages = messagesByUrl.get(url)?.values() || []
|
||||
|
||||
const commentsByGoalId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
goalComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [goalId, [comment]] of commentsByGoalId.entries()) {
|
||||
const goalItemPath = makeGoalPath(url, goalId)
|
||||
|
||||
if (hasNotification(spacePathMobile, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
}
|
||||
|
||||
if (hasNotification(goalPath, comment)) {
|
||||
paths.add(goalPath)
|
||||
}
|
||||
|
||||
if (hasNotification(goalItemPath, comment)) {
|
||||
paths.add(goalItemPath)
|
||||
}
|
||||
}
|
||||
|
||||
const commentsByThreadId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
threadComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [threadId, [comment]] of commentsByThreadId.entries()) {
|
||||
const threadItemPath = makeThreadPath(url, threadId)
|
||||
|
||||
if (hasNotification(spacePathMobile, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
}
|
||||
|
||||
if (hasNotification(threadPath, comment)) {
|
||||
paths.add(threadPath)
|
||||
}
|
||||
|
||||
if (hasNotification(threadItemPath, comment)) {
|
||||
paths.add(threadItemPath)
|
||||
}
|
||||
}
|
||||
|
||||
const commentsByEventId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
calendarComments.filter(spec({kind: COMMENT})),
|
||||
)
|
||||
|
||||
for (const [eventId, [comment]] of commentsByEventId.entries()) {
|
||||
const calendarItemPath = makeCalendarPath(url, eventId)
|
||||
|
||||
if (hasNotification(spacePathMobile, comment)) {
|
||||
paths.add(spacePathMobile)
|
||||
}
|
||||
|
||||
if (hasNotification(calendarPath, comment)) {
|
||||
paths.add(calendarPath)
|
||||
}
|
||||
|
||||
if (hasNotification(calendarItemPath, comment)) {
|
||||
paths.add(calendarItemPath)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNip29($relaysByUrl.get(url))) {
|
||||
for (const h of getSpaceRoomsFromGroupList(url, $userGroupList)) {
|
||||
const roomPath = makeRoomPath(url, h)
|
||||
const latestEvent = find(e => e.tags.some(spec(["h", h])), messages)
|
||||
|
||||
if (hasNotification(roomPath, latestEvent)) {
|
||||
paths.add(spacePathMobile)
|
||||
paths.add(spacePath)
|
||||
paths.add(roomPath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasNotification(messagesPath, first(messages))) {
|
||||
paths.add(spacePathMobile)
|
||||
paths.add(spacePath)
|
||||
paths.add(messagesPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
export const badgeCount = derived(notifications, notifications => {
|
||||
return notifications.size
|
||||
|
||||
+15
-31
@@ -1,5 +1,5 @@
|
||||
import {on, throttle, fromPairs, batch} from "@welshman/lib"
|
||||
import {throttled, freshness} from "@welshman/store"
|
||||
import {on, throttle, indexBy, fromPairs, batch} from "@welshman/lib"
|
||||
import {throttled} from "@welshman/store"
|
||||
import {
|
||||
ALERT_ANDROID,
|
||||
ALERT_EMAIL,
|
||||
@@ -38,13 +38,14 @@ import type {Zapper, TrustedEvent, RelayProfile} from "@welshman/util"
|
||||
import type {RepositoryUpdate, WrapItem} from "@welshman/net"
|
||||
import type {Handle, RelayStats} from "@welshman/app"
|
||||
import {
|
||||
plaintext,
|
||||
tracker,
|
||||
relays,
|
||||
relayStats,
|
||||
plaintext,
|
||||
repository,
|
||||
handles,
|
||||
zappers,
|
||||
relaysByUrl,
|
||||
relayStatsByUrl,
|
||||
onRelayStats,
|
||||
handlesByNip05,
|
||||
zappersByLnurl,
|
||||
onZapper,
|
||||
onHandle,
|
||||
wrapManager,
|
||||
@@ -185,9 +186,9 @@ const relaysAdapter = {
|
||||
name: "relays",
|
||||
keyPath: "url",
|
||||
init: async (table: IDBTable<RelayProfile>) => {
|
||||
relays.set(await table.getAll())
|
||||
relaysByUrl.set(indexBy(r => r.url, await table.getAll()))
|
||||
|
||||
return onRelay(batch(3000, table.bulkPut))
|
||||
return onRelay(batch(1000, table.bulkPut))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -195,9 +196,9 @@ const relayStatsAdapter = {
|
||||
name: "relayStats",
|
||||
keyPath: "url",
|
||||
init: async (table: IDBTable<RelayStats>) => {
|
||||
relayStats.set(await table.getAll())
|
||||
relayStatsByUrl.set(indexBy(r => r.url, await table.getAll()))
|
||||
|
||||
return throttled(3000, relayStats).subscribe(table.bulkPut)
|
||||
return onRelayStats(batch(1000, table.bulkPut))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -205,9 +206,9 @@ const handlesAdapter = {
|
||||
name: "handles",
|
||||
keyPath: "nip05",
|
||||
init: async (table: IDBTable<Handle>) => {
|
||||
handles.set(await table.getAll())
|
||||
handlesByNip05.set(indexBy(r => r.nip05, await table.getAll()))
|
||||
|
||||
return onHandle(batch(3000, table.bulkPut))
|
||||
return onHandle(batch(1000, table.bulkPut))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -215,28 +216,12 @@ const zappersAdapter = {
|
||||
name: "zappers",
|
||||
keyPath: "lnurl",
|
||||
init: async (table: IDBTable<Zapper>) => {
|
||||
zappers.set(await table.getAll())
|
||||
zappersByLnurl.set(indexBy(z => z.lnurl, await table.getAll()))
|
||||
|
||||
return onZapper(batch(3000, table.bulkPut))
|
||||
},
|
||||
}
|
||||
|
||||
type FreshnessItem = {key: string; value: number}
|
||||
|
||||
const freshnessAdapter = {
|
||||
name: "freshness",
|
||||
keyPath: "key",
|
||||
init: async (table: IDBTable<FreshnessItem>) => {
|
||||
const initialRecords = await table.getAll()
|
||||
|
||||
freshness.set(fromPairs(initialRecords.map(({key, value}) => [key, value])))
|
||||
|
||||
return throttled(3000, freshness).subscribe($freshness => {
|
||||
table.bulkPut(Object.entries($freshness).map(([key, value]) => ({key, value})))
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
type PlaintextItem = {key: string; value: string}
|
||||
|
||||
const plaintextAdapter = {
|
||||
@@ -280,7 +265,6 @@ export const adapters = [
|
||||
relayStatsAdapter,
|
||||
handlesAdapter,
|
||||
zappersAdapter,
|
||||
freshnessAdapter,
|
||||
plaintextAdapter,
|
||||
wrapManagerAdapter,
|
||||
]
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
const {relay, id} = $page.params as MakeNonOptional<typeof $page.params>
|
||||
const url = decodeRelay(relay)
|
||||
const event = deriveEvent(id)
|
||||
const event = deriveEvent(id, [url])
|
||||
const filters = [{kinds: [COMMENT], "#E": [id]}]
|
||||
const replies = deriveEventsDesc(deriveEventsById({filters, repository}))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {sortBy, sleep} from "@welshman/lib"
|
||||
import {sleep} from "@welshman/lib"
|
||||
import type {MakeNonOptional} from "@welshman/lib"
|
||||
import {COMMENT, getTagValue} from "@welshman/util"
|
||||
import {repository} from "@welshman/app"
|
||||
@@ -27,10 +27,10 @@
|
||||
|
||||
const {relay, id} = $page.params as MakeNonOptional<typeof $page.params>
|
||||
const url = decodeRelay(relay)
|
||||
const event = deriveEvent(id)
|
||||
const event = deriveEvent(id, [url])
|
||||
const filters = [{kinds: [COMMENT], "#E": [id]}]
|
||||
const replies = deriveEventsDesc(deriveEventsById({repository, filters}))
|
||||
const summary = getTagValue("summary", $event.tags)
|
||||
const summary = getTagValue("summary", $event?.tags || [])
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<h1 class="text-xl">{$event.content}</h1>
|
||||
<h1 class="text-xl">{$event?.content}</h1>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {sortBy, sleep} from "@welshman/lib"
|
||||
import {sleep} from "@welshman/lib"
|
||||
import type {MakeNonOptional} from "@welshman/lib"
|
||||
import {COMMENT, getTagValue} from "@welshman/util"
|
||||
import {repository} from "@welshman/app"
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
const {relay, id} = $page.params as MakeNonOptional<typeof $page.params>
|
||||
const url = decodeRelay(relay)
|
||||
const event = deriveEvent(id)
|
||||
const event = deriveEvent(id, [url])
|
||||
const filters = [{kinds: [COMMENT], "#E": [id]}]
|
||||
const replies = deriveEventsDesc(deriveEventsById({filters, repository}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user