forked from coracle/flotilla
Add notification badges
This commit is contained in:
+25
-15
@@ -55,8 +55,11 @@
|
||||
MESSAGE,
|
||||
COMMENT,
|
||||
THREAD,
|
||||
GENERAL,
|
||||
} from "@app/state"
|
||||
import {loadUserData, subscribePersistent} from "@app/commands"
|
||||
import {checked} from "@app/notifications"
|
||||
import * as notifications from "@app/notifications"
|
||||
import * as state from "@app/state"
|
||||
|
||||
// Migration: old nostrtalk instance used different sessions
|
||||
@@ -67,7 +70,7 @@
|
||||
let ready: Promise<unknown> = Promise.resolve()
|
||||
|
||||
onMount(async () => {
|
||||
Object.assign(window, {get, ...lib, ...util, ...net, ...app, ...state})
|
||||
Object.assign(window, {get, ...lib, ...util, ...net, ...app, ...state, ...notifications})
|
||||
|
||||
const getScoreEvent = () => {
|
||||
const ALWAYS_KEEP = Infinity
|
||||
@@ -133,6 +136,7 @@
|
||||
events: storageAdapters.fromRepository(repository, {throttle: 300, migrate: migrateEvents}),
|
||||
relays: {keyPath: "url", store: throttled(1000, relays)},
|
||||
handles: {keyPath: "nip05", store: throttled(1000, handles)},
|
||||
checked: storageAdapters.fromObjectStore(checked, {throttle: 1000}),
|
||||
freshness: storageAdapters.fromObjectStore(freshness, {
|
||||
throttle: 1000,
|
||||
migrate: migrateFreshness,
|
||||
@@ -167,29 +171,32 @@
|
||||
await loadUserData($pubkey)
|
||||
}
|
||||
|
||||
// Listen for space data, populate space-based notifications
|
||||
let unsubRooms: any
|
||||
|
||||
userMembership.subscribe($membership => {
|
||||
unsubRooms?.()
|
||||
|
||||
const since = ago(30)
|
||||
const rooms = uniq(getMembershipRooms($membership).map(m => m.room))
|
||||
const rooms = uniq(getMembershipRooms($membership).map(m => m.room)).concat(GENERAL)
|
||||
const relays = uniq(getMembershipUrls($membership))
|
||||
|
||||
if (relays.length > 0) {
|
||||
subscribePersistent({
|
||||
relays,
|
||||
filters: [
|
||||
{kinds: [THREAD], since},
|
||||
{kinds: [MESSAGE], "#~": rooms, since},
|
||||
{kinds: [COMMENT], "#K": [THREAD, MESSAGE].map(String), since},
|
||||
{kinds: [DELETE], "#k": [THREAD, COMMENT, MESSAGE].map(String), since},
|
||||
{kinds: [MEMBERSHIPS], "#r": relays, since},
|
||||
],
|
||||
})
|
||||
}
|
||||
subscribePersistent({
|
||||
relays,
|
||||
filters: [
|
||||
{kinds: [THREAD], since},
|
||||
{kinds: [THREAD], limit: 1},
|
||||
{kinds: [MESSAGE], "#~": rooms, since},
|
||||
{kinds: [MESSAGE], "#~": rooms, limit: 1},
|
||||
{kinds: [COMMENT], "#K": [THREAD, MESSAGE].map(String), since},
|
||||
{kinds: [COMMENT], "#K": [THREAD, MESSAGE].map(String), limit: 1},
|
||||
{kinds: [DELETE], "#k": [THREAD, COMMENT, MESSAGE].map(String), since},
|
||||
{kinds: [MEMBERSHIPS], "#r": relays, since: ago(WEEK, 2)},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
// Listen for chats, populate chat-based notifications
|
||||
let unsubChats: any
|
||||
|
||||
derived([pubkey, userInboxRelaySelections], identity).subscribe(
|
||||
@@ -198,7 +205,10 @@
|
||||
|
||||
if ($pubkey) {
|
||||
unsubChats = subscribePersistent({
|
||||
filters: [{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)}],
|
||||
filters: [
|
||||
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},
|
||||
{kinds: [WRAP], "#p": [$pubkey], limit: 100},
|
||||
],
|
||||
relays: getRelayUrls($userInboxRelaySelections),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onDestroy} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import Chat from "@app/components/Chat.svelte"
|
||||
import {setChecked} from "@app/state"
|
||||
import {setChecked} from "@app/notifications"
|
||||
|
||||
onMount(() => {
|
||||
onDestroy(() => {
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onMount, onDestroy} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import Page from "@lib/components/Page.svelte"
|
||||
import Delay from "@lib/components/Delay.svelte"
|
||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||
import {pushToast} from "@app/toast"
|
||||
import {setChecked} from "@app/notifications"
|
||||
import {checkRelayConnection, checkRelayAuth} from "@app/commands"
|
||||
import {decodeRelay} from "@app/state"
|
||||
|
||||
@@ -26,6 +27,10 @@
|
||||
onMount(() => {
|
||||
checkConnection()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#key url}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -11,7 +10,7 @@
|
||||
import ProfileFeed from "@app/components/ProfileFeed.svelte"
|
||||
import RelayName from "@app/components/RelayName.svelte"
|
||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||
import {decodeRelay, setChecked} from "@app/state"
|
||||
import {decodeRelay} from "@app/state"
|
||||
import {pushDrawer} from "@app/modal"
|
||||
import {makeChatPath} from "@app/routes"
|
||||
|
||||
@@ -21,10 +20,6 @@
|
||||
const openMenu = () => pushDrawer(MenuSpace, {url})
|
||||
|
||||
$: pubkey = $relay?.profile?.pubkey
|
||||
|
||||
onMount(() => {
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex flex-col">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onDestroy} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {sortBy, append} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
@@ -34,8 +34,8 @@
|
||||
MESSAGE,
|
||||
COMMENT,
|
||||
getMembershipRoomsByUrl,
|
||||
setChecked,
|
||||
} from "@app/state"
|
||||
import {setChecked} from "@app/notifications"
|
||||
import {addRoomMembership, removeRoomMembership} from "@app/commands"
|
||||
import {pushDrawer} from "@app/modal"
|
||||
import {popKey} from "@app/implicit"
|
||||
@@ -91,7 +91,7 @@
|
||||
elements.reverse()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
onDestroy(() => {
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onMount, onDestroy} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {sortBy, last, ago} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
@@ -14,7 +14,8 @@
|
||||
import EventItem from "@app/components/EventItem.svelte"
|
||||
import EventCreate from "@app/components/EventCreate.svelte"
|
||||
import {pushModal, pushDrawer} from "@app/modal"
|
||||
import {deriveEventsForUrl, pullConservatively, decodeRelay, setChecked} from "@app/state"
|
||||
import {deriveEventsForUrl, pullConservatively, decodeRelay} from "@app/state"
|
||||
import {setChecked} from "@app/notifications"
|
||||
|
||||
const url = decodeRelay($page.params.relay)
|
||||
const kinds = [EVENT_DATE, EVENT_TIME]
|
||||
@@ -54,8 +55,6 @@
|
||||
.slice(0, limit)
|
||||
|
||||
onMount(() => {
|
||||
setChecked($page.url.pathname)
|
||||
|
||||
const sub = subscribe({filters: [{kinds, since: ago(30)}]})
|
||||
|
||||
pullConservatively({filters: [{kinds}], relays: [url]})
|
||||
@@ -63,6 +62,10 @@
|
||||
return () => sub.close()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
}, 5000)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onMount, onDestroy} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {sortBy, uniqBy} from "@welshman/lib"
|
||||
import {sortBy, sleep, uniqBy} from "@welshman/lib"
|
||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||
import type {Filter, TrustedEvent} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
||||
import {nthEq} from "@welshman/lib"
|
||||
import {createFeedController, userMutes} from "@welshman/app"
|
||||
import {createScroller, type Scroller} from "@lib/html"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
@@ -15,72 +16,67 @@
|
||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||
import ThreadItem from "@app/components/ThreadItem.svelte"
|
||||
import ThreadCreate from "@app/components/ThreadCreate.svelte"
|
||||
import {THREAD, COMMENT, decodeRelay, setChecked} from "@app/state"
|
||||
import {THREAD, decodeRelay, getEventsForUrl} from "@app/state"
|
||||
import {pushModal, pushDrawer} from "@app/modal"
|
||||
import {THREAD_FILTERS, setChecked} from "@app/notifications"
|
||||
|
||||
const url = decodeRelay($page.params.relay)
|
||||
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
||||
const filters: Filter[] = [{kinds: [THREAD]}, {kinds: [COMMENT], "#K": [String(THREAD)]}]
|
||||
|
||||
const openMenu = () => pushDrawer(MenuSpace, {url})
|
||||
|
||||
const createThread = () => pushModal(ThreadCreate, {url})
|
||||
|
||||
const ctrl = createFeedController({
|
||||
useWindowing: true,
|
||||
feed: makeIntersectionFeed(makeRelayFeed(url), feedsFromFilters(THREAD_FILTERS)),
|
||||
onEvent: (event: TrustedEvent) => {
|
||||
if (
|
||||
event.kind === THREAD &&
|
||||
!event.tags.some(nthEq(0, "e")) &&
|
||||
!mutedPubkeys.includes(event.pubkey)
|
||||
) {
|
||||
buffer.push(event)
|
||||
}
|
||||
},
|
||||
onExhausted: () => {
|
||||
loading = false
|
||||
},
|
||||
})
|
||||
|
||||
let loading = true
|
||||
let unmounted = false
|
||||
let element: Element
|
||||
let scroller: Scroller
|
||||
let buffer: TrustedEvent[] = []
|
||||
let events: TrustedEvent[] = []
|
||||
|
||||
onMount(() => {
|
||||
setChecked($page.url.pathname)
|
||||
|
||||
let unmounted = false
|
||||
|
||||
const ctrl = createFeedController({
|
||||
useWindowing: true,
|
||||
feed: makeIntersectionFeed(makeRelayFeed(url), feedsFromFilters(filters)),
|
||||
onEvent: (event: TrustedEvent) => {
|
||||
if (
|
||||
event.kind === THREAD &&
|
||||
!event.tags.some(nthEq(0, "e")) &&
|
||||
!mutedPubkeys.includes(event.pubkey)
|
||||
) {
|
||||
buffer.push(event)
|
||||
}
|
||||
},
|
||||
onExhausted: () => {
|
||||
loading = false
|
||||
},
|
||||
})
|
||||
let events: TrustedEvent[] = sortBy(e => -e.created_at, getEventsForUrl(url, [{kinds: [THREAD]}]))
|
||||
|
||||
onMount(async () => {
|
||||
// Element is frequently not defined. I don't know why
|
||||
setTimeout(() => {
|
||||
if (!unmounted) {
|
||||
scroller = createScroller({
|
||||
element,
|
||||
delay: 300,
|
||||
threshold: 3000,
|
||||
onScroll: () => {
|
||||
buffer = uniqBy(
|
||||
e => e.id,
|
||||
sortBy(e => -e.created_at, buffer),
|
||||
)
|
||||
events = [...events, ...buffer.splice(0, 5)]
|
||||
await sleep(1000)
|
||||
|
||||
if (buffer.length < 50) {
|
||||
ctrl.load(50)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
if (!unmounted) {
|
||||
scroller = createScroller({
|
||||
element,
|
||||
delay: 300,
|
||||
threshold: 3000,
|
||||
onScroll: () => {
|
||||
buffer = sortBy(e => -e.created_at, buffer)
|
||||
events = uniqBy(e => e.id, [...events, ...buffer.splice(0, 5)])
|
||||
|
||||
return () => {
|
||||
unmounted = true
|
||||
scroller?.stop()
|
||||
if (buffer.length < 50) {
|
||||
ctrl.load(50)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
unmounted = true
|
||||
scroller?.stop()
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex h-screen flex-col" bind:this={element}>
|
||||
@@ -101,7 +97,9 @@
|
||||
</PageBar>
|
||||
<div class="flex flex-grow flex-col gap-2 overflow-auto p-2">
|
||||
{#each events as event (event.id)}
|
||||
<ThreadItem {url} {event} />
|
||||
<div in:fly>
|
||||
<ThreadItem {url} {event} />
|
||||
</div>
|
||||
{/each}
|
||||
<p class="flex h-10 items-center justify-center py-20">
|
||||
<Spinner {loading}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {onMount, onDestroy} from "svelte"
|
||||
import {sortBy, nthEq, sleep} from "@welshman/lib"
|
||||
import {page} from "$app/stores"
|
||||
import {repository, subscribe} from "@welshman/app"
|
||||
@@ -14,6 +14,7 @@
|
||||
import ThreadActions from "@app/components/ThreadActions.svelte"
|
||||
import ThreadReply from "@app/components/ThreadReply.svelte"
|
||||
import {COMMENT, deriveEvent, decodeRelay} from "@app/state"
|
||||
import {setChecked} from "@app/notifications"
|
||||
import {pushDrawer} from "@app/modal"
|
||||
|
||||
const {relay, id} = $page.params
|
||||
@@ -43,6 +44,10 @@
|
||||
|
||||
return () => sub.close()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
setChecked($page.url.pathname)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex flex-col-reverse gap-3 px-2">
|
||||
|
||||
Reference in New Issue
Block a user