- {#if $notification}
+ {#if $notifications.has(path)}
{/if}
Active {formatTimestampRelative(lastActive)}
diff --git a/src/app/notifications.ts b/src/app/notifications.ts
index d070e048..25bd9f1a 100644
--- a/src/app/notifications.ts
+++ b/src/app/notifications.ts
@@ -1,16 +1,15 @@
import {writable, derived} from "svelte/store"
import {page} from "$app/stores"
-import {deriveEvents} from "@welshman/store"
-import {repository, pubkey} from "@welshman/app"
-import {prop, max, sortBy, assoc, lt, now} from "@welshman/lib"
-import type {Filter, TrustedEvent} from "@welshman/util"
-import {DIRECT_MESSAGE, MESSAGE, THREAD, COMMENT} from "@welshman/util"
-import {makeSpacePath, makeThreadPath, makeRoomPath} from "@app/routes"
+import {pubkey} from "@welshman/app"
+import {prop, max, sortBy, now} from "@welshman/lib"
+import type {TrustedEvent} from "@welshman/util"
+import {MESSAGE} from "@welshman/util"
+import {makeSpacePath, makeChatPath, makeThreadPath, makeRoomPath} from "@app/routes"
import {
- LEGACY_THREAD,
+ THREAD_FILTER,
+ COMMENT_FILTER,
+ chats,
getEventsForUrl,
- deriveEventsForUrl,
- getMembershipUrls,
userRoomsByUrl,
repositoryStore,
} from "@app/state"
@@ -19,85 +18,74 @@ import {
export const checked = writable
>({})
-checked.subscribe(v => console.log("====== checked", v))
-
export const deriveChecked = (key: string) => derived(checked, prop(key))
-export const setChecked = (key: string, ts = now()) =>
- Boolean(console.trace("====== setChecked", key)) ||
- checked.update(state => ({...state, [key]: ts}))
+export const setChecked = (key: string) =>
+ checked.update(state => ({...state, [key]: now()}))
-// Filters for various routes
+// Derived notifications state
-export const CHAT_FILTERS: Filter[] = [{kinds: [DIRECT_MESSAGE]}]
+export const notifications = derived(
+ [pubkey, checked, chats, userRoomsByUrl, repositoryStore],
+ ([$pubkey, $checked, $chats, $userRoomsByUrl, $repository]) => {
+ const hasNotification = (path: string, events: TrustedEvent[]) => {
+ const [latestEvent] = sortBy($e => -$e.created_at, events)
-export const SPACE_FILTERS: Filter[] = [{kinds: [THREAD, MESSAGE, COMMENT]}]
+ if (!latestEvent || latestEvent.pubkey === $pubkey) {
+ return false
+ }
-export const ROOM_FILTERS: Filter[] = [{kinds: [MESSAGE]}]
+ let checkPath = ""
+ let lastChecked = $checked["*"]
-export const THREAD_FILTERS: Filter[] = [
- {kinds: [THREAD, LEGACY_THREAD]},
- {kinds: [COMMENT], "#K": [String(THREAD), String(LEGACY_THREAD)]},
-]
+ for (const segment of path.slice(1).split("/")) {
+ checkPath += "/" + segment
+ lastChecked = max([lastChecked, $checked[checkPath]])
+ }
-export const getNotificationFilters = (since: number): Filter[] =>
- [...CHAT_FILTERS, ...SPACE_FILTERS, ...THREAD_FILTERS].map(assoc("since", since))
-
-export const getRoomFilters = (room: string): Filter[] => ROOM_FILTERS.map(assoc("#h", [room]))
-
-// Notification derivation
-
-export const getNotification = (
- pubkey: string | null,
- lastChecked: number,
- events: TrustedEvent[],
-) => {
- const [latestEvent] = sortBy($e => -$e.created_at, events)
-
- return latestEvent?.pubkey !== pubkey && lt(lastChecked, latestEvent?.created_at)
-}
-
-export const deriveNotification = (path: string, filters: Filter[], url?: string) => {
- const events = url ? deriveEventsForUrl(url, filters) : deriveEvents(repository, {filters})
-
- return derived(
- [pubkey, deriveChecked("*"), deriveChecked(path), events],
- ([$pubkey, $allChecked, $checked, $events]) => {
- return getNotification($pubkey, max([$allChecked, $checked]), $events)
- },
- )
-}
-
-export const spacesNotifications = derived(
- [pubkey, checked, userRoomsByUrl, repositoryStore],
- ([$pubkey, $checked, $userRoomsByUrl, $repository]) => {
- const hasNotification = (url: string, path: string, filters: Filter[]) => {
- const lastChecked = max([$checked["*"], $checked[path]])
- const events = getEventsForUrl($repository, url, filters)
-
- return getNotification($pubkey, lastChecked, events)
+ return lastChecked < latestEvent.created_at
}
- return Array.from($userRoomsByUrl.entries())
- .filter(([url, rooms]) => {
- if (hasNotification(url, makeThreadPath(url), THREAD_FILTERS)) {
- return true
- }
+ const paths = new Set()
- for (const room of rooms) {
- if (hasNotification(url, makeRoomPath(url, room), [{kinds: [MESSAGE], "#h": [room]}])) {
- return true
- }
- }
+ for (const {pubkeys, messages} of $chats) {
+ const chatPath = makeChatPath(pubkeys)
- return false
- })
- .map(([url]) => makeSpacePath(url))
+ if (hasNotification(chatPath, messages)) {
+ paths.add("/chat")
+ paths.add(chatPath)
+ }
+ }
+
+ for (const [url, rooms] of $userRoomsByUrl.entries()) {
+ const spacePath = makeSpacePath(url)
+ const threadPath = makeThreadPath(url)
+ const threadFilters = [THREAD_FILTER, COMMENT_FILTER]
+ const threadEvents = getEventsForUrl($repository, url, threadFilters)
+
+ if (hasNotification(threadPath, threadEvents)) {
+ paths.add(spacePath)
+ paths.add(threadPath)
+ }
+
+ for (const room of rooms) {
+ const roomPath = makeRoomPath(url, room)
+ const roomFilters = [{kinds: [MESSAGE], "#h": [room]}]
+ const roomEvents = getEventsForUrl($repository, url, roomFilters)
+
+ if (hasNotification(roomPath, roomEvents)) {
+ paths.add(spacePath)
+ paths.add(roomPath)
+ }
+ }
+ }
+
+ return paths
},
)
-export const inactiveSpacesNotifications = derived(
- [page, spacesNotifications],
- ([$page, $spacesNotifications]) =>
- $spacesNotifications.filter(path => !$page.url.pathname.startsWith(path)),
+export const inactiveNotifications = derived(
+ [page, notifications],
+ ([$page, $notifications]) =>
+ new Set(Array.from($notifications).filter(path => !$page.url.pathname.startsWith(path))),
)
diff --git a/src/app/state.ts b/src/app/state.ts
index 8535d4e6..2bfe81ec 100644
--- a/src/app/state.ts
+++ b/src/app/state.ts
@@ -40,7 +40,14 @@ import {
asDecryptedEvent,
normalizeRelayUrl,
} from "@welshman/util"
-import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
+import type {
+ TrustedEvent,
+ Repository,
+ SignedEvent,
+ PublishedList,
+ List,
+ Filter,
+} from "@welshman/util"
import {Nip59} from "@welshman/signer"
import {
pubkey,
@@ -63,7 +70,7 @@ import {
thunks,
walkThunks,
} from "@welshman/app"
-import type {Thunk} from "@welshman/app"
+import type {Thunk, Relay} from "@welshman/app"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEvents, deriveEventsMapped, withGetter, synced} from "@welshman/store"
@@ -107,6 +114,13 @@ export const IMGPROXY_URL = "https://imgproxy.coracle.social"
export const REACTION_KINDS = [REACTION, ZAP_RESPONSE]
+export const THREAD_FILTER: Filter = {kinds: [THREAD, LEGACY_THREAD]}
+
+export const COMMENT_FILTER: Filter = {
+ kinds: [COMMENT],
+ "#K": [String(THREAD), String(LEGACY_THREAD)],
+}
+
export const NIP46_PERMS =
"nip04_encrypt,nip04_decrypt,nip44_encrypt,nip44_decrypt," +
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, GROUPS, WRAP, REACTION]
diff --git a/src/routes/spaces/[relay]/+layout.svelte b/src/routes/spaces/[relay]/+layout.svelte
index 0785eeac..4abf2652 100644
--- a/src/routes/spaces/[relay]/+layout.svelte
+++ b/src/routes/spaces/[relay]/+layout.svelte
@@ -13,7 +13,7 @@
import {setChecked} from "@app/notifications"
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
import {decodeRelay} from "@app/state"
- import {spacesNotifications} from "@app/notifications"
+ import {notifications} from "@app/notifications"
const url = decodeRelay($page.params.relay)
@@ -35,7 +35,7 @@
// We have to watch this one, since on mobile the badge will be visible when active
$: {
- if ($spacesNotifications.includes($page.url.pathname)) {
+ if ($notifications.has($page.url.pathname)) {
setChecked($page.url.pathname)
}
}
diff --git a/src/routes/spaces/[relay]/threads/+page.svelte b/src/routes/spaces/[relay]/threads/+page.svelte
index 497cc3f5..cbed2fa7 100644
--- a/src/routes/spaces/[relay]/threads/+page.svelte
+++ b/src/routes/spaces/[relay]/threads/+page.svelte
@@ -3,7 +3,7 @@
import {derived} from "svelte/store"
import {page} from "$app/stores"
import {sortBy, min, nthEq, sleep} from "@welshman/lib"
- import {getListTags, getPubkeyTagValues, THREAD, COMMENT} from "@welshman/util"
+ import {getListTags, getPubkeyTagValues} from "@welshman/util"
import {throttled} from "@welshman/store"
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {createFeedController, userMutes} from "@welshman/app"
@@ -16,15 +16,14 @@
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
import ThreadItem from "@app/components/ThreadItem.svelte"
import ThreadCreate from "@app/components/ThreadCreate.svelte"
- import {LEGACY_THREAD, decodeRelay, deriveEventsForUrl} from "@app/state"
- import {THREAD_FILTERS, setChecked} from "@app/notifications"
+ import {THREAD_FILTER, COMMENT_FILTER, decodeRelay, deriveEventsForUrl} from "@app/state"
+ import {setChecked} from "@app/notifications"
import {pushModal} from "@app/modal"
const url = decodeRelay($page.params.relay)
- const threads = deriveEventsForUrl(url, [{kinds: [THREAD, LEGACY_THREAD]}])
- const comments = deriveEventsForUrl(url, [
- {kinds: [COMMENT], "#K": [String(THREAD), String(LEGACY_THREAD)]},
- ])
+ const feeds = feedsFromFilters([THREAD_FILTER, COMMENT_FILTER])
+ const threads = deriveEventsForUrl(url, [THREAD_FILTER])
+ const comments = deriveEventsForUrl(url, [COMMENT_FILTER])
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
const events = throttled(
@@ -51,7 +50,7 @@
const ctrl = createFeedController({
useWindowing: true,
- feed: makeIntersectionFeed(makeRelayFeed(url), feedsFromFilters(THREAD_FILTERS)),
+ feed: makeIntersectionFeed(makeRelayFeed(url), feeds),
onExhausted: () => {
loading = false
},