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