summaryrefslogtreecommitdiff
path: root/apps/web/app/reader/_components
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-08 02:35:39 -0800
committerFuwn <[email protected]>2026-02-08 02:35:39 -0800
commit75706083f735c2705bbdc814b9013adfe6adbf7b (patch)
treed3c63c717fb8b2df109ac3ce96a03c27ebd6b34a /apps/web/app/reader/_components
parentfeat: add feed management features and fix subscribe_to_feed bugs (diff)
downloadasa.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.tsx68
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>