diff options
| author | Fuwn <[email protected]> | 2026-02-08 02:35:39 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 02:35:39 -0800 |
| commit | 75706083f735c2705bbdc814b9013adfe6adbf7b (patch) | |
| tree | d3c63c717fb8b2df109ac3ce96a03c27ebd6b34a /apps/web/app/reader/_components | |
| parent | feat: add feed management features and fix subscribe_to_feed bugs (diff) | |
| download | asa.news-75706083f735c2705bbdc814b9013adfe6adbf7b.tar.xz asa.news-75706083f735c2705bbdc814b9013adfe6adbf7b.zip | |
fix: share clipboard write for Safari transient activation
- Use ClipboardItem with Promise to preserve user gesture context
- Fall back to showing share URL in toast if clipboard is unavailable
- Derive app origin from request URL when NEXT_PUBLIC_APP_URL is unset
- Add onError handlers to share/unshare mutations
Diffstat (limited to 'apps/web/app/reader/_components')
| -rw-r--r-- | apps/web/app/reader/_components/entry-detail-panel.tsx | 68 |
1 files changed, 43 insertions, 25 deletions
diff --git a/apps/web/app/reader/_components/entry-detail-panel.tsx b/apps/web/app/reader/_components/entry-detail-panel.tsx index 6cf9841..b67ae2a 100644 --- a/apps/web/app/reader/_components/entry-detail-panel.tsx +++ b/apps/web/app/reader/_components/entry-detail-panel.tsx @@ -114,28 +114,7 @@ export function EntryDetailPanel({ const updateHighlightNote = useUpdateHighlightNote() const deleteHighlight = useDeleteHighlight() - const shareMutation = useMutation({ - mutationFn: async (note?: string | null) => { - const response = await fetch("/api/share", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ entryIdentifier, note: note ?? null }), - }) - if (!response.ok) throw new Error("failed to create share") - return response.json() as Promise<{ - shareToken: string - shareUrl: string - }> - }, - onSuccess: async (data) => { - await navigator.clipboard.writeText(data.shareUrl) - notify("link copied") - queryClient.invalidateQueries({ - queryKey: queryKeys.entryShare.single(entryIdentifier), - }) - queryClient.invalidateQueries({ queryKey: ["shared-entries"] }) - }, - }) + const [isSharePending, setIsSharePending] = useState(false) const unshareMutation = useMutation({ mutationFn: async (shareToken: string) => { @@ -150,6 +129,9 @@ export function EntryDetailPanel({ queryKey: queryKeys.entryShare.single(entryIdentifier), }) }, + onError: (error: Error) => { + notify(error.message) + }, }) useEffect(() => { @@ -294,8 +276,44 @@ export function EntryDetailPanel({ }, [isShareNoteDialogOpen]) function handleShareConfirm() { - shareMutation.mutate(shareNoteText.trim() || null) + const note = shareNoteText.trim() || null setIsShareNoteDialogOpen(false) + setIsSharePending(true) + + const shareUrlPromise = fetch("/api/share", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ entryIdentifier, note }), + }) + .then((response) => { + if (!response.ok) throw new Error("failed to create share") + return response.json() + }) + .then((data: { shareUrl: string }) => data.shareUrl) + + try { + const clipboardItem = new ClipboardItem({ + "text/plain": shareUrlPromise.then( + (url) => new Blob([url], { type: "text/plain" }) + ), + }) + navigator.clipboard.write([clipboardItem]).then( + () => notify("link copied"), + () => shareUrlPromise.then((url) => notify("shared — " + url)) + ) + } catch { + shareUrlPromise.then((url) => notify("shared — " + url)) + } + + shareUrlPromise.then( + () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.entryShare.single(entryIdentifier), + }) + queryClient.invalidateQueries({ queryKey: ["shared-entries"] }) + }, + (error) => notify(error.message) + ).finally(() => setIsSharePending(false)) } function handleCreateHighlight(note: string | null) { @@ -551,10 +569,10 @@ export function EntryDetailPanel({ </button> <button type="submit" - disabled={shareMutation.isPending} + disabled={isSharePending} className="flex-1 border border-border bg-background-tertiary px-4 py-2 text-text-primary transition-colors hover:bg-border disabled:opacity-50" > - {shareMutation.isPending ? "sharing..." : "share"} + {isSharePending ? "sharing..." : "share"} </button> </div> </form> |