Add a "Share link" action to posts
This commit is contained in:
parent
bfad456d82
commit
0f487eb33a
1 changed files with 64 additions and 35 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import { page } from "$app/state";
|
import { page } from "$app/state";
|
||||||
import { goto } from "$app/navigation";
|
import { afterNavigate, goto } from "$app/navigation";
|
||||||
import * as nip19 from "@nostr/tools/nip19";
|
import * as nip19 from "@nostr/tools/nip19";
|
||||||
import {
|
import {
|
||||||
threadDetailStore,
|
threadDetailStore,
|
||||||
|
|
@ -208,6 +208,20 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The OP shares the thread URL, a reply its anchored URL.
|
||||||
|
async function shareLink(p: PostData, isOp: boolean) {
|
||||||
|
if (!detail) return;
|
||||||
|
openMenuId = null;
|
||||||
|
const url =
|
||||||
|
page.url.origin + `/thread/${detail.id}` + (isOp ? "" : `#post-${p.id}`);
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url);
|
||||||
|
showToast("Link copied");
|
||||||
|
} catch {
|
||||||
|
showToast("Could not copy the link");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!openMenuId) return;
|
if (!openMenuId) return;
|
||||||
const close = () => (openMenuId = null);
|
const close = () => (openMenuId = null);
|
||||||
|
|
@ -255,20 +269,26 @@
|
||||||
return clearHighlights;
|
return clearHighlights;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Arriving with a #post-… hash (a reply link): the target only exists once
|
// Arriving with a #post-… hash (a reply link): scroll to the post once per
|
||||||
// the replies have loaded, so scroll when it appears, once per navigation.
|
// navigation. It may only exist once the replies have loaded, so re-check as
|
||||||
|
// they stream in; on a direct load it is already there and SvelteKit's own
|
||||||
|
// deep-link scroll runs after ours, so afterNavigate applies ours again.
|
||||||
let scrolledHashFor = "";
|
let scrolledHashFor = "";
|
||||||
$effect(() => {
|
function scrollToHash(force = false) {
|
||||||
const hash = page.url.hash;
|
const hash = page.url.hash;
|
||||||
allPosts; // Re-check as replies stream in
|
|
||||||
if (!threadEl || !hash.startsWith("#post-")) return;
|
if (!threadEl || !hash.startsWith("#post-")) return;
|
||||||
const key = `${page.params.id}|${hash}`;
|
const key = `${page.params.id}|${hash}`;
|
||||||
if (scrolledHashFor === key) return;
|
if (!force && scrolledHashFor === key) return;
|
||||||
const el = document.getElementById(hash.slice(1));
|
const el = document.getElementById(hash.slice(1));
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
scrolledHashFor = key;
|
scrolledHashFor = key;
|
||||||
scrollToPost(el);
|
scrollToPost(el);
|
||||||
|
}
|
||||||
|
$effect(() => {
|
||||||
|
allPosts;
|
||||||
|
scrollToHash();
|
||||||
});
|
});
|
||||||
|
afterNavigate(() => scrollToHash(true));
|
||||||
|
|
||||||
// On desktop the thread scrolls inside <main> under a sticky title with a
|
// On desktop the thread scrolls inside <main> under a sticky title with a
|
||||||
// fade below it, so place the post under both; elsewhere the scroll margin
|
// fade below it, so place the post under both; elsewhere the scroll margin
|
||||||
|
|
@ -470,9 +490,6 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4 flex flex-shrink-0 items-center gap-1">
|
<div class="ml-4 flex flex-shrink-0 items-center gap-1">
|
||||||
{#if canDeletePost(p)}
|
|
||||||
{@const tooOld =
|
|
||||||
!canModerate && !withinSelfDeleteWindow(p.createdAt)}
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button
|
<button
|
||||||
onclick={(e) => toggleMenu(p.id, e)}
|
onclick={(e) => toggleMenu(p.id, e)}
|
||||||
|
|
@ -497,6 +514,18 @@
|
||||||
role="menu"
|
role="menu"
|
||||||
class="absolute top-7 right-0 z-20 w-36 rounded-lg border border-neutral-100 bg-white py-1 text-sm shadow-lg dark:border-neutral-800 dark:bg-neutral-900"
|
class="absolute top-7 right-0 z-20 w-36 rounded-lg border border-neutral-100 bg-white py-1 text-sm shadow-lg dark:border-neutral-800 dark:bg-neutral-900"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
role="menuitem"
|
||||||
|
onclick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
shareLink(p, index === 0);
|
||||||
|
}}
|
||||||
|
class="w-full px-3 py-1.5 text-left text-neutral-700 hover:bg-neutral-50 dark:text-neutral-300 dark:hover:bg-neutral-800"
|
||||||
|
>Share link</button
|
||||||
|
>
|
||||||
|
{#if canDeletePost(p)}
|
||||||
|
{@const tooOld =
|
||||||
|
!canModerate && !withinSelfDeleteWindow(p.createdAt)}
|
||||||
<button
|
<button
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
disabled={tooOld}
|
disabled={tooOld}
|
||||||
|
|
@ -507,10 +536,10 @@
|
||||||
class="w-full px-3 py-1.5 text-left text-red-600 hover:bg-red-50 disabled:cursor-not-allowed disabled:text-neutral-400 disabled:hover:bg-transparent dark:disabled:text-neutral-600"
|
class="w-full px-3 py-1.5 text-left text-red-600 hover:bg-red-50 disabled:cursor-not-allowed disabled:text-neutral-400 disabled:hover:bg-transparent dark:disabled:text-neutral-600"
|
||||||
>Delete{tooOld ? " (too old)" : ""}</button
|
>Delete{tooOld ? " (too old)" : ""}</button
|
||||||
>
|
>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
<span class="text-sm text-neutral-400 dark:text-neutral-500"
|
<span class="text-sm text-neutral-400 dark:text-neutral-500"
|
||||||
>{formatDate(p.createdAt)}</span
|
>{formatDate(p.createdAt)}</span
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue