@inshapardaz/likhari-react 0.1.7-dev.81 → 0.1.7-dev.84
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.
- package/dist/EditorRoot.d.ts +27 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +92 -4
- package/dist/components/DraftBanner.d.ts +18 -0
- package/dist/components/DraftBanner.d.ts.map +1 -0
- package/dist/components/DraftBanner.js +43 -0
- package/dist/components/DraftsDialog.d.ts +25 -0
- package/dist/components/DraftsDialog.d.ts.map +1 -0
- package/dist/components/DraftsDialog.js +66 -0
- package/dist/components/Toolbar.d.ts +4 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +8 -6
- package/dist/i18n/strings.d.ts +27 -3
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +69 -6
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +0 -2
- package/dist/persistence/draftStorage.d.ts +54 -0
- package/dist/persistence/draftStorage.d.ts.map +1 -0
- package/dist/persistence/draftStorage.js +195 -0
- package/dist/persistence/draftUi.d.ts +9 -0
- package/dist/persistence/draftUi.d.ts.map +1 -0
- package/dist/persistence/draftUi.js +23 -0
- package/dist/theme/editor.css +74 -39
- package/dist/theme/editorTheme.d.ts.map +1 -1
- package/dist/theme/editorTheme.js +0 -1
- package/package.json +1 -1
- package/dist/blocks/PageBreakNode.d.ts +0 -30
- package/dist/blocks/PageBreakNode.d.ts.map +0 -1
- package/dist/blocks/PageBreakNode.js +0 -110
- package/dist/plugins/PageBreakPlugin.d.ts +0 -3
- package/dist/plugins/PageBreakPlugin.d.ts.map +0 -1
- package/dist/plugins/PageBreakPlugin.js +0 -19
package/dist/EditorRoot.d.ts
CHANGED
|
@@ -70,6 +70,33 @@ export interface EditorRootProps {
|
|
|
70
70
|
* `[...DEFAULT_FONT_OPTIONS, { name: 'Mine', family: '"Mine", serif' }]`.
|
|
71
71
|
*/
|
|
72
72
|
fontOptions?: FontOption[];
|
|
73
|
+
/**
|
|
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`.
|
|
83
|
+
*/
|
|
84
|
+
autosave?: boolean;
|
|
85
|
+
/** Debounce delay before writing an autosave draft, in ms. Default `750`. */
|
|
86
|
+
autosaveDelayMs?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Skips (rather than throwing) an autosave write whose JSON would exceed
|
|
89
|
+
* this size — `localStorage` has a shared ~5-10MB per-origin ceiling, so a
|
|
90
|
+
* single document with embedded base64 images must not be allowed to
|
|
91
|
+
* autosave past a sane limit. Default `2_000_000` (~2MB).
|
|
92
|
+
*/
|
|
93
|
+
autosaveMaxBytes?: number;
|
|
94
|
+
/**
|
|
95
|
+
* How many drafts to keep in the browser, across all documents; the oldest
|
|
96
|
+
* beyond this are deleted as new ones are written (this editor's own draft
|
|
97
|
+
* is never pruned). Default `20`.
|
|
98
|
+
*/
|
|
99
|
+
autosaveMaxDrafts?: number;
|
|
73
100
|
}
|
|
74
101
|
export interface EditorRef {
|
|
75
102
|
getContent(format: FormatId): string;
|
package/dist/EditorRoot.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -17,20 +17,30 @@ 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 {
|
|
20
|
+
import { DraftBanner } from './components/DraftBanner.js';
|
|
21
|
+
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
21
22
|
import { ImageOptionsContext } from './image/ImageOptionsContext.js';
|
|
22
23
|
import { PortalTargetContext } from './PortalTargetContext.js';
|
|
23
24
|
import { UiStringsContext, getStrings } from './i18n/index.js';
|
|
25
|
+
const DEFAULT_AUTOSAVE_DELAY_MS = 750;
|
|
26
|
+
const DEFAULT_AUTOSAVE_MAX_BYTES = 2000000;
|
|
27
|
+
const DEFAULT_AUTOSAVE_MAX_DRAFTS = 20;
|
|
24
28
|
function initialEditorStateJson(initialContent) {
|
|
25
29
|
if (!initialContent)
|
|
26
30
|
return undefined;
|
|
27
31
|
const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
|
|
28
32
|
return JSON.stringify(state);
|
|
29
33
|
}
|
|
30
|
-
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, }, ref) {
|
|
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) {
|
|
31
35
|
const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
|
|
32
36
|
const strings = useMemo(() => getStrings(locale), [locale]);
|
|
33
37
|
const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
|
|
38
|
+
// The draft's storage key: the host's documentId, else — so editors without
|
|
39
|
+
// one never overwrite each other — a unique id generated for this editor (or
|
|
40
|
+
// the id of an anonymous draft the user restored into it).
|
|
41
|
+
const [generatedDraftId] = useState(createDraftId);
|
|
42
|
+
const [adoptedDraftId, setAdoptedDraftId] = useState(null);
|
|
43
|
+
const draftId = documentId ?? adoptedDraftId ?? generatedDraftId;
|
|
34
44
|
// The urdu-web-fonts stylesheets are needed for the font dropdown and for
|
|
35
45
|
// RTL content, whose canvas font (editor.css) is one of those families.
|
|
36
46
|
useEffect(() => {
|
|
@@ -48,17 +58,70 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
48
58
|
const editorStateRef = useRef(null);
|
|
49
59
|
const lastSavedJsonRef = useRef(null);
|
|
50
60
|
const [isDirty, setIsDirty] = useState(false);
|
|
61
|
+
const isDirtyRef = useRef(isDirty);
|
|
62
|
+
isDirtyRef.current = isDirty;
|
|
63
|
+
const autosaveTimerRef = useRef(null);
|
|
64
|
+
// The latest change not yet written, so unload / unmount can flush it.
|
|
65
|
+
const pendingDraftRef = useRef(null);
|
|
66
|
+
const flushDraft = () => {
|
|
67
|
+
if (autosaveTimerRef.current) {
|
|
68
|
+
clearTimeout(autosaveTimerRef.current);
|
|
69
|
+
autosaveTimerRef.current = null;
|
|
70
|
+
}
|
|
71
|
+
const pending = pendingDraftRef.current;
|
|
72
|
+
pendingDraftRef.current = null;
|
|
73
|
+
if (!pending)
|
|
74
|
+
return;
|
|
75
|
+
// A document that is blank again isn't worth a draft.
|
|
76
|
+
if (hasDraftContent(pending.json)) {
|
|
77
|
+
writeDraft(pending.id, pending.json, autosaveMaxBytes);
|
|
78
|
+
pruneDrafts(autosaveMaxDrafts, [pending.id]);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
clearDraft(pending.id);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const flushDraftRef = useRef(flushDraft);
|
|
85
|
+
flushDraftRef.current = flushDraft;
|
|
86
|
+
// Computed once, before the first autosave write could possibly run, so
|
|
87
|
+
// the restore-prompt plugin below can tell a stored draft that's merely
|
|
88
|
+
// identical to the initial content apart (no prompt needed) from one that
|
|
89
|
+
// actually represents newer, unsaved work.
|
|
90
|
+
const initialContentJsonRef = useRef(undefined);
|
|
91
|
+
if (initialContentJsonRef.current === undefined) {
|
|
92
|
+
initialContentJsonRef.current = initialEditorStateJson(initialContent);
|
|
93
|
+
}
|
|
51
94
|
const initialConfig = useMemo(() => ({
|
|
52
95
|
namespace: `likhari-editor-${documentId ?? 'anonymous'}`,
|
|
53
96
|
nodes: EDITOR_NODES,
|
|
54
97
|
theme: editorTheme,
|
|
55
|
-
editorState:
|
|
98
|
+
editorState: initialContentJsonRef.current,
|
|
56
99
|
onError(error) {
|
|
57
100
|
throw error;
|
|
58
101
|
},
|
|
59
102
|
}),
|
|
60
103
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
61
104
|
[]);
|
|
105
|
+
// beforeunload (lexical-editor-spec.md §6.4): the automatic half of the
|
|
106
|
+
// navigation guard — hasUnsavedChanges()/confirmDiscard() below are the
|
|
107
|
+
// imperative half a host's own router guard calls for in-app navigation,
|
|
108
|
+
// which the editor can't intercept on its own.
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (typeof window === 'undefined')
|
|
111
|
+
return;
|
|
112
|
+
const handleBeforeUnload = (event) => {
|
|
113
|
+
// Write any edit still waiting out the debounce before the page goes.
|
|
114
|
+
flushDraftRef.current();
|
|
115
|
+
if (!isDirtyRef.current)
|
|
116
|
+
return;
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.returnValue = '';
|
|
119
|
+
};
|
|
120
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
121
|
+
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
122
|
+
}, []);
|
|
123
|
+
// Unmounting (e.g. in-app navigation) also writes what is still pending.
|
|
124
|
+
useEffect(() => () => flushDraftRef.current(), []);
|
|
62
125
|
useImperativeHandle(ref, () => ({
|
|
63
126
|
getContent(format) {
|
|
64
127
|
if (!editorStateRef.current)
|
|
@@ -95,6 +158,20 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
95
158
|
const json = JSON.stringify(state.toJSON());
|
|
96
159
|
setIsDirty(json !== lastSavedJsonRef.current);
|
|
97
160
|
onChange?.(state.toJSON());
|
|
161
|
+
if (autosave) {
|
|
162
|
+
pendingDraftRef.current = { id: draftId, json };
|
|
163
|
+
if (autosaveTimerRef.current)
|
|
164
|
+
clearTimeout(autosaveTimerRef.current);
|
|
165
|
+
autosaveTimerRef.current = setTimeout(flushDraft, autosaveDelayMs);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/** A draft was restored into the editor: that content is unsaved work. */
|
|
169
|
+
const handleDraftRestored = (entry) => {
|
|
170
|
+
setIsDirty(true);
|
|
171
|
+
// Without a documentId the restored draft becomes this editor's own, so
|
|
172
|
+
// later edits update it instead of piling up copies.
|
|
173
|
+
if (!documentId)
|
|
174
|
+
setAdoptedDraftId(entry.id);
|
|
98
175
|
};
|
|
99
176
|
const handleSave = () => {
|
|
100
177
|
if (!editorStateRef.current)
|
|
@@ -104,6 +181,15 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
104
181
|
const content = defaultFormatRegistry.serialize(format, json);
|
|
105
182
|
lastSavedJsonRef.current = JSON.stringify(json);
|
|
106
183
|
setIsDirty(false);
|
|
184
|
+
// A successful explicit save supersedes any in-progress/queued autosave
|
|
185
|
+
// draft — don't let a stale one linger and falsely prompt to "restore"
|
|
186
|
+
// older content next time this document is opened (§6.2).
|
|
187
|
+
if (autosaveTimerRef.current) {
|
|
188
|
+
clearTimeout(autosaveTimerRef.current);
|
|
189
|
+
autosaveTimerRef.current = null;
|
|
190
|
+
}
|
|
191
|
+
pendingDraftRef.current = null;
|
|
192
|
+
clearDraft(draftId);
|
|
107
193
|
onSave?.(content, format);
|
|
108
194
|
};
|
|
109
195
|
const dir = locale === 'en' ? 'ltr' : 'rtl';
|
|
@@ -112,5 +198,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
112
198
|
// rather than colliding on a shared one.
|
|
113
199
|
const portalTargetId = useId();
|
|
114
200
|
const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
|
|
115
|
-
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
|
|
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 })] }) }) }) })] }) }));
|
|
116
204
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { IconHistory } from '@tabler/icons-react';
|
|
4
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
5
|
+
import { archiveDraft, clearDraft, hasDraftContent, readDraft } from '../persistence/draftStorage.js';
|
|
6
|
+
import { applyDraftToEditor, formatDraftTime } from '../persistence/draftUi.js';
|
|
7
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
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.
|
|
13
|
+
*/
|
|
14
|
+
export function DraftBanner({ draftId, initialJson, locale, onRestored }) {
|
|
15
|
+
const [editor] = useLexicalComposerContext();
|
|
16
|
+
const strings = useUiStrings();
|
|
17
|
+
const [draft, setDraft] = useState(() => {
|
|
18
|
+
const stored = readDraft(draftId);
|
|
19
|
+
return stored && hasDraftContent(stored.json) && stored.json !== initialJson ? stored : null;
|
|
20
|
+
});
|
|
21
|
+
if (!draft)
|
|
22
|
+
return null;
|
|
23
|
+
const restore = () => {
|
|
24
|
+
if (applyDraftToEditor(editor, draft.json))
|
|
25
|
+
onRestored({ id: draftId, draft });
|
|
26
|
+
// A corrupt or incompatible draft can't be restored: drop the banner and keep the initial content.
|
|
27
|
+
setDraft(null);
|
|
28
|
+
};
|
|
29
|
+
const ignore = () => {
|
|
30
|
+
// Move it aside, so edits from here on can't overwrite it unseen.
|
|
31
|
+
if (archiveDraft(draftId))
|
|
32
|
+
clearDraft(draftId);
|
|
33
|
+
setDraft(null);
|
|
34
|
+
};
|
|
35
|
+
const remove = () => {
|
|
36
|
+
clearDraft(draftId);
|
|
37
|
+
setDraft(null);
|
|
38
|
+
};
|
|
39
|
+
// Plain elements styled with the editor's own tokens (editor.css): Mantine's
|
|
40
|
+
// theme variables are scoped to the portal target, so an in-flow Mantine
|
|
41
|
+
// button here would fall back to Mantine's default blue.
|
|
42
|
+
return (_jsxs("div", { className: "likhari-draft-banner", role: "status", "aria-live": "polite", children: [_jsxs("div", { className: "likhari-draft-banner-message", children: [_jsx(IconHistory, { size: 18, stroke: 1.75, "aria-hidden": "true" }), _jsx("span", { children: strings.drafts.bannerMessage(formatDraftTime(draft.savedAt, locale)) })] }), _jsxs("div", { className: "likhari-draft-banner-actions", children: [_jsx("button", { type: "button", className: "likhari-draft-banner-button", "data-variant": "primary", onClick: restore, children: strings.drafts.restore }), _jsx("button", { type: "button", className: "likhari-draft-banner-button", onClick: ignore, children: strings.drafts.ignore }), _jsx("button", { type: "button", className: "likhari-draft-banner-button", "data-variant": "danger", onClick: remove, children: strings.drafts.remove })] })] }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DraftEntry } from '../persistence/draftStorage.js';
|
|
2
|
+
import type { Locale } from '../i18n/index.js';
|
|
3
|
+
/** What the toolbar needs to offer the drafts list. */
|
|
4
|
+
export interface DraftsToolbarOptions {
|
|
5
|
+
/** The key this editor's own autosave writes to. */
|
|
6
|
+
currentId: string;
|
|
7
|
+
/** Size cap for archiving the content a restore replaces. */
|
|
8
|
+
maxBytes: number;
|
|
9
|
+
/** Without a `documentId`, a restored draft becomes this editor's draft, so later edits update it instead of duplicating it. */
|
|
10
|
+
adoptOnRestore: boolean;
|
|
11
|
+
onRestored: (entry: DraftEntry) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface DraftsDialogProps extends DraftsToolbarOptions {
|
|
14
|
+
opened: boolean;
|
|
15
|
+
locale: Locale;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Every autosaved draft in this browser (all documents, and the earlier
|
|
20
|
+
* versions kept when a restore prompt was ignored), newest first, each with
|
|
21
|
+
* Restore and Delete. Restoring over content that differs asks first, and keeps
|
|
22
|
+
* the replaced content as a draft of its own, so nothing is lost either way.
|
|
23
|
+
*/
|
|
24
|
+
export declare function DraftsDialog({ opened, locale, currentId, maxBytes, adoptOnRestore, onRestored, onClose }: DraftsDialogProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=DraftsDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DraftsDialog.d.ts","sourceRoot":"","sources":["../../src/components/DraftsDialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,UAAU,EAChB,MAAM,6BAA6B,CAAC;AAIrC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,gIAAgI;IAChI,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,iBAAkB,SAAQ,oBAAoB;IAC7D,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,iBAAiB,+BAmH3H"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Badge, Button, Group, Modal, Paper, Stack, Text } from '@mantine/core';
|
|
4
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
5
|
+
import { clearDraft, createDraftId, hasDraftContent, isGeneratedDraftId, listDrafts, writeDraft, } from '../persistence/draftStorage.js';
|
|
6
|
+
import { applyDraftToEditor, formatDraftTime } from '../persistence/draftUi.js';
|
|
7
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
8
|
+
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
9
|
+
/**
|
|
10
|
+
* Every autosaved draft in this browser (all documents, and the earlier
|
|
11
|
+
* versions kept when a restore prompt was ignored), newest first, each with
|
|
12
|
+
* Restore and Delete. Restoring over content that differs asks first, and keeps
|
|
13
|
+
* the replaced content as a draft of its own, so nothing is lost either way.
|
|
14
|
+
*/
|
|
15
|
+
export function DraftsDialog({ opened, locale, currentId, maxBytes, adoptOnRestore, onRestored, onClose }) {
|
|
16
|
+
const [editor] = useLexicalComposerContext();
|
|
17
|
+
const strings = useUiStrings();
|
|
18
|
+
const portalTarget = usePortalTarget();
|
|
19
|
+
const [entries, setEntries] = useState([]);
|
|
20
|
+
const [pending, setPending] = useState(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (opened) {
|
|
23
|
+
setEntries(listDrafts());
|
|
24
|
+
setPending(null);
|
|
25
|
+
}
|
|
26
|
+
}, [opened]);
|
|
27
|
+
const currentJson = () => JSON.stringify(editor.getEditorState().toJSON());
|
|
28
|
+
const restore = (entry) => {
|
|
29
|
+
const before = currentJson();
|
|
30
|
+
// Keep what is being replaced, as a draft of its own.
|
|
31
|
+
if (before !== entry.draft.json && hasDraftContent(before)) {
|
|
32
|
+
writeDraft(createDraftId(), before, maxBytes, currentId);
|
|
33
|
+
if (adoptOnRestore)
|
|
34
|
+
clearDraft(currentId);
|
|
35
|
+
}
|
|
36
|
+
if (applyDraftToEditor(editor, entry.draft.json)) {
|
|
37
|
+
onRestored(entry);
|
|
38
|
+
onClose();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const requestRestore = (entry) => {
|
|
42
|
+
const before = currentJson();
|
|
43
|
+
// Replacing real, different content is confirmed first (inline, not a window.confirm).
|
|
44
|
+
if (before !== entry.draft.json && hasDraftContent(before))
|
|
45
|
+
setPending({ id: entry.id, action: 'restore' });
|
|
46
|
+
else
|
|
47
|
+
restore(entry);
|
|
48
|
+
};
|
|
49
|
+
const remove = (entry) => {
|
|
50
|
+
clearDraft(entry.id);
|
|
51
|
+
setEntries(listDrafts());
|
|
52
|
+
setPending(null);
|
|
53
|
+
};
|
|
54
|
+
const label = (entry) => {
|
|
55
|
+
const source = entry.draft.sourceId;
|
|
56
|
+
if (source)
|
|
57
|
+
return isGeneratedDraftId(source) ? strings.drafts.earlierUntitled : strings.drafts.earlierVersionOf(source);
|
|
58
|
+
return isGeneratedDraftId(entry.id) ? strings.drafts.untitled : strings.drafts.documentLabel(entry.id);
|
|
59
|
+
};
|
|
60
|
+
return (_jsx(Modal, { opened: opened, onClose: onClose, title: strings.drafts.dialogTitle, centered: true, size: "lg", portalProps: portalTarget ? { target: portalTarget } : undefined, children: _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", c: "dimmed", children: strings.drafts.intro }), entries.length === 0 && _jsx(Text, { size: "sm", children: strings.drafts.empty }), entries.map((entry) => {
|
|
61
|
+
const isCurrent = entry.id === currentId;
|
|
62
|
+
const isPending = pending?.id === entry.id;
|
|
63
|
+
return (_jsx(Paper, { withBorder: true, p: "sm", radius: "sm", "data-draft-id": entry.id, children: _jsxs(Stack, { gap: 6, children: [_jsxs(Group, { justify: "space-between", wrap: "nowrap", align: "flex-start", children: [_jsx(Text, { size: "sm", lineClamp: 2, style: { flex: 1, wordBreak: 'break-word' }, children: entry.draft.preview || strings.drafts.noText }), isCurrent && _jsx(Badge, { variant: "light", children: strings.drafts.thisDocument })] }), _jsxs(Text, { size: "xs", c: "dimmed", children: [label(entry), " \u00B7 ", strings.drafts.savedAt(formatDraftTime(entry.draft.savedAt, locale)), " \u00B7", ' ', strings.drafts.size(Math.max(1, Math.round(entry.draft.json.length / 1024)))] }), !isCurrent &&
|
|
64
|
+
(isPending ? (_jsxs(Group, { gap: "xs", align: "center", children: [_jsx(Text, { size: "sm", children: pending.action === 'restore' ? strings.drafts.replacePrompt : strings.drafts.deletePrompt }), _jsx(Button, { size: "compact-sm", color: pending.action === 'delete' ? 'red' : undefined, onClick: () => (pending.action === 'restore' ? restore(entry) : remove(entry)), children: pending.action === 'restore' ? strings.drafts.replace : strings.drafts.delete }), _jsx(Button, { size: "compact-sm", variant: "default", onClick: () => setPending(null), children: strings.common.cancel })] })) : (_jsxs(Group, { gap: "xs", children: [_jsx(Button, { size: "compact-sm", onClick: () => requestRestore(entry), children: strings.drafts.restore }), _jsx(Button, { size: "compact-sm", variant: "subtle", color: "red", onClick: () => setPending({ id: entry.id, action: 'delete' }), children: strings.drafts.delete })] })))] }) }, entry.id));
|
|
65
|
+
}), _jsx(Group, { justify: "flex-end", children: _jsx(Button, { variant: "default", onClick: onClose, children: strings.drafts.close }) })] }) }));
|
|
66
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DraftsToolbarOptions } from './DraftsDialog.js';
|
|
1
2
|
import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
|
|
2
3
|
import { type FontOption } from '../fonts/index.js';
|
|
3
4
|
import type { Locale } from '../i18n/strings.js';
|
|
@@ -12,6 +13,8 @@ export interface ToolbarProps {
|
|
|
12
13
|
direction?: 'ltr' | 'rtl';
|
|
13
14
|
/** UI locale — picks the translated strings for labels, tooltips and menu items. */
|
|
14
15
|
locale?: Locale;
|
|
16
|
+
/** Autosave drafts: when set, the toolbar offers a Drafts button that lists and restores them. */
|
|
17
|
+
drafts?: DraftsToolbarOptions;
|
|
15
18
|
}
|
|
16
|
-
export declare function Toolbar({ config, onSave, isDirty, showSave, fontOptions, direction, locale }: ToolbarProps): import("react").JSX.Element;
|
|
19
|
+
export declare function Toolbar({ config, onSave, isDirty, showSave, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
|
|
17
20
|
//# sourceMappingURL=Toolbar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA2CA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AAsUvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAqxBxJ"}
|
|
@@ -10,15 +10,15 @@ import { $isListNode, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, IN
|
|
|
10
10
|
import { $findMatchingParent, $insertNodeToNearestRoot } from '@lexical/utils';
|
|
11
11
|
import { ImageDialog } from '../image/ImageDialog.js';
|
|
12
12
|
import { $createImageNode } from '../image/ImageNode.js';
|
|
13
|
-
import { INSERT_PAGE_BREAK_COMMAND } from '../blocks/PageBreakNode.js';
|
|
14
13
|
import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
|
|
15
14
|
import { LinkDialog } from './LinkDialog.js';
|
|
15
|
+
import { DraftsDialog } from './DraftsDialog.js';
|
|
16
16
|
import { normalizeLinkUrl } from '../utils/linkUrl.js';
|
|
17
17
|
import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fonts/index.js';
|
|
18
18
|
import { setStyleProperty } from '../utils/style.js';
|
|
19
19
|
import { useStrings } from '../i18n/useStrings.js';
|
|
20
20
|
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
21
|
-
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers,
|
|
21
|
+
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
22
22
|
/** Sentinel for "no inline style": Lexical returns the default it is given when
|
|
23
23
|
* the selection has none, so this tells "unstyled" (show the canvas default)
|
|
24
24
|
* apart from "styled with a value the dropdown doesn't list" (show nothing). */
|
|
@@ -120,11 +120,12 @@ function LinkMenuItems({ url, onEdit, onRemove, strings }) {
|
|
|
120
120
|
// Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
|
|
121
121
|
_jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url || strings.link.noUrl })), _jsx(Menu.Item, { leftSection: _jsx(IconPencil, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onEdit, children: strings.link.editLink }), _jsx(Menu.Item, { color: "red", leftSection: _jsx(IconLinkOff, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onRemove, children: strings.link.removeLink })] }));
|
|
122
122
|
}
|
|
123
|
-
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en' }) {
|
|
123
|
+
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
124
124
|
const [editor] = useLexicalComposerContext();
|
|
125
125
|
const [state, setState] = useState(INITIAL_STATE);
|
|
126
126
|
const strings = useStrings(locale);
|
|
127
127
|
const portalTarget = usePortalTarget();
|
|
128
|
+
const [draftsOpen, setDraftsOpen] = useState(false);
|
|
128
129
|
// Responsive "priority+" overflow: the toolbar's outer container and a
|
|
129
130
|
// hidden nowrap clone (rendered with every movable group forced inline)
|
|
130
131
|
// are both measured; whenever the clone's natural width exceeds the
|
|
@@ -427,7 +428,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
427
428
|
const showFormattingGroup = headingLevels.length > 0 || config.lists.bullet || config.lists.numbered || config.lists.check || config.blocks.quote;
|
|
428
429
|
const showInlineGroup = fmt.bold || fmt.italic || fmt.underline;
|
|
429
430
|
const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
|
|
430
|
-
const showStubInsertGroup = config.images.linked || config.images.embedded || config.
|
|
431
|
+
const showStubInsertGroup = config.images.linked || config.images.embedded || config.poetry.enabled;
|
|
431
432
|
const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
|
|
432
433
|
// The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
|
|
433
434
|
// as normal inline toolbar groups whenever there's room, and only move
|
|
@@ -585,6 +586,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
585
586
|
// the toolbar's own layout width.
|
|
586
587
|
const fixedGroups = [
|
|
587
588
|
showSave && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty), onClick: onSave }) }, "save")),
|
|
589
|
+
drafts && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarButton, { icon: IconHistory, title: strings.toolbar.drafts, onClick: () => setDraftsOpen(true) }) }, "drafts")),
|
|
588
590
|
config.history && (_jsxs("div", { className: "likhari-toolbar-group", children: [_jsx(ToolbarButton, { icon: IconArrowBackUp, title: strings.toolbar.undo, disabled: !state.canUndo, onClick: () => editor.dispatchCommand(UNDO_COMMAND, undefined) }), _jsx(ToolbarButton, { icon: IconArrowForwardUp, title: strings.toolbar.redo, disabled: !state.canRedo, onClick: () => editor.dispatchCommand(REDO_COMMAND, undefined) })] }, "history")),
|
|
589
591
|
showFormattingGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: IconPilcrow, label: strings.toolbar.formattingLabel, width: 148, value: formattingValue, data: formattingOptions, comingSoon: strings.toolbar.comingSoon, renderOption: iconOptionRenderer(FORMATTING_ICONS, IconPilcrow), onChange: withRefocus((v) => applyFormatting(v)) }) }, "formatting")),
|
|
590
592
|
showInlineGroup && (_jsxs("div", { className: "likhari-toolbar-group", children: [fmt.bold && (_jsx(ToolbarButton, { icon: IconBold, title: strings.toolbar.bold, active: state.activeFormats.has('bold'), onClick: () => formatText('bold') })), fmt.italic && (_jsx(ToolbarButton, { icon: IconItalic, title: strings.toolbar.italic, active: state.activeFormats.has('italic'), onClick: () => formatText('italic') })), fmt.underline && (_jsx(ToolbarButton, { icon: IconUnderline, title: strings.toolbar.underline, active: state.activeFormats.has('underline'), onClick: () => formatText('underline') }))] }, "inline")),
|
|
@@ -596,7 +598,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
596
598
|
restoreSelection();
|
|
597
599
|
openLinkDialog();
|
|
598
600
|
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
|
|
599
|
-
showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.
|
|
601
|
+
showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
|
|
600
602
|
showLanguageGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.language.autocorrect && _jsx(StubButton, { icon: IconWand, title: strings.toolbar.autocorrect, comingSoon: strings.toolbar.comingSoon }), config.language.textCleanup && _jsx(StubButton, { icon: IconSparkles, title: strings.toolbar.textCleanup, comingSoon: strings.toolbar.comingSoon }), config.language.spellCheck && _jsx(StubButton, { icon: IconAbc, title: strings.toolbar.spellChecker, comingSoon: strings.toolbar.comingSoon })] }, "language")),
|
|
601
603
|
];
|
|
602
604
|
return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
|
|
@@ -618,5 +620,5 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
618
620
|
display: 'flex',
|
|
619
621
|
flexWrap: 'nowrap',
|
|
620
622
|
gap: 8,
|
|
621
|
-
}, children: [fixedGroups, movableGroups.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key)))] }), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog }))] }));
|
|
623
|
+
}, children: [fixedGroups, movableGroups.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key)))] }), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
|
|
622
624
|
}
|
package/dist/i18n/strings.d.ts
CHANGED
|
@@ -15,8 +15,6 @@ export interface Strings {
|
|
|
15
15
|
contentLabel: string;
|
|
16
16
|
/** window.confirm() prompt shown by EditorRef.confirmDiscard() when there are unsaved changes. */
|
|
17
17
|
confirmDiscard: string;
|
|
18
|
-
/** Label shown on a page-break marker's dashed line while editing. */
|
|
19
|
-
pageBreakLabel: string;
|
|
20
18
|
};
|
|
21
19
|
common: {
|
|
22
20
|
cancel: string;
|
|
@@ -27,6 +25,8 @@ export interface Strings {
|
|
|
27
25
|
/** aria-label on the toolbar's own role="toolbar" container. */
|
|
28
26
|
ariaLabel: string;
|
|
29
27
|
save: string;
|
|
28
|
+
/** Opens the list of autosaved drafts. */
|
|
29
|
+
drafts: string;
|
|
30
30
|
undo: string;
|
|
31
31
|
redo: string;
|
|
32
32
|
formattingLabel: string;
|
|
@@ -42,7 +42,6 @@ export interface Strings {
|
|
|
42
42
|
linkOptions: string;
|
|
43
43
|
insertLink: string;
|
|
44
44
|
insertImage: string;
|
|
45
|
-
insertPageBreak: string;
|
|
46
45
|
poetryBlocks: string;
|
|
47
46
|
comingSoon: (label: string) => string;
|
|
48
47
|
autocorrect: string;
|
|
@@ -75,6 +74,31 @@ export interface Strings {
|
|
|
75
74
|
right: string;
|
|
76
75
|
};
|
|
77
76
|
};
|
|
77
|
+
/** Autosave drafts (lexical-editor-spec.md §6.2): the restore banner and the drafts list dialog. */
|
|
78
|
+
drafts: {
|
|
79
|
+
/** Banner shown when a newer unsaved draft of this document is found; `when` is a formatted date/time. */
|
|
80
|
+
bannerMessage: (when: string) => string;
|
|
81
|
+
restore: string;
|
|
82
|
+
ignore: string;
|
|
83
|
+
remove: string;
|
|
84
|
+
dialogTitle: string;
|
|
85
|
+
intro: string;
|
|
86
|
+
empty: string;
|
|
87
|
+
/** Shown for a draft with no text (e.g. only an image). */
|
|
88
|
+
noText: string;
|
|
89
|
+
thisDocument: string;
|
|
90
|
+
untitled: string;
|
|
91
|
+
documentLabel: (id: string) => string;
|
|
92
|
+
earlierUntitled: string;
|
|
93
|
+
earlierVersionOf: (id: string) => string;
|
|
94
|
+
savedAt: (when: string) => string;
|
|
95
|
+
size: (kb: number) => string;
|
|
96
|
+
replacePrompt: string;
|
|
97
|
+
replace: string;
|
|
98
|
+
deletePrompt: string;
|
|
99
|
+
delete: string;
|
|
100
|
+
close: string;
|
|
101
|
+
};
|
|
78
102
|
link: {
|
|
79
103
|
menuLabel: string;
|
|
80
104
|
noUrl: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;+DAC+D;AAC/D,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAElD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE;QACN,mGAAmG;QACnG,WAAW,EAAE,MAAM,CAAC;QACpB,gDAAgD;QAChD,YAAY,EAAE,MAAM,CAAC;QACrB,kGAAkG;QAClG,cAAc,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
|