Show a person's most recent pinned note on their profile dialog
This commit is contained in:
parent
e8a1f0bba7
commit
fc86977a47
5 changed files with 77 additions and 27 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
import ProfileAbout from "@app/components/ProfileAbout.svelte"
|
import ProfileAbout from "@app/components/ProfileAbout.svelte"
|
||||||
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
||||||
import ProfileMenu from "@app/components/ProfileMenu.svelte"
|
import ProfileMenu from "@app/components/ProfileMenu.svelte"
|
||||||
|
import ProfilePinnedNote from "@app/components/ProfilePinnedNote.svelte"
|
||||||
import ProfileStatus from "@app/components/ProfileStatus.svelte"
|
import ProfileStatus from "@app/components/ProfileStatus.svelte"
|
||||||
import {messagingRelayLists, profiles, relayManagement, user} from "@app/core"
|
import {messagingRelayLists, profiles, relayManagement, user} from "@app/core"
|
||||||
import {deriveUserIsSpaceAdmin} from "@app/management"
|
import {deriveUserIsSpaceAdmin} from "@app/management"
|
||||||
|
|
@ -138,6 +139,7 @@
|
||||||
<ProfileStatus {pubkey} {url} />
|
<ProfileStatus {pubkey} {url} />
|
||||||
<ProfileAbout {pubkey} {url} />
|
<ProfileAbout {pubkey} {url} />
|
||||||
<ProfileBadges {pubkey} {url} />
|
<ProfileBadges {pubkey} {url} />
|
||||||
|
<ProfilePinnedNote {pubkey} {url} />
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onDestroy, onMount} from "svelte"
|
import {onDestroy, onMount} from "svelte"
|
||||||
import {derived, writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import type {Readable} from "svelte/store"
|
import type {Readable} from "svelte/store"
|
||||||
import {sortBy, uniqBy, now} from "@welshman/lib"
|
import {sortBy, uniqBy, now} from "@welshman/lib"
|
||||||
import type {Maybe} from "@welshman/lib"
|
import type {Maybe} from "@welshman/lib"
|
||||||
import {NOTE, outbox} from "@welshman/util"
|
import {NOTE, outbox} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {getReplyTags} from "@welshman/domain"
|
import {getReplyTags} from "@welshman/domain"
|
||||||
import {PinLists} from "@welshman/app"
|
|
||||||
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 {app, network, router} from "@app/core"
|
import {router} from "@app/core"
|
||||||
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
|
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
|
||||||
|
import {derivePinnedEvents} from "@app/pins"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
|
@ -25,30 +25,9 @@
|
||||||
|
|
||||||
onDestroy(context.cleanup)
|
onDestroy(context.cleanup)
|
||||||
|
|
||||||
const pinnedIds = derived($app.use(PinLists).one(pubkey), $pinList => $pinList?.ids() ?? [])
|
const pinnedEvents = derivePinnedEvents(pubkey)
|
||||||
|
|
||||||
// Pinned notes are asked for by id instead of by the feed's filters, so they get a store of
|
const pinnedIds = $derived($pinnedEvents.map(e => e.id))
|
||||||
// their own. A feed dedupes and sorts what it holds as it loads it, and writing to its store
|
|
||||||
// from out here bypasses both.
|
|
||||||
const pinnedEvents = writable<TrustedEvent[]>([])
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if ($pinnedIds.length > 0) {
|
|
||||||
const controller = new AbortController()
|
|
||||||
|
|
||||||
relays.then($relays =>
|
|
||||||
$network.load({
|
|
||||||
relays: $relays,
|
|
||||||
filters: [{ids: $pinnedIds}],
|
|
||||||
signal: controller.signal,
|
|
||||||
onEvent: event =>
|
|
||||||
pinnedEvents.update($pinned => uniqBy(e => e.id, $pinned.concat(event))),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => controller.abort()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let element: HTMLElement | undefined = $state()
|
let element: HTMLElement | undefined = $state()
|
||||||
let older: Maybe<ReturnType<typeof makeScrollLoader>> = $state()
|
let older: Maybe<ReturnType<typeof makeScrollLoader>> = $state()
|
||||||
|
|
@ -59,7 +38,7 @@
|
||||||
|
|
||||||
const feedEvents = $derived(
|
const feedEvents = $derived(
|
||||||
sortBy(
|
sortBy(
|
||||||
e => ($pinnedIds.includes(e.id) ? -(now() + e.created_at) : -e.created_at),
|
e => (pinnedIds.includes(e.id) ? -(now() + e.created_at) : -e.created_at),
|
||||||
uniqBy(e => e.id, [...$pinnedEvents, ...$events]).filter(
|
uniqBy(e => e.id, [...$pinnedEvents, ...$events]).filter(
|
||||||
e => getReplyTags(e.tags).replies.length === 0,
|
e => getReplyTags(e.tags).replies.length === 0,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
31
src/app/components/ProfilePinnedNote.svelte
Normal file
31
src/app/components/ProfilePinnedNote.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {NOTE} from "@welshman/util"
|
||||||
|
import Pin from "@assets/icons/pin.svg?dataurl"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import NoteCard from "@app/components/NoteCard.svelte"
|
||||||
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
|
import {derivePinnedEvents} from "@app/pins"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
pubkey: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {pubkey, url}: Props = $props()
|
||||||
|
|
||||||
|
const pinnedEvents = derivePinnedEvents(pubkey)
|
||||||
|
|
||||||
|
const event = $derived($pinnedEvents.find(e => e.kind === NOTE))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if event}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-content-muted flex items-center gap-2 text-sm">
|
||||||
|
<Icon icon={Pin} size={4} />
|
||||||
|
Pinned note
|
||||||
|
</p>
|
||||||
|
<NoteCard {event} {url} hideProfile class="card">
|
||||||
|
<NoteContent {event} {url} minLength={120} maxLength={240} expandMode="inline" />
|
||||||
|
</NoteCard>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
MessagingRelayLists,
|
MessagingRelayLists,
|
||||||
MuteLists,
|
MuteLists,
|
||||||
Network,
|
Network,
|
||||||
|
PinLists,
|
||||||
Profiles,
|
Profiles,
|
||||||
Reactions,
|
Reactions,
|
||||||
RelayLists,
|
RelayLists,
|
||||||
|
|
@ -143,6 +144,7 @@ export const logger = usePlugin(Logger)
|
||||||
export const messagingRelayLists = usePlugin(MessagingRelayLists)
|
export const messagingRelayLists = usePlugin(MessagingRelayLists)
|
||||||
export const muteLists = usePlugin(MuteLists)
|
export const muteLists = usePlugin(MuteLists)
|
||||||
export const network = usePlugin(Network)
|
export const network = usePlugin(Network)
|
||||||
|
export const pinLists = usePlugin(PinLists)
|
||||||
export const profiles = usePlugin(Profiles)
|
export const profiles = usePlugin(Profiles)
|
||||||
export const reactions = usePlugin(Reactions)
|
export const reactions = usePlugin(Reactions)
|
||||||
export const relayLists = usePlugin(RelayLists)
|
export const relayLists = usePlugin(RelayLists)
|
||||||
|
|
|
||||||
36
src/app/pins.ts
Normal file
36
src/app/pins.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {derived} from "svelte/store"
|
||||||
|
import type {Readable} from "svelte/store"
|
||||||
|
import {getIdFilters, outbox} from "@welshman/util"
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {network, pinLists, router} from "@app/core"
|
||||||
|
import {deriveEvents} from "@app/repository"
|
||||||
|
|
||||||
|
export const derivePinnedEvents = (pubkey: string): Readable<TrustedEvent[]> =>
|
||||||
|
derived(
|
||||||
|
pinLists.get().one(pubkey),
|
||||||
|
($pinList, set: (events: TrustedEvent[]) => void) => {
|
||||||
|
const ids = $pinList?.ids() || []
|
||||||
|
|
||||||
|
if (ids.length > 0) {
|
||||||
|
const controller = new AbortController()
|
||||||
|
const filters = getIdFilters(ids)
|
||||||
|
|
||||||
|
router
|
||||||
|
.get()
|
||||||
|
.resolver.relays([outbox(pubkey)])
|
||||||
|
.then($relays =>
|
||||||
|
network.get().load({relays: $relays, filters, signal: controller.signal}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const unsubscribe = deriveEvents(filters).subscribe(set)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort()
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set([])
|
||||||
|
},
|
||||||
|
[] as TrustedEvent[],
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue