diff options
| author | Fuwn <[email protected]> | 2026-02-08 07:07:59 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 07:07:59 -0800 |
| commit | a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1 (patch) | |
| tree | 7d44bdcb94cc1b69fbc201a4757f27f3751c5adb /apps/web/app/reader | |
| parent | chore: gate Vercel analytics and speed insights to production only (diff) | |
| download | asa.news-a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1.tar.xz asa.news-a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1.zip | |
feat: share with highlighted excerpt and fix auth redirect URLs
Add "share" button to text selection toolbar so users can share an entry
with a highlighted passage visible to visitors. The public share page
renders the highlight and scrolls to it on load.
Also fix magic link and password reset redirects to use NEXT_PUBLIC_APP_URL
instead of window.location.origin so emails link to the production domain.
Diffstat (limited to 'apps/web/app/reader')
| -rw-r--r-- | apps/web/app/reader/_components/entry-detail-panel.tsx | 58 | ||||
| -rw-r--r-- | apps/web/app/reader/_components/highlight-selection-toolbar.tsx | 9 |
2 files changed, 67 insertions, 0 deletions
diff --git a/apps/web/app/reader/_components/entry-detail-panel.tsx b/apps/web/app/reader/_components/entry-detail-panel.tsx index b67ae2a..b772ea1 100644 --- a/apps/web/app/reader/_components/entry-detail-panel.tsx +++ b/apps/web/app/reader/_components/entry-detail-panel.tsx @@ -341,6 +341,63 @@ export function EntryDetailPanel({ setSelectionToolbarState(null) } + function handleShareFromSelection() { + const container = proseContainerReference.current + if (!container || !selectionToolbarState) return + + const serialized = serializeSelectionRange( + container, + selectionToolbarState.range + ) + if (!serialized) return + + window.getSelection()?.removeAllRanges() + setSelectionToolbarState(null) + setIsSharePending(true) + + const shareUrlPromise = fetch("/api/share", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + entryIdentifier, + highlightedText: serialized.highlightedText, + highlightTextOffset: serialized.textOffset, + highlightTextLength: serialized.textLength, + highlightTextPrefix: serialized.textPrefix, + highlightTextSuffix: serialized.textSuffix, + }), + }) + .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 with highlight"), + () => 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 handleUpdateHighlightNote(note: string | null) { if (!highlightPopoverState) return updateHighlightNote.mutate({ @@ -514,6 +571,7 @@ export function EntryDetailPanel({ selectionRect={selectionToolbarState.selectionRect} containerRect={selectionToolbarState.containerRect} onHighlight={handleCreateHighlight} + onShare={handleShareFromSelection} onDismiss={() => setSelectionToolbarState(null)} /> )} diff --git a/apps/web/app/reader/_components/highlight-selection-toolbar.tsx b/apps/web/app/reader/_components/highlight-selection-toolbar.tsx index 42522bf..f7eb8f8 100644 --- a/apps/web/app/reader/_components/highlight-selection-toolbar.tsx +++ b/apps/web/app/reader/_components/highlight-selection-toolbar.tsx @@ -6,12 +6,14 @@ interface HighlightSelectionToolbarProperties { selectionRect: DOMRect containerRect: DOMRect onHighlight: (note: string | null) => void + onShare: () => void onDismiss: () => void } export function HighlightSelectionToolbar({ selectionRect, onHighlight, + onShare, onDismiss, }: HighlightSelectionToolbarProperties) { const [showNoteInput, setShowNoteInput] = useState(false) @@ -72,6 +74,13 @@ export function HighlightSelectionToolbar({ > + note </button> + <button + type="button" + onClick={onShare} + className="px-2 py-1 text-xs text-text-dim transition-colors hover:bg-background-tertiary hover:text-text-secondary" + > + share + </button> </div> )} </div> |