@inshapardaz/likhari-react 0.1.7-dev.84 → 0.1.7-dev.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,10 @@ import type { SerializedEditorState } from 'lexical';
3
3
  import { type FormatId } from '@inshapardaz/likhari-converters';
4
4
  import { type EditorFeatureConfig, type FeatureConfigPresetName } from '@inshapardaz/likhari-core';
5
5
  import { type FontOption } from './fonts/index.js';
6
+ import { type DraftRestoreMode } from './components/DraftRestore.js';
6
7
  import { type Locale } from './i18n/index.js';
8
+ /** What happens when the user is about to leave with unsaved changes. */
9
+ export type NavigationGuardMode = 'confirm' | 'save-draft' | 'off';
7
10
  export interface EditorInitialContent {
8
11
  format: Extract<FormatId, 'lexical-json' | 'plain-text'>;
9
12
  value: string;
@@ -71,17 +74,44 @@ export interface EditorRootProps {
71
74
  */
72
75
  fontOptions?: FontOption[];
73
76
  /**
74
- * Debounced autosave of the current content to `localStorage`
75
- * (lexical-editor-spec.md §6.2) — a resilience net against an accidental tab
76
- * close, independent of `onSave`/the Save button. The draft is stored under
77
- * `documentId` when you give one (§6.1); without one, a unique id is
78
- * generated for this editor, so editors never overwrite each other's drafts.
79
- * A draft of this document newer than the initial content shows a banner
80
- * (Restore / Ignore / Remove) on mount, and the toolbar's Drafts button lists
81
- * every draft in the browser to restore or delete. A draft is cleared on a
82
- * successful explicit save. Defaults to `true`.
77
+ * Saves drafts: a debounced autosave of the current content to
78
+ * `localStorage` (lexical-editor-spec.md §6.2), a resilience net against an
79
+ * accidental tab close that is independent of `onSave`/the Save button. The
80
+ * draft is stored under `documentId` when you give one (§6.1); without one, a
81
+ * unique id is generated for this editor, so editors never overwrite each
82
+ * other's drafts. The toolbar's Drafts button lists every draft in the browser
83
+ * to restore or delete. A draft is cleared on a successful explicit save.
84
+ * Defaults to `true`.
83
85
  */
84
86
  autosave?: boolean;
87
+ /**
88
+ * What to do on mount when a draft of this `documentId` newer than the initial
89
+ * content exists (needs `documentId`):
90
+ * - `'prompt'` (default): a banner with Restore / Ignore / Remove draft;
91
+ * - `'auto'`: load the draft straight away, no questions (see `onDraftRestored`);
92
+ * - `'off'`: keep the initial content; the draft is moved aside into the drafts
93
+ * list so new edits don't overwrite it unseen.
94
+ */
95
+ restoreDraft?: DraftRestoreMode;
96
+ /**
97
+ * What happens when the user is about to leave with unsaved changes, for both
98
+ * `EditorRef.confirmDiscard()` (in-app navigation, which your router guard
99
+ * calls) and closing or refreshing the tab:
100
+ * - `'confirm'` (default): `confirmDiscard()` opens an in-editor popup (Save /
101
+ * Save draft / Discard changes / Stay); closing the tab shows the browser's
102
+ * own prompt, which can't be customised;
103
+ * - `'save-draft'`: silently saves a draft and lets the user go — no popup, no
104
+ * browser prompt. Works even with `autosave={false}`. If the draft can't be
105
+ * stored (e.g. the document is over `autosaveMaxBytes`) it falls back to
106
+ * `'confirm'`, so work is never lost silently;
107
+ * - `'off'`: no guard; the user is never asked.
108
+ */
109
+ navigationGuard?: NavigationGuardMode;
110
+ /** Called when a saved draft was loaded into the editor (banner Restore, or `restoreDraft="auto"`). */
111
+ onDraftRestored?: (draft: {
112
+ documentId: string;
113
+ savedAt: number;
114
+ }) => void;
85
115
  /** Debounce delay before writing an autosave draft, in ms. Default `750`. */
86
116
  autosaveDelayMs?: number;
87
117
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAU1D,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAajE,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,YAAY,CAAC,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAClD;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,eAAO,MAAM,UAAU,uGAwQrB,CAAC"}
1
+ {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"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"}
@@ -17,7 +17,8 @@ import { EditorThemeProvider } from './theme/EditorThemeProvider.js';
17
17
  import { Toolbar } from './components/Toolbar.js';
18
18
  import { injectUrduWebFontsCss } from './fonts/index.js';
19
19
  import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
20
- import { DraftBanner } from './components/DraftBanner.js';
20
+ import { DraftRestore } from './components/DraftRestore.js';
21
+ import { LeaveDialog } from './components/LeaveDialog.js';
21
22
  import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
22
23
  import { ImageOptionsContext } from './image/ImageOptionsContext.js';
23
24
  import { PortalTargetContext } from './PortalTargetContext.js';
@@ -31,7 +32,7 @@ function initialEditorStateJson(initialContent) {
31
32
  const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
32
33
  return JSON.stringify(state);
33
34
  }
34
- export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
35
+ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
35
36
  const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
36
37
  const strings = useMemo(() => getStrings(locale), [locale]);
37
38
  const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
@@ -83,6 +84,59 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
83
84
  };
84
85
  const flushDraftRef = useRef(flushDraft);
85
86
  flushDraftRef.current = flushDraft;
87
+ /** Writes the latest content as a draft now, without waiting out the debounce.
88
+ * Returns whether it is safely stored (or there was nothing worth storing). */
89
+ const saveDraftNow = () => {
90
+ if (autosaveTimerRef.current) {
91
+ clearTimeout(autosaveTimerRef.current);
92
+ autosaveTimerRef.current = null;
93
+ }
94
+ const latest = editorStateRef.current ? JSON.stringify(editorStateRef.current.toJSON()) : pendingDraftRef.current?.json;
95
+ pendingDraftRef.current = null;
96
+ if (!latest)
97
+ return true;
98
+ // A blank document isn't worth a draft.
99
+ if (!hasDraftContent(latest)) {
100
+ clearDraft(draftId);
101
+ return true;
102
+ }
103
+ const stored = writeDraft(draftId, latest, autosaveMaxBytes);
104
+ if (stored)
105
+ pruneDrafts(autosaveMaxDrafts, [draftId]);
106
+ return stored;
107
+ };
108
+ const saveDraftNowRef = useRef(saveDraftNow);
109
+ saveDraftNowRef.current = saveDraftNow;
110
+ const navigationGuardRef = useRef(navigationGuard);
111
+ navigationGuardRef.current = navigationGuard;
112
+ // The in-editor "leave?" popup behind confirmDiscard().
113
+ const [leavePrompt, setLeavePrompt] = useState(null);
114
+ const leaveResolveRef = useRef(null);
115
+ const settleLeave = (canLeave) => {
116
+ const resolve = leaveResolveRef.current;
117
+ leaveResolveRef.current = null;
118
+ setLeavePrompt(null);
119
+ resolve?.(canLeave);
120
+ };
121
+ const askToLeave = (draftFailed = false) => new Promise((resolve) => {
122
+ leaveResolveRef.current?.(false); // a second call supersedes an unanswered one
123
+ leaveResolveRef.current = resolve;
124
+ setLeavePrompt({ draftFailed });
125
+ });
126
+ const confirmDiscardImpl = async () => {
127
+ if (!isDirtyRef.current || navigationGuard === 'off')
128
+ return true;
129
+ if (navigationGuard === 'save-draft') {
130
+ if (saveDraftNow())
131
+ return true;
132
+ return askToLeave(true);
133
+ }
134
+ return askToLeave();
135
+ };
136
+ const confirmDiscardRef = useRef(confirmDiscardImpl);
137
+ confirmDiscardRef.current = confirmDiscardImpl;
138
+ // An unanswered popup must not leave a caller's promise hanging if the editor goes away.
139
+ useEffect(() => () => leaveResolveRef.current?.(false), []);
86
140
  // Computed once, before the first autosave write could possibly run, so
87
141
  // the restore-prompt plugin below can tell a stored draft that's merely
88
142
  // identical to the initial content apart (no prompt needed) from one that
@@ -114,6 +168,12 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
114
168
  flushDraftRef.current();
115
169
  if (!isDirtyRef.current)
116
170
  return;
171
+ const guard = navigationGuardRef.current;
172
+ if (guard === 'off')
173
+ return;
174
+ // Saved a draft instead: nothing is lost, so don't interrupt the user.
175
+ if (guard === 'save-draft' && saveDraftNowRef.current())
176
+ return;
117
177
  event.preventDefault();
118
178
  event.returnValue = '';
119
179
  };
@@ -141,17 +201,13 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
141
201
  hasUnsavedChanges() {
142
202
  return isDirty;
143
203
  },
144
- async confirmDiscard() {
145
- if (!isDirty)
146
- return true;
147
- if (typeof window === 'undefined')
148
- return true;
149
- return window.confirm(strings.editor.confirmDiscard);
204
+ confirmDiscard() {
205
+ return confirmDiscardRef.current();
150
206
  },
151
207
  focus() {
152
208
  rootElementRef.current?.focus();
153
209
  },
154
- }), [isDirty, strings]);
210
+ }), [isDirty]);
155
211
  const rootElementRef = useRef(null);
156
212
  const handleChange = (state) => {
157
213
  editorStateRef.current = state;
@@ -168,6 +224,8 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
168
224
  /** A draft was restored into the editor: that content is unsaved work. */
169
225
  const handleDraftRestored = (entry) => {
170
226
  setIsDirty(true);
227
+ if (documentId && entry.id === documentId)
228
+ onDraftRestored?.({ documentId, savedAt: entry.draft.savedAt });
171
229
  // Without a documentId the restored draft becomes this editor's own, so
172
230
  // later edits update it instead of piling up copies.
173
231
  if (!documentId)
@@ -198,7 +256,24 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
198
256
  // rather than colliding on a shared one.
199
257
  const portalTargetId = useId();
200
258
  const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
201
- return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsx(UiStringsContext.Provider, { value: strings, children: _jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
202
- ? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
203
- : undefined }), autosave && documentId && (_jsx(DraftBanner, { draftId: documentId, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }) }) })] }) }));
259
+ return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
260
+ ? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
261
+ : undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
262
+ handleSave();
263
+ settleLeave(true);
264
+ }, onSaveDraft: () => {
265
+ if (saveDraftNow())
266
+ settleLeave(true);
267
+ else
268
+ setLeavePrompt({ draftFailed: true });
269
+ }, onDiscard: () => {
270
+ // The user chose to drop this work, so no draft of it should linger either.
271
+ if (autosaveTimerRef.current) {
272
+ clearTimeout(autosaveTimerRef.current);
273
+ autosaveTimerRef.current = null;
274
+ }
275
+ pendingDraftRef.current = null;
276
+ clearDraft(draftId);
277
+ settleLeave(true);
278
+ }, onStay: () => settleLeave(false) })] }) })] }) }));
204
279
  });
@@ -0,0 +1,24 @@
1
+ import { type DraftEntry } from '../persistence/draftStorage.js';
2
+ import type { Locale } from '../i18n/index.js';
3
+ /** What to do on mount with a stored draft of this document that is newer than the initial content. */
4
+ export type DraftRestoreMode = 'prompt' | 'auto' | 'off';
5
+ export interface DraftRestoreProps {
6
+ /** The host's `documentId` — the key the draft is stored under. */
7
+ draftId: string;
8
+ mode: DraftRestoreMode;
9
+ /** The Lexical JSON the editor started with; a draft identical to it isn't offered. */
10
+ initialJson?: string;
11
+ locale: Locale;
12
+ onRestored: (entry: DraftEntry) => void;
13
+ }
14
+ /**
15
+ * On mount, handles a draft of this document newer than the initial content
16
+ * (lexical-editor-spec.md §6.2), per `mode`:
17
+ * - `prompt` (default): a banner — Restore, Ignore (kept in the drafts list,
18
+ * since new edits would otherwise overwrite it) or Remove. Never picks silently.
19
+ * - `auto`: loads the draft straight away, no banner.
20
+ * - `off`: leaves the editor on its initial content, but moves the draft aside
21
+ * as an earlier version so new edits can't overwrite it unseen.
22
+ */
23
+ export declare function DraftRestore({ draftId, mode, initialJson, locale, onRestored }: DraftRestoreProps): import("react").JSX.Element | null;
24
+ //# sourceMappingURL=DraftRestore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DraftRestore.d.ts","sourceRoot":"","sources":["../../src/components/DraftRestore.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAwD,KAAK,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AAGtI,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,uGAAuG;AACvG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;IACvB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,sCA8DjG"}
@@ -1,26 +1,30 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { IconHistory } from '@tabler/icons-react';
4
4
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
5
5
  import { archiveDraft, clearDraft, hasDraftContent, readDraft } from '../persistence/draftStorage.js';
6
6
  import { applyDraftToEditor, formatDraftTime } from '../persistence/draftUi.js';
7
7
  import { useUiStrings } from '../i18n/useStrings.js';
8
8
  /**
9
- * Shown on mount when a draft of this document newer than the initial content
10
- * is found (lexical-editor-spec.md §6.2). It never picks one silently: the
11
- * user can Restore it, Ignore it (it is kept in the drafts list, since new
12
- * edits would otherwise overwrite it), or Remove it.
9
+ * On mount, handles a draft of this document newer than the initial content
10
+ * (lexical-editor-spec.md §6.2), per `mode`:
11
+ * - `prompt` (default): a banner — Restore, Ignore (kept in the drafts list,
12
+ * since new edits would otherwise overwrite it) or Remove. Never picks silently.
13
+ * - `auto`: loads the draft straight away, no banner.
14
+ * - `off`: leaves the editor on its initial content, but moves the draft aside
15
+ * as an earlier version so new edits can't overwrite it unseen.
13
16
  */
14
- export function DraftBanner({ draftId, initialJson, locale, onRestored }) {
17
+ export function DraftRestore({ draftId, mode, initialJson, locale, onRestored }) {
15
18
  const [editor] = useLexicalComposerContext();
16
19
  const strings = useUiStrings();
17
20
  const [draft, setDraft] = useState(() => {
18
21
  const stored = readDraft(draftId);
19
22
  return stored && hasDraftContent(stored.json) && stored.json !== initialJson ? stored : null;
20
23
  });
21
- if (!draft)
22
- return null;
24
+ const handled = useRef(false);
23
25
  const restore = () => {
26
+ if (!draft)
27
+ return;
24
28
  if (applyDraftToEditor(editor, draft.json))
25
29
  onRestored({ id: draftId, draft });
26
30
  // A corrupt or incompatible draft can't be restored: drop the banner and keep the initial content.
@@ -32,10 +36,24 @@ export function DraftBanner({ draftId, initialJson, locale, onRestored }) {
32
36
  clearDraft(draftId);
33
37
  setDraft(null);
34
38
  };
39
+ // Non-interactive modes act once, on mount.
40
+ useEffect(() => {
41
+ if (handled.current || !draft)
42
+ return;
43
+ handled.current = true;
44
+ if (mode === 'auto')
45
+ restore();
46
+ else if (mode === 'off')
47
+ ignore();
48
+ // Deliberately runs once on mount only.
49
+ // eslint-disable-next-line react-hooks/exhaustive-deps
50
+ }, []);
35
51
  const remove = () => {
36
52
  clearDraft(draftId);
37
53
  setDraft(null);
38
54
  };
55
+ if (!draft || mode !== 'prompt')
56
+ return null;
39
57
  // Plain elements styled with the editor's own tokens (editor.css): Mantine's
40
58
  // theme variables are scoped to the portal target, so an in-flow Mantine
41
59
  // button here would fall back to Mantine's default blue.
@@ -0,0 +1,22 @@
1
+ export interface LeaveDialogProps {
2
+ opened: boolean;
3
+ /** Offer "Save" (calls the host's `onSave`, then lets the user leave). */
4
+ canSave: boolean;
5
+ /** Offer "Save draft" (keeps the work in this browser, then lets the user leave). */
6
+ canSaveDraft: boolean;
7
+ /** A "Save draft" attempt failed; explains why the dialog is still open. */
8
+ draftFailed: boolean;
9
+ onSave: () => void;
10
+ onSaveDraft: () => void;
11
+ onDiscard: () => void;
12
+ /** Stay on the page (also Escape, the close button, and clicking outside). */
13
+ onStay: () => void;
14
+ }
15
+ /**
16
+ * The in-editor popup behind `EditorRef.confirmDiscard()`: what to do with
17
+ * unsaved changes before leaving. It replaces `window.confirm()`, which can't
18
+ * be styled, translated per editor, or offer more than OK/Cancel. (The browser's
19
+ * own tab-close prompt can't be replaced — see `navigationGuard`.)
20
+ */
21
+ export declare function LeaveDialog({ opened, canSave, canSaveDraft, draftFailed, onSave, onSaveDraft, onDiscard, onStay }: LeaveDialogProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=LeaveDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeaveDialog.d.ts","sourceRoot":"","sources":["../../src/components/LeaveDialog.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,YAAY,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,gBAAgB,+BAoCnI"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Alert, Button, Group, Modal, Stack, Text } from '@mantine/core';
3
+ import { useUiStrings } from '../i18n/useStrings.js';
4
+ import { usePortalTarget } from '../PortalTargetContext.js';
5
+ /**
6
+ * The in-editor popup behind `EditorRef.confirmDiscard()`: what to do with
7
+ * unsaved changes before leaving. It replaces `window.confirm()`, which can't
8
+ * be styled, translated per editor, or offer more than OK/Cancel. (The browser's
9
+ * own tab-close prompt can't be replaced — see `navigationGuard`.)
10
+ */
11
+ export function LeaveDialog({ opened, canSave, canSaveDraft, draftFailed, onSave, onSaveDraft, onDiscard, onStay }) {
12
+ const strings = useUiStrings();
13
+ const portalTarget = usePortalTarget();
14
+ return (_jsx(Modal, { opened: opened, onClose: onStay, title: strings.leaveDialog.title, centered: true, size: "md", portalProps: portalTarget ? { target: portalTarget } : undefined, children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "sm", children: strings.leaveDialog.message }), draftFailed && (_jsx(Alert, { color: "red", variant: "light", p: "xs", children: strings.leaveDialog.saveDraftFailed })), _jsxs(Group, { justify: "flex-end", gap: "xs", wrap: "wrap", children: [_jsx(Button, { variant: "default", onClick: onStay, "data-autofocus": true, children: strings.leaveDialog.stay }), _jsx(Button, { variant: "subtle", color: "red", onClick: onDiscard, children: strings.leaveDialog.discard }), canSaveDraft && (_jsx(Button, { variant: "default", onClick: onSaveDraft, children: strings.leaveDialog.saveDraft })), canSave && _jsx(Button, { onClick: onSave, children: strings.leaveDialog.save })] })] }) }));
15
+ }
@@ -13,8 +13,6 @@ export interface Strings {
13
13
  placeholder: string;
14
14
  /** aria-label on the contenteditable itself. */
15
15
  contentLabel: string;
16
- /** window.confirm() prompt shown by EditorRef.confirmDiscard() when there are unsaved changes. */
17
- confirmDiscard: string;
18
16
  };
19
17
  common: {
20
18
  cancel: string;
@@ -74,6 +72,17 @@ export interface Strings {
74
72
  right: string;
75
73
  };
76
74
  };
75
+ /** The popup `EditorRef.confirmDiscard()` shows when the user is about to leave with unsaved changes (lexical-editor-spec.md §6.4). */
76
+ leaveDialog: {
77
+ title: string;
78
+ message: string;
79
+ save: string;
80
+ saveDraft: string;
81
+ discard: string;
82
+ stay: string;
83
+ /** Shown when a draft could not be stored (document too large, or storage unavailable). */
84
+ saveDraftFailed: string;
85
+ };
77
86
  /** Autosave drafts (lexical-editor-spec.md §6.2): the restore banner and the drafts list dialog. */
78
87
  drafts: {
79
88
  /** Banner shown when a newer unsaved draft of this document is found; `when` is a formatted date/time. */
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,6 @@ const en = {
8
8
  editor: {
9
9
  placeholder: 'Start writing…',
10
10
  contentLabel: 'Editor content',
11
- confirmDiscard: 'You have unsaved changes. Discard them?',
12
11
  },
13
12
  common: {
14
13
  cancel: 'Cancel',
@@ -66,6 +65,15 @@ const en = {
66
65
  right: 'Align right',
67
66
  },
68
67
  },
68
+ leaveDialog: {
69
+ title: 'Unsaved changes',
70
+ message: 'You have unsaved changes. What would you like to do before leaving?',
71
+ save: 'Save',
72
+ saveDraft: 'Save draft',
73
+ discard: 'Discard changes',
74
+ stay: 'Stay',
75
+ saveDraftFailed: "The draft couldn't be saved (the document may be too large, or browser storage is unavailable).",
76
+ },
69
77
  drafts: {
70
78
  bannerMessage: (when) => `An unsaved draft of this document from ${when} was found.`,
71
79
  restore: 'Restore',
@@ -167,7 +175,6 @@ const ur = {
167
175
  editor: {
168
176
  placeholder: 'لکھنا شروع کریں…',
169
177
  contentLabel: 'ایڈیٹر کا مواد',
170
- confirmDiscard: 'آپ کی غیر محفوظ شدہ تبدیلیاں ہیں۔ کیا انہیں رد کر دیا جائے؟',
171
178
  },
172
179
  common: {
173
180
  cancel: 'منسوخ کریں',
@@ -225,6 +232,15 @@ const ur = {
225
232
  right: 'دائیں سیدھ',
226
233
  },
227
234
  },
235
+ leaveDialog: {
236
+ title: 'غیر محفوظ شدہ تبدیلیاں',
237
+ message: 'آپ کی تبدیلیاں محفوظ نہیں ہوئیں۔ جانے سے پہلے آپ کیا کرنا چاہیں گے؟',
238
+ save: 'محفوظ کریں',
239
+ saveDraft: 'مسودہ محفوظ کریں',
240
+ discard: 'تبدیلیاں رد کریں',
241
+ stay: 'یہیں رہیں',
242
+ saveDraftFailed: 'مسودہ محفوظ نہیں ہو سکا (دستاویز بہت بڑی ہو سکتی ہے، یا براؤزر کا اسٹوریج دستیاب نہیں)۔',
243
+ },
228
244
  drafts: {
229
245
  bannerMessage: (when) => `اس دستاویز کا ایک غیر محفوظ شدہ مسودہ (${when}) ملا ہے۔`,
230
246
  restore: 'بحال کریں',
@@ -326,7 +342,6 @@ const paShahmukhi = {
326
342
  editor: {
327
343
  placeholder: 'لکھنا شروع کرو…',
328
344
  contentLabel: 'ایڈیٹر دی سامگری',
329
- confirmDiscard: 'تہاڈیاں کجھ تبدیلیاں سنبھالیاں نئیں گئیاں۔ کی ایہناں نوں رد کر دیئے؟',
330
345
  },
331
346
  common: {
332
347
  cancel: 'رد کرو',
@@ -384,6 +399,15 @@ const paShahmukhi = {
384
399
  right: 'سجے سیدھ',
385
400
  },
386
401
  },
402
+ leaveDialog: {
403
+ title: 'غیر محفوظ تبدیلیاں',
404
+ message: 'تہاڈیاں تبدیلیاں سنبھالیاں نئیں گئیاں۔ جان توں پہلاں تسی کی کرنا چاہو گے؟',
405
+ save: 'سنبھالو',
406
+ saveDraft: 'سودھا سنبھالو',
407
+ discard: 'تبدیلیاں رد کرو',
408
+ stay: 'ایتھے ای رہو',
409
+ saveDraftFailed: 'سودھا سنبھالیا نئیں جا سکیا (دستاویز بہت وڈی ہو سکدی اے، یا براؤزر دی سٹوریج نئیں اے)۔',
410
+ },
387
411
  drafts: {
388
412
  bannerMessage: (when) => `ایس دستاویز دا اک غیر محفوظ سودھا (${when}) لبھیا اے۔`,
389
413
  restore: 'بحال کرو',
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { EditorRoot } from './EditorRoot.js';
2
- export type { EditorRootProps, EditorRef, EditorInitialContent } from './EditorRoot.js';
2
+ export type { EditorRootProps, EditorRef, EditorInitialContent, NavigationGuardMode } from './EditorRoot.js';
3
+ export type { DraftRestoreMode } from './components/DraftRestore.js';
3
4
  export { defaultMantineTheme } from './theme/mantineTheme.js';
4
5
  export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
5
6
  export type { ImagePayload, ImageLinkType, SerializedImageNode } from './image/ImageNode.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
@@ -33,13 +33,14 @@ export declare function extractPreview(json: string): string;
33
33
  export declare function hasDraftContent(json: string): boolean;
34
34
  export declare function readDraft(id: string): StoredDraft | null;
35
35
  /**
36
- * Best-effort write. Silently skips (rather than throwing) when `json` exceeds
36
+ * Best-effort write; returns whether the draft was actually stored (callers that
37
+ * promise the user a saved draft must check). Skips (rather than throwing) when `json` exceeds
37
38
  * `maxBytes` — localStorage has a shared ~5-10MB per-origin ceiling (§6.2), so a
38
39
  * single large document (e.g. embedded base64 images) must not be allowed to
39
40
  * autosave past a caller-configured limit — or when the write itself fails
40
41
  * (quota exceeded, privacy mode).
41
42
  */
42
- export declare function writeDraft(id: string, json: string, maxBytes: number, sourceId?: string): void;
43
+ export declare function writeDraft(id: string, json: string, maxBytes: number, sourceId?: string): boolean;
43
44
  export declare function clearDraft(id: string): void;
44
45
  /** Every stored draft, newest first. */
45
46
  export declare function listDrafts(): DraftEntry[];
@@ -1 +1 @@
1
- {"version":3,"file":"draftStorage.d.ts","sourceRoot":"","sources":["../../src/persistence/draftStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,OAAO,EAAE,CAAC,CAAC;IACX,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,MAAM,CAOtC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEtD;AAgDD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAaD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQxD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAc9F;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQ3C;AAED,wCAAwC;AACxC,wBAAgB,UAAU,IAAI,UAAU,EAAE,CAezC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWtD;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,IAAI,CAQrE"}
1
+ {"version":3,"file":"draftStorage.d.ts","sourceRoot":"","sources":["../../src/persistence/draftStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,OAAO,EAAE,CAAC,CAAC;IACX,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,MAAM,CAOtC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEtD;AAgDD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAaD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQxD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAgBjG;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQ3C;AAED,wCAAwC;AACxC,wBAAgB,UAAU,IAAI,UAAU,EAAE,CAezC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWtD;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,IAAI,CAQrE"}
@@ -104,7 +104,8 @@ export function readDraft(id) {
104
104
  }
105
105
  }
106
106
  /**
107
- * Best-effort write. Silently skips (rather than throwing) when `json` exceeds
107
+ * Best-effort write; returns whether the draft was actually stored (callers that
108
+ * promise the user a saved draft must check). Skips (rather than throwing) when `json` exceeds
108
109
  * `maxBytes` — localStorage has a shared ~5-10MB per-origin ceiling (§6.2), so a
109
110
  * single large document (e.g. embedded base64 images) must not be allowed to
110
111
  * autosave past a caller-configured limit — or when the write itself fails
@@ -113,20 +114,22 @@ export function readDraft(id) {
113
114
  export function writeDraft(id, json, maxBytes, sourceId) {
114
115
  const storage = getLocalStorage();
115
116
  if (!storage)
116
- return;
117
+ return false;
117
118
  // .length (UTF-16 code units) over-estimates UTF-8 byte size for any
118
119
  // non-Latin text, which only makes this check more conservative — fine
119
120
  // for a soft cap that doesn't need to be exact.
120
121
  if (json.length > maxBytes)
121
- return;
122
+ return false;
122
123
  const draft = { json, savedAt: Date.now(), version: 1, preview: extractPreview(json) };
123
124
  if (sourceId)
124
125
  draft.sourceId = sourceId;
125
126
  try {
126
127
  storage.setItem(draftKey(id), JSON.stringify(draft));
128
+ return true;
127
129
  }
128
130
  catch {
129
131
  // Quota exceeded or similar — autosave is best-effort.
132
+ return false;
130
133
  }
131
134
  }
132
135
  export function clearDraft(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.7-dev.84",
3
+ "version": "0.1.7-dev.86",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,18 +0,0 @@
1
- import { type DraftEntry } from '../persistence/draftStorage.js';
2
- import type { Locale } from '../i18n/index.js';
3
- export interface DraftBannerProps {
4
- /** The host's `documentId` — the key the draft is stored under. */
5
- draftId: string;
6
- /** The Lexical JSON the editor started with; a draft identical to it isn't offered. */
7
- initialJson?: string;
8
- locale: Locale;
9
- onRestored: (entry: DraftEntry) => void;
10
- }
11
- /**
12
- * Shown on mount when a draft of this document newer than the initial content
13
- * is found (lexical-editor-spec.md §6.2). It never picks one silently: the
14
- * user can Restore it, Ignore it (it is kept in the drafts list, since new
15
- * edits would otherwise overwrite it), or Remove it.
16
- */
17
- export declare function DraftBanner({ draftId, initialJson, locale, onRestored }: DraftBannerProps): import("react").JSX.Element | null;
18
- //# sourceMappingURL=DraftBanner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DraftBanner.d.ts","sourceRoot":"","sources":["../../src/components/DraftBanner.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAwD,KAAK,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AAGtI,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACzC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,gBAAgB,sCAiDzF"}