Simplify profile notes loading

This commit is contained in:
Jon Staab 2026-06-29 15:48:43 -07:00
parent 0b26eb8058
commit 10119aaa6d
7 changed files with 58 additions and 140 deletions

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {goto} from "$app/navigation"
import {displayProfileByPubkey, loadMessagingRelayList} from "@welshman/app" import {displayProfileByPubkey, loadMessagingRelayList} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import UserCircle from "@assets/icons/user-circle.svg?dataurl" import UserCircle from "@assets/icons/user-circle.svg?dataurl"
@ -25,7 +26,7 @@
} from "@app/members" } from "@app/members"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {goToProfile} from "@app/routes" import {makeProfilePath} from "@app/routes"
export type Props = { export type Props = {
pubkey: string pubkey: string
@ -42,7 +43,7 @@
const back = () => history.back() const back = () => history.back()
const viewProfile = () => goToProfile(pubkey) const viewProfile = () => goto(makeProfilePath(pubkey), {replaceState: true})
const toggleMenu = () => { const toggleMenu = () => {
showMenu = !showMenu showMenu = !showMenu

View file

@ -28,7 +28,6 @@
import ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
import ProfileTrust from "@app/components/ProfileTrust.svelte" import ProfileTrust from "@app/components/ProfileTrust.svelte"
import ProfileSharedSpaces from "@app/components/ProfileSharedSpaces.svelte" import ProfileSharedSpaces from "@app/components/ProfileSharedSpaces.svelte"
import ProfilePinnedNotes from "@app/components/ProfilePinnedNotes.svelte"
import ProfilePageNotes from "@app/components/ProfilePageNotes.svelte" import ProfilePageNotes from "@app/components/ProfilePageNotes.svelte"
import ProfileQrCode from "@app/components/ProfileQrCode.svelte" import ProfileQrCode from "@app/components/ProfileQrCode.svelte"
import ProfileEdit from "@app/components/ProfileEdit.svelte" import ProfileEdit from "@app/components/ProfileEdit.svelte"
@ -255,7 +254,6 @@
<ProfileTrust pubkey={target} {isSelf} /> <ProfileTrust pubkey={target} {isSelf} />
<ProfileSharedSpaces pubkey={target} {isSelf} /> <ProfileSharedSpaces pubkey={target} {isSelf} />
</div> </div>
<ProfilePinnedNotes pubkey={target} />
<ProfilePageNotes pubkey={target} /> <ProfilePageNotes pubkey={target} />
</div> </div>
</div> </div>

View file

@ -1,15 +1,17 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {derived, get, writable} from "svelte/store" import {derived, writable} from "svelte/store"
import {sortBy, uniqBy} from "@welshman/lib" import type {Writable} from "svelte/store"
import {feedFromFilter} from "@welshman/feeds" import {sortBy, now} from "@welshman/lib"
import {NOTE, getReplyTags, getListTags, getEventTagValues} from "@welshman/util" import {NOTE, getReplyTags, getListTags, getEventTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {derivePinList, makeFeedController} from "@welshman/app" import {derivePinList} from "@welshman/app"
import {createScroller} from "@lib/html" import {Router} from "@welshman/router"
import {load} from "@welshman/net"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
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"
import {makeFeed} from "@app/feeds"
type Props = { type Props = {
pubkey: string pubkey: string
@ -18,51 +20,52 @@
const {pubkey}: Props = $props() const {pubkey}: Props = $props()
const pinList = derivePinList(pubkey) const pinList = derivePinList(pubkey)
const pinnedIds = derived(pinList, $pinList => new Set(getEventTagValues(getListTags($pinList)))) const pinnedIds = derived(pinList, $pinList => getEventTagValues(getListTags($pinList)))
let element: HTMLElement | undefined = $state() $effect(() => {
const events = writable<TrustedEvent[]>([]) if ($pinnedIds.length > 0) {
let exhausted = $state(false) const controller = new AbortController()
let buffer: TrustedEvent[] = []
const ctrl = makeFeedController({ load({
useWindowing: true, relays: Router.get().FromPubkeys([pubkey]).getUrls(),
feed: feedFromFilter({kinds: [NOTE], authors: [pubkey]}), filters: [{ids: $pinnedIds}],
onEvent: (event: TrustedEvent) => { signal: controller.signal,
if (getReplyTags(event.tags).replies.length === 0 && !get(pinnedIds).has(event.id)) { onEvent: e => events.update($events => $events.concat(e)),
buffer.push(event) })
}
}, return () => controller.abort()
onExhausted: () => { }
exhausted = true
},
}) })
let element: HTMLElement | undefined = $state()
let exhausted = $state(false)
let events: Writable<TrustedEvent[]> = $state(writable([]))
const feedEvents = $derived(
sortBy(
e => ($pinnedIds.includes(e.id) ? -(now() + e.created_at) : -e.created_at),
$events.filter(e => getReplyTags(e.tags).replies.length === 0),
),
)
onMount(() => { onMount(() => {
const scroller = createScroller({ const feed = makeFeed({
url: Router.get().FromPubkeys([pubkey]).getUrls()[0],
element: element!, element: element!,
delay: 300, filters: [{kinds: [NOTE], authors: [pubkey]}],
threshold: 3000, onBackwardExhausted: () => {
onScroll: () => { exhausted = true
buffer = uniqBy(
e => e.id,
sortBy(e => -e.created_at, buffer),
)
events.update($events => uniqBy(e => e.id, [...$events, ...buffer.splice(0, 5)]))
if (buffer.length < 50) {
ctrl.load(50)
}
}, },
}) })
return () => scroller.stop() events = feed.events
return () => feed.cleanup()
}) })
</script> </script>
<div class="flex flex-col gap-4" bind:this={element}> <div class="flex flex-col gap-4" bind:this={element}>
{#each $events as event (event.id)} {#each feedEvents as event (event.id)}
<div in:fly> <div in:fly>
<NoteItem {event} /> <NoteItem {event} />
</div> </div>

View file

@ -1,96 +0,0 @@
<script lang="ts">
import {derived} from "svelte/store"
import {sortBy} from "@welshman/lib"
import {getListTags, getEventTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {derivePinList, repository} from "@welshman/app"
import {Router} from "@welshman/router"
import {load} from "@welshman/net"
import {deriveEventsById, deriveEventsDesc} from "@welshman/store"
import {fly} from "@lib/transition"
import Spinner from "@lib/components/Spinner.svelte"
import NoteItem from "@app/components/NoteItem.svelte"
type Props = {
pubkey: string
}
const {pubkey}: Props = $props()
const pinList = derivePinList(pubkey)
const pinnedIds = derived(pinList, $pinList => getEventTagValues(getListTags($pinList)))
const pinnedEvents = derived(
pinnedIds,
($pinnedIds, set) => {
if ($pinnedIds.length === 0) {
set([])
return
}
return deriveEventsDesc(
deriveEventsById({repository, filters: [{ids: $pinnedIds}]}),
).subscribe(events => {
set(sortBy(event => -$pinnedIds.indexOf(event.id), events))
})
},
[] as TrustedEvent[],
)
let fetching = $state(false)
$effect(() => {
const ids = $pinnedIds
if (ids.length === 0) {
fetching = false
return
}
const missing = ids.filter(id => !repository.getEvent(id))
if (missing.length === 0) {
fetching = false
return
}
fetching = true
const controller = new AbortController()
load({
relays: Router.get().FromPubkeys([pubkey]).getUrls(),
filters: [{ids: missing}],
signal: controller.signal,
onClose: () => {
fetching = false
},
})
return () => controller.abort()
})
const loading = $derived(
fetching || ($pinnedIds.length > 0 && $pinnedEvents.length < $pinnedIds.length),
)
</script>
{#if $pinnedIds.length > 0 || loading}
{#if loading && $pinnedEvents.length === 0}
<p class="flex items-center justify-center py-8">
<Spinner loading />
</p>
{:else if $pinnedEvents.length > 0}
<div class="flex flex-col gap-4">
{#each $pinnedEvents as event (event.id)}
<div in:fly>
<NoteItem {event} />
</div>
{/each}
</div>
{/if}
{/if}

View file

@ -41,7 +41,7 @@
{@const isMember = !isSelf && $userSpaceUrls.includes(url)} {@const isMember = !isSelf && $userSpaceUrls.includes(url)}
<Link <Link
href={makeSpacePath(url)} href={makeSpacePath(url)}
class="card card-interactive flex flex-row items-center gap-3 p-3 sm:p-4"> class="card card-interactive flex flex-row items-center justify-between gap-3 p-3 sm:p-4">
<RelayIcon {url} size={8} /> <RelayIcon {url} size={8} />
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View file

@ -48,8 +48,6 @@ export const setupHistory = () =>
export const makeProfilePath = (pubkey: string) => `/people/${nip19.npubEncode(pubkey)}` export const makeProfilePath = (pubkey: string) => `/people/${nip19.npubEncode(pubkey)}`
export const goToProfile = (pubkey: string) => goto(makeProfilePath(pubkey))
// Chat // Chat
export const makeChatPath = (pubkeys: string[]) => `/chat/${makeChatId(pubkeys)}` export const makeChatPath = (pubkeys: string[]) => `/chat/${makeChatId(pubkeys)}`

View file

@ -0,0 +1,14 @@
<script lang="ts">
import type {Snippet} from "svelte"
import {page} from "$app/stores"
type Props = {
children?: Snippet
}
const {children}: Props = $props()
</script>
{#key $page.params.npub}
{@render children?.()}
{/key}