mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Clean up feeds and listeners
This commit is contained in:
@@ -268,7 +268,7 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => {
|
|||||||
export const checkRelayAccess = async (url: string, claim = "") => {
|
export const checkRelayAccess = async (url: string, claim = "") => {
|
||||||
const connection = ctx.net.pool.get(url)
|
const connection = ctx.net.pool.get(url)
|
||||||
|
|
||||||
await connection.auth.attempt()
|
await connection.auth.attempt(5000)
|
||||||
|
|
||||||
const thunk = publishThunk({
|
const thunk = publishThunk({
|
||||||
event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||||
@@ -309,9 +309,10 @@ export const checkRelayAuth = async (url: string) => {
|
|||||||
const connection = ctx.net.pool.get(url)
|
const connection = ctx.net.pool.get(url)
|
||||||
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
||||||
|
|
||||||
await connection.auth.attempt()
|
await connection.auth.attempt(5000)
|
||||||
|
|
||||||
if (!okStatuses.includes(connection.auth.status)) {
|
if (!okStatuses.includes(connection.auth.status)) {
|
||||||
|
console.log(connection.auth.status, connection)
|
||||||
return `Failed to authenticate: "${connection.auth.message}"`
|
return `Failed to authenticate: "${connection.auth.message}"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {REACTION} from "@welshman/util"
|
import {REACTION} from "@welshman/util"
|
||||||
import {pubkey, load, formatTimestamp} from "@welshman/app"
|
import {pubkey, load} from "@welshman/app"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
@@ -40,8 +40,5 @@
|
|||||||
<Icon icon="smile-circle" size={4} />
|
<Icon icon="smile-circle" size={4} />
|
||||||
</EmojiButton>
|
</EmojiButton>
|
||||||
</ReactionSummary>
|
</ReactionSummary>
|
||||||
<p class="whitespace-nowrap text-sm opacity-75">
|
|
||||||
{formatTimestamp(event.created_at)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</NoteCard>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {sortBy, flatten} from "@welshman/lib"
|
import {sortBy, uniqBy} from "@welshman/lib"
|
||||||
import {deriveEvents} from "@welshman/store"
|
|
||||||
import {feedFromFilter} from "@welshman/feeds"
|
import {feedFromFilter} from "@welshman/feeds"
|
||||||
import {NOTE, getAncestorTags} from "@welshman/util"
|
import {NOTE, getAncestorTags} from "@welshman/util"
|
||||||
import {repository, createFeedController} from "@welshman/app"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {createFeedController} from "@welshman/app"
|
||||||
import {createScroller} from "@lib/html"
|
import {createScroller} from "@lib/html"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import NoteItem from "@app/components/NoteItem.svelte"
|
import NoteItem from "@app/components/NoteItem.svelte"
|
||||||
@@ -12,18 +12,36 @@
|
|||||||
export let url
|
export let url
|
||||||
export let pubkey
|
export let pubkey
|
||||||
|
|
||||||
const filter = {kinds: [NOTE], authors: [pubkey]}
|
const ctrl = createFeedController({
|
||||||
const events = deriveEvents(repository, {filters: [filter]})
|
useWindowing: true,
|
||||||
|
feed: feedFromFilter({kinds: [NOTE], authors: [pubkey]}),
|
||||||
|
onEvent: (event: TrustedEvent) => {
|
||||||
|
if (getAncestorTags(event.tags).replies.length === 0) {
|
||||||
|
buffer.push(event)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
let element: Element
|
let element: Element
|
||||||
|
let buffer: TrustedEvent[] = []
|
||||||
|
let events: TrustedEvent[] = []
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const ctrl = createFeedController({feed: feedFromFilter(filter)})
|
|
||||||
const scroller = createScroller({
|
const scroller = createScroller({
|
||||||
element,
|
element,
|
||||||
delay: 300,
|
delay: 300,
|
||||||
threshold: 3000,
|
threshold: 3000,
|
||||||
onScroll: () => ctrl.load(5),
|
onScroll: () => {
|
||||||
|
buffer = uniqBy(
|
||||||
|
e => e.id,
|
||||||
|
sortBy(e => -e.created_at, buffer),
|
||||||
|
)
|
||||||
|
events = [...events, ...buffer.splice(0, 5)]
|
||||||
|
|
||||||
|
if (buffer.length < 50) {
|
||||||
|
ctrl.load(50)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => scroller.stop()
|
return () => scroller.stop()
|
||||||
@@ -32,10 +50,8 @@
|
|||||||
|
|
||||||
<div class="col-4" bind:this={element}>
|
<div class="col-4" bind:this={element}>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each sortBy(e => -e.created_at, $events) as event (event.id)}
|
{#each events as event (event.id)}
|
||||||
{#if flatten(Object.values(getAncestorTags(event.tags))).length === 0}
|
|
||||||
<NoteItem {url} {event} />
|
<NoteItem {url} {event} />
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
<p class="center my-12 flex">
|
<p class="center my-12 flex">
|
||||||
<Spinner loading />
|
<Spinner loading />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {dev} from "$app/environment"
|
import {dev} from "$app/environment"
|
||||||
import {sleep, take, sortBy, ago, now, HOUR} from "@welshman/lib"
|
import {ctx, uniq, sleep, take, sortBy, ago, now, HOUR, WEEK} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
PROFILE,
|
PROFILE,
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
RELAYS,
|
RELAYS,
|
||||||
INBOX_RELAYS,
|
INBOX_RELAYS,
|
||||||
WRAP,
|
WRAP,
|
||||||
|
DELETE,
|
||||||
getPubkeyTagValues,
|
getPubkeyTagValues,
|
||||||
getListTags,
|
getListTags,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
@@ -41,8 +42,17 @@
|
|||||||
import {setupTracking} from "@app/tracking"
|
import {setupTracking} from "@app/tracking"
|
||||||
import {setupAnalytics} from "@app/analytics"
|
import {setupAnalytics} from "@app/analytics"
|
||||||
import {theme} from "@app/theme"
|
import {theme} from "@app/theme"
|
||||||
import {INDEXER_RELAYS} from "@app/state"
|
import {
|
||||||
import {loadUserData} from "@app/commands"
|
INDEXER_RELAYS,
|
||||||
|
getMembershipUrls,
|
||||||
|
getMembershipRooms,
|
||||||
|
userMembership,
|
||||||
|
MEMBERSHIPS,
|
||||||
|
MESSAGE,
|
||||||
|
COMMENT,
|
||||||
|
THREAD,
|
||||||
|
} from "@app/state"
|
||||||
|
import {loadUserData, subscribePersistent} from "@app/commands"
|
||||||
import * as state from "@app/state"
|
import * as state from "@app/state"
|
||||||
|
|
||||||
// Migration: old nostrtalk instance used different sessions
|
// Migration: old nostrtalk instance used different sessions
|
||||||
@@ -130,10 +140,55 @@
|
|||||||
tracker: storageAdapters.fromTracker(tracker, {throttle: 1000}),
|
tracker: storageAdapters.fromTracker(tracker, {throttle: 1000}),
|
||||||
}).then(() => sleep(300))
|
}).then(() => sleep(300))
|
||||||
|
|
||||||
|
let unsubRooms: any
|
||||||
|
|
||||||
|
userMembership.subscribe($membership => {
|
||||||
|
unsubRooms?.()
|
||||||
|
|
||||||
|
const since = ago(30)
|
||||||
|
const rooms = uniq(getMembershipRooms($membership).map(m => m.room))
|
||||||
|
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},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let unsubChats: any
|
||||||
|
|
||||||
|
pubkey.subscribe($pubkey => {
|
||||||
|
unsubChats?.()
|
||||||
|
|
||||||
|
if ($pubkey) {
|
||||||
|
unsubChats = subscribePersistent({
|
||||||
|
filters: [{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK)}],
|
||||||
|
relays: ctx.app.router.UserInbox().getUrls(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unwrap gift wraps as they come in
|
||||||
|
repository.on("update", ({added}) => {
|
||||||
|
for (const event of added) {
|
||||||
|
state.ensureUnwrapped(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load relay info
|
||||||
for (const url of INDEXER_RELAYS) {
|
for (const url of INDEXER_RELAYS) {
|
||||||
loadRelay(url)
|
loadRelay(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load user data
|
||||||
if ($pubkey) {
|
if ($pubkey) {
|
||||||
loadUserData($pubkey)
|
loadUserData($pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {WEEK, ctx, ago} from "@welshman/lib"
|
import {WEEK, ctx, ago} from "@welshman/lib"
|
||||||
import {WRAP} from "@welshman/util"
|
import {WRAP} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {pubkey, repository, subscribe} from "@welshman/app"
|
import {pubkey, repository} from "@welshman/app"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -35,15 +35,9 @@
|
|||||||
$: chats = $chatSearch.searchOptions(term).filter(c => c.pubkeys.length > 1)
|
$: chats = $chatSearch.searchOptions(term).filter(c => c.pubkeys.length > 1)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const sub = subscribe({
|
|
||||||
filters: [{kinds: [WRAP], "#p": [$pubkey!], since: ago(WEEK)}],
|
|
||||||
relays: ctx.app.router.UserInbox().getUrls(),
|
|
||||||
})
|
|
||||||
|
|
||||||
repository.on("update", onUpdate)
|
repository.on("update", onUpdate)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
sub.close()
|
|
||||||
repository.off("update", onUpdate)
|
repository.off("update", onUpdate)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
<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 {subscribe} from "@welshman/app"
|
|
||||||
import {DELETE} from "@welshman/util"
|
|
||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
import Delay from "@lib/components/Delay.svelte"
|
import Delay from "@lib/components/Delay.svelte"
|
||||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {checkRelayConnection, checkRelayAuth} from "@app/commands"
|
import {checkRelayConnection, checkRelayAuth} from "@app/commands"
|
||||||
import {decodeRelay, MEMBERSHIPS, THREAD, MESSAGE, COMMENT} from "@app/state"
|
import {decodeRelay} from "@app/state"
|
||||||
|
|
||||||
$: url = decodeRelay($page.params.relay)
|
$: url = decodeRelay($page.params.relay)
|
||||||
|
|
||||||
@@ -27,16 +25,6 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
checkConnection()
|
checkConnection()
|
||||||
|
|
||||||
const sub = subscribe({
|
|
||||||
filters: [
|
|
||||||
{kinds: [DELETE], "#k": [THREAD, COMMENT, MESSAGE].map(String)},
|
|
||||||
{kinds: [MEMBERSHIPS], "#r": [url]},
|
|
||||||
],
|
|
||||||
relays: [url],
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => sub.close()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {sortBy, ago, append} from "@welshman/lib"
|
import {sortBy, append} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {createEvent} from "@welshman/util"
|
import {createEvent} from "@welshman/util"
|
||||||
import {formatTimestampAsDate, publishThunk} from "@welshman/app"
|
import {formatTimestampAsDate, publishThunk} from "@welshman/app"
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
COMMENT,
|
COMMENT,
|
||||||
getMembershipRoomsByUrl,
|
getMembershipRoomsByUrl,
|
||||||
} from "@app/state"
|
} from "@app/state"
|
||||||
import {subscribePersistent, addRoomMembership, removeRoomMembership} from "@app/commands"
|
import {addRoomMembership, removeRoomMembership} from "@app/commands"
|
||||||
import {pushDrawer} from "@app/modal"
|
import {pushDrawer} from "@app/modal"
|
||||||
import {popKey} from "@app/implicit"
|
import {popKey} from "@app/implicit"
|
||||||
|
|
||||||
@@ -90,15 +89,6 @@
|
|||||||
elements.reverse()
|
elements.reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const unsub = subscribePersistent({
|
|
||||||
filters: [{"#~": [room], since: ago(30)}],
|
|
||||||
relays: [url],
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => unsub()
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loading = false
|
loading = false
|
||||||
}, 5000)
|
}, 5000)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<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 {ago, assoc} from "@welshman/lib"
|
import {sortBy, uniqBy} from "@welshman/lib"
|
||||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||||
import type {Filter} from "@welshman/util"
|
import type {Filter, TrustedEvent} from "@welshman/util"
|
||||||
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
||||||
import {nthEq} from "@welshman/lib"
|
import {nthEq} from "@welshman/lib"
|
||||||
import {createFeedController, userMutes} from "@welshman/app"
|
import {createFeedController, userMutes} from "@welshman/app"
|
||||||
@@ -15,40 +15,43 @@
|
|||||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||||
import ThreadItem from "@app/components/ThreadItem.svelte"
|
import ThreadItem from "@app/components/ThreadItem.svelte"
|
||||||
import ThreadCreate from "@app/components/ThreadCreate.svelte"
|
import ThreadCreate from "@app/components/ThreadCreate.svelte"
|
||||||
import {THREAD, COMMENT, deriveEventsForUrl, decodeRelay} from "@app/state"
|
import {THREAD, COMMENT, decodeRelay} from "@app/state"
|
||||||
import {pushModal, pushDrawer} from "@app/modal"
|
import {pushModal, pushDrawer} from "@app/modal"
|
||||||
import {subscribePersistent} from "@app/commands"
|
|
||||||
|
|
||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
const events = deriveEventsForUrl(url, [{kinds: [THREAD]}])
|
|
||||||
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
||||||
const filters: Filter[] = [{kinds: [THREAD]}, {kinds: [COMMENT], "#K": [String(THREAD)]}]
|
const filters: Filter[] = [{kinds: [THREAD]}, {kinds: [COMMENT], "#K": [String(THREAD)]}]
|
||||||
const feed = makeIntersectionFeed(makeRelayFeed(url), feedsFromFilters(filters))
|
|
||||||
|
|
||||||
const openMenu = () => pushDrawer(MenuSpace, {url})
|
const openMenu = () => pushDrawer(MenuSpace, {url})
|
||||||
|
|
||||||
const createThread = () => pushModal(ThreadCreate, {url})
|
const createThread = () => pushModal(ThreadCreate, {url})
|
||||||
|
|
||||||
let limit = 5
|
|
||||||
let loading = true
|
let loading = true
|
||||||
let element: Element
|
let element: Element
|
||||||
let scroller: Scroller
|
let scroller: Scroller
|
||||||
|
let buffer: TrustedEvent[] = []
|
||||||
|
let events: TrustedEvent[] = []
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let unmounted = false
|
let unmounted = false
|
||||||
|
|
||||||
const ctrl = createFeedController({
|
const ctrl = createFeedController({
|
||||||
feed,
|
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: () => {
|
onExhausted: () => {
|
||||||
loading = false
|
loading = false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const unsub = subscribePersistent({
|
|
||||||
filters: filters.map(assoc("since", ago(30))),
|
|
||||||
relays: [url],
|
|
||||||
})
|
|
||||||
|
|
||||||
// Element is frequently not defined. I don't know why
|
// Element is frequently not defined. I don't know why
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!unmounted) {
|
if (!unmounted) {
|
||||||
@@ -56,10 +59,16 @@
|
|||||||
element,
|
element,
|
||||||
delay: 300,
|
delay: 300,
|
||||||
threshold: 3000,
|
threshold: 3000,
|
||||||
onScroll: async () => {
|
onScroll: () => {
|
||||||
limit += 5
|
buffer = uniqBy(
|
||||||
|
e => e.id,
|
||||||
|
sortBy(e => -e.created_at, buffer),
|
||||||
|
)
|
||||||
|
events = [...events, ...buffer.splice(0, 5)]
|
||||||
|
|
||||||
await ctrl.load(5)
|
if (buffer.length < 50) {
|
||||||
|
ctrl.load(50)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -67,7 +76,6 @@
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unmounted = true
|
unmounted = true
|
||||||
unsub()
|
|
||||||
scroller?.stop()
|
scroller?.stop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -90,16 +98,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</PageBar>
|
</PageBar>
|
||||||
<div class="flex flex-grow flex-col gap-2 overflow-auto p-2">
|
<div class="flex flex-grow flex-col gap-2 overflow-auto p-2">
|
||||||
{#each $events.slice(0, limit) as event (event.id)}
|
{#each events as event (event.id)}
|
||||||
{#if !event.tags.some(nthEq(0, "e")) && !mutedPubkeys.includes(event.pubkey)}
|
|
||||||
<ThreadItem {url} {event} />
|
<ThreadItem {url} {event} />
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
<p class="flex h-10 items-center justify-center py-20">
|
<p class="flex h-10 items-center justify-center py-20">
|
||||||
<Spinner {loading}>
|
<Spinner {loading}>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
Looking for threads...
|
Looking for threads...
|
||||||
{:else if $events.length === 0}
|
{:else if events.length === 0}
|
||||||
No threads found.
|
No threads found.
|
||||||
{/if}
|
{/if}
|
||||||
</Spinner>
|
</Spinner>
|
||||||
|
|||||||
Reference in New Issue
Block a user