Improve permalinks
This commit is contained in:
parent
4b8fcdf196
commit
2b26c033f6
5 changed files with 26 additions and 27 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
import type {Readable} from "svelte/store"
|
import type {Readable} from "svelte/store"
|
||||||
import {debounce} from "throttle-debounce"
|
import {debounce} from "throttle-debounce"
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import {now, ifLet, ago, MINUTE} from "@welshman/lib"
|
import {now, ifLet, ago, spec, MINUTE} from "@welshman/lib"
|
||||||
import type {Maybe} from "@welshman/lib"
|
import type {Maybe} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {relay, stamp, MESSAGE, RELAY_ADD_MEMBER, ROOM_ADD_MEMBER} from "@welshman/util"
|
import {relay, stamp, MESSAGE, RELAY_ADD_MEMBER, ROOM_ADD_MEMBER} from "@welshman/util"
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
|
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
|
||||||
import {pageLoading} from "@app/loading"
|
import {pageLoading} from "@app/loading"
|
||||||
import {checked, deferredRoomPath, setChecked} from "@app/notifications"
|
import {checked, deferredRoomPath, setChecked} from "@app/notifications"
|
||||||
import {highlightedEvent, makeRoomPath} from "@app/routes"
|
import {getPermalinkTarget, highlightedEvent, makeRoomPath} from "@app/routes"
|
||||||
import {pendingShare, type Share} from "@app/share"
|
import {pendingShare, type Share} from "@app/share"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
|
|
@ -117,6 +117,7 @@
|
||||||
: readable(MembershipStatus.Granted)
|
: readable(MembershipStatus.Granted)
|
||||||
const at = $derived(parseInt($page.url.searchParams.get("at")!))
|
const at = $derived(parseInt($page.url.searchParams.get("at")!))
|
||||||
const inviteCode = $derived($page.url.searchParams.get("code") || "")
|
const inviteCode = $derived($page.url.searchParams.get("code") || "")
|
||||||
|
const target = $derived(getPermalinkTarget($page.url))
|
||||||
|
|
||||||
const join = async () => {
|
const join = async () => {
|
||||||
if (h) {
|
if (h) {
|
||||||
|
|
@ -334,7 +335,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!released && !pinned && !isNaN(at)) {
|
if (!released && !pinned && !isNaN(at)) {
|
||||||
const targetEvent = $events.find(event => event.created_at >= at)
|
// The pointer only helps once that message has arrived, so the anchor still carries the jump.
|
||||||
|
const pointedAt = target ? $events.find(spec({id: target})) : undefined
|
||||||
|
const targetEvent = pointedAt ?? $events.find(event => event.created_at >= at)
|
||||||
|
|
||||||
if (targetEvent) {
|
if (targetEvent) {
|
||||||
scrollToRow(targetEvent.id, {highlight: true, pin: true})
|
scrollToRow(targetEvent.id, {highlight: true, pin: true})
|
||||||
|
|
@ -697,6 +700,8 @@
|
||||||
<p class="flex h-10 items-center justify-center py-20">
|
<p class="flex h-10 items-center justify-center py-20">
|
||||||
{#if reachedStartOfHistory}
|
{#if reachedStartOfHistory}
|
||||||
End of message history
|
End of message history
|
||||||
|
{:else}
|
||||||
|
<Spinner loading>Looking for messages...</Spinner>
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {get, writable} from "svelte/store"
|
import {get, writable} from "svelte/store"
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {identity} from "@welshman/lib"
|
import {identity, tryCatch} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
CLASSIFIED,
|
CLASSIFIED,
|
||||||
|
|
@ -32,8 +32,7 @@ export let lastChatUrl: string | undefined = undefined
|
||||||
|
|
||||||
export const lastPageBySpaceUrl = new Map<string, string>()
|
export const lastPageBySpaceUrl = new Map<string, string>()
|
||||||
|
|
||||||
// The event a link pointed at. A store rather than a class set on the node, because each row owns
|
// A store rather than a class on the node, which each row's class attribute would overwrite.
|
||||||
// its own class attribute and drops anything written behind its back on the next render.
|
|
||||||
export const highlightedEvent = writable<string | undefined>(undefined)
|
export const highlightedEvent = writable<string | undefined>(undefined)
|
||||||
|
|
||||||
// The space the user was in most recently, so the space menu can be opened from a page that isn't
|
// The space the user was in most recently, so the space menu can be opened from a page that isn't
|
||||||
|
|
@ -237,7 +236,16 @@ export const makeEventPermalink = (event: TrustedEvent, url?: string) => {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${PLATFORM_URL}${path}#${nip19.neventEncode({id: event.id, relays: urls})}`
|
const pointer = nip19.neventEncode({id: event.id, relays: urls})
|
||||||
|
|
||||||
|
return `${PLATFORM_URL}${path}${path.includes("?") ? "&" : "?"}event=${pointer}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPermalinkTarget = (url: URL) => {
|
||||||
|
const pointer = url.searchParams.get("event")
|
||||||
|
const decoded = pointer ? tryCatch(() => nip19.decode(pointer)) : undefined
|
||||||
|
|
||||||
|
return decoded?.type === "nevent" ? decoded.data.id : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const scrollToEvent = (id: string) => {
|
export const scrollToEvent = (id: string) => {
|
||||||
|
|
|
||||||
|
|
@ -129,9 +129,7 @@
|
||||||
width: var(--rule-bleed-r, 100vw);
|
width: var(--rule-bleed-r, 100vw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Marks the event a link pointed at. The mark stays until another link is followed, so the reader
|
/* The wash is an inset shadow because a row's hover rule sets a background that would cover it. */
|
||||||
can scroll away and still find their place. The wash is a spread inset shadow rather than a
|
|
||||||
background because a row's hover rule sets its own background and would paint over it. */
|
|
||||||
@utility highlight-target {
|
@utility highlight-target {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 3px 0 0 var(--primary),
|
inset 3px 0 0 var(--primary),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@
|
||||||
const {children}: Props = $props()
|
const {children}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key $page.url.searchParams.get("at")}
|
{#key [$page.url.searchParams.get("at"), $page.url.searchParams.get("event")].join()}
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
{/key}
|
{/key}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onDestroy} from "svelte"
|
import {onDestroy} from "svelte"
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
import {page} from "$app/state"
|
||||||
import {call, sleep, spec, tryCatch} from "@welshman/lib"
|
import {sleep, spec} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {deriveEventsAsc} from "@welshman/store"
|
import {deriveEventsAsc} from "@welshman/store"
|
||||||
import {getCommentFiltersForRoot, tagValue, tagSpec} from "@welshman/util"
|
import {getCommentFiltersForRoot, tagValue, tagSpec} from "@welshman/util"
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
import {network} from "@app/core"
|
import {network} from "@app/core"
|
||||||
import {makeFeedContext} from "@app/feeds"
|
import {makeFeedContext} from "@app/feeds"
|
||||||
import {decodeRelay} from "@app/relays"
|
import {decodeRelay} from "@app/relays"
|
||||||
import {makeSpacePath, scrollToEvent} from "@app/routes"
|
import {getPermalinkTarget, makeSpacePath, scrollToEvent} from "@app/routes"
|
||||||
import type {PageProps} from "./$types"
|
import type {PageProps} from "./$types"
|
||||||
|
|
||||||
const REPLY_BATCH_SIZE = 20
|
const REPLY_BATCH_SIZE = 20
|
||||||
|
|
@ -42,19 +42,7 @@
|
||||||
// A permalink's target. Replies stream in newest first, so the post it names is usually here
|
// A permalink's target. Replies stream in newest first, so the post it names is usually here
|
||||||
// long before the ones above it — its position is only right once the thread has finished
|
// long before the ones above it — its position is only right once the thread has finished
|
||||||
// arriving, so this is kept and re-read rather than acted on the first time it shows up.
|
// arriving, so this is kept and re-read rather than acted on the first time it shows up.
|
||||||
let target: string | undefined = $state(
|
let target: string | undefined = $state(getPermalinkTarget(page.url))
|
||||||
call(() => {
|
|
||||||
const hash = window.location.hash.replace(/^#/, "")
|
|
||||||
|
|
||||||
if (hash.startsWith("nevent1")) {
|
|
||||||
const decoded = tryCatch(() => nip19.decode(hash))
|
|
||||||
|
|
||||||
if (decoded?.type === "nevent") {
|
|
||||||
return decoded.data.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
let revealed = $state(REPLY_BATCH_SIZE)
|
let revealed = $state(REPLY_BATCH_SIZE)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue