import type { Editor } from '@tiptap/react'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { deriveMarkdownFromEditorJSON } from './derive-markdown'; /** * Shown when edit access to the open page was revoked while the user had * unsynced changes (issue #39, realtime-collaboration.md §Offline). The local * content stays visible behind the dialog; the user can export it as Markdown * before losing it, and discards the device-local copy only on an explicit * choice. */ export function AccessRevokedDialog({ editor, slug, onDiscard, }: { editor: Editor; slug: string; onDiscard: () => Promise; }): React.JSX.Element { const { t } = useTranslation('editor'); const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'error'>('idle'); // Derive Markdown from the live editor doc, so it reflects the local edits // the server never received (see derive-markdown.ts for the schema handling). function currentMarkdown(): string { return deriveMarkdownFromEditorJSON(editor.getJSON()); } async function copyMarkdown(): Promise { try { await navigator.clipboard.writeText(currentMarkdown()); setCopyStatus('copied'); } catch { setCopyStatus('error'); } setTimeout(() => setCopyStatus('idle'), 2000); } function downloadMarkdown(): void { const blob = new Blob([currentMarkdown()], { type: 'text/markdown;charset=utf-8' }); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = `${slug}.md`; document.body.append(anchor); anchor.click(); anchor.remove(); URL.revokeObjectURL(url); } async function discard(): Promise { if (!window.confirm(t('accessRevoked.discardConfirm'))) return; await onDiscard(); } return (

{t('accessRevoked.title')}

{t('accessRevoked.description')}

); }