@inshapardaz/likhari-react 0.1.7-dev.79 → 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.
@@ -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;
@@ -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;AAW1D,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;AAIjE,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEnE,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;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,uGAuKrB,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"}
@@ -7,7 +7,6 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
7
7
  import { ListPlugin } from '@lexical/react/LexicalListPlugin';
8
8
  import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
9
9
  import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
10
- import { TablePlugin } from '@lexical/react/LexicalTablePlugin';
11
10
  import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
12
11
  import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
13
12
  import { defaultFormatRegistry } from '@inshapardaz/likhari-converters';
@@ -18,19 +17,30 @@ import { EditorThemeProvider } from './theme/EditorThemeProvider.js';
18
17
  import { Toolbar } from './components/Toolbar.js';
19
18
  import { injectUrduWebFontsCss } from './fonts/index.js';
20
19
  import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
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: initialEditorStateJson(initialContent),
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 }), _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, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: false, hasTabHandler: true }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }) }) })] }) }));
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":"AA8CA,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;CACjB;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,EAAE,YAAY,+BAuyBhJ"}
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,16 +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 { TableDialog } from './TableDialog.js';
14
- import { INSERT_TABLE_COMMAND } from '@lexical/table';
15
13
  import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
16
14
  import { LinkDialog } from './LinkDialog.js';
15
+ import { DraftsDialog } from './DraftsDialog.js';
17
16
  import { normalizeLinkUrl } from '../utils/linkUrl.js';
18
17
  import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fonts/index.js';
19
18
  import { setStyleProperty } from '../utils/style.js';
20
19
  import { useStrings } from '../i18n/useStrings.js';
21
20
  import { usePortalTarget } from '../PortalTargetContext.js';
22
- 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, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTable, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
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';
23
22
  /** Sentinel for "no inline style": Lexical returns the default it is given when
24
23
  * the selection has none, so this tells "unstyled" (show the canvas default)
25
24
  * apart from "styled with a value the dropdown doesn't list" (show nothing). */
@@ -121,11 +120,12 @@ function LinkMenuItems({ url, onEdit, onRemove, strings }) {
121
120
  // Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
122
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 })] }));
123
122
  }
124
- 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 }) {
125
124
  const [editor] = useLexicalComposerContext();
126
125
  const [state, setState] = useState(INITIAL_STATE);
127
126
  const strings = useStrings(locale);
128
127
  const portalTarget = usePortalTarget();
128
+ const [draftsOpen, setDraftsOpen] = useState(false);
129
129
  // Responsive "priority+" overflow: the toolbar's outer container and a
130
130
  // hidden nowrap clone (rendered with every movable group forced inline)
131
131
  // are both measured; whenever the clone's natural width exceeds the
@@ -275,26 +275,6 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
275
275
  });
276
276
  closeImageDialog();
277
277
  };
278
- const [tableDialogOpen, setTableDialogOpen] = useState(false);
279
- const openTableDialog = () => {
280
- snapshotSelection();
281
- setTableDialogOpen(true);
282
- };
283
- const closeTableDialog = () => {
284
- setTableDialogOpen(false);
285
- editor.focus();
286
- };
287
- const insertTable = ({ rows, columns, headerRow }) => {
288
- const saved = menuSelectionRef.current;
289
- if (saved)
290
- editor.update(() => $setSelection(saved.clone()), { discrete: true });
291
- editor.dispatchCommand(INSERT_TABLE_COMMAND, {
292
- rows: String(rows),
293
- columns: String(columns),
294
- includeHeaders: { rows: headerRow, columns: false },
295
- });
296
- closeTableDialog();
297
- };
298
278
  /** Applies a font property per `config.font.scope`: to the selection, to
299
279
  * every text node in the document, or (for 'both') to the selection when
300
280
  * there is a range and to the whole document when there isn't. */
@@ -448,7 +428,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
448
428
  const showFormattingGroup = headingLevels.length > 0 || config.lists.bullet || config.lists.numbered || config.lists.check || config.blocks.quote;
449
429
  const showInlineGroup = fmt.bold || fmt.italic || fmt.underline;
450
430
  const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
451
- const showStubInsertGroup = config.images.linked || config.images.embedded || config.tables || config.poetry.enabled;
431
+ const showStubInsertGroup = config.images.linked || config.images.embedded || config.poetry.enabled;
452
432
  const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
453
433
  // The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
454
434
  // as normal inline toolbar groups whenever there's room, and only move
@@ -606,6 +586,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
606
586
  // the toolbar's own layout width.
607
587
  const fixedGroups = [
608
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")),
609
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")),
610
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")),
611
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")),
@@ -617,7 +598,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
617
598
  restoreSelection();
618
599
  openLinkDialog();
619
600
  }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
620
- 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.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
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")),
621
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")),
622
603
  ];
623
604
  return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
@@ -639,5 +620,5 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
639
620
  display: 'flex',
640
621
  flexWrap: 'nowrap',
641
622
  gap: 8,
642
- }, 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.tables && _jsx(TableDialog, { opened: tableDialogOpen, onSubmit: insertTable, onClose: closeTableDialog }), 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 })] }));
643
624
  }
@@ -25,6 +25,8 @@ export interface Strings {
25
25
  /** aria-label on the toolbar's own role="toolbar" container. */
26
26
  ariaLabel: string;
27
27
  save: string;
28
+ /** Opens the list of autosaved drafts. */
29
+ drafts: string;
28
30
  undo: string;
29
31
  redo: string;
30
32
  formattingLabel: string;
@@ -40,7 +42,6 @@ export interface Strings {
40
42
  linkOptions: string;
41
43
  insertLink: string;
42
44
  insertImage: string;
43
- insertTable: string;
44
45
  poetryBlocks: string;
45
46
  comingSoon: (label: string) => string;
46
47
  autocorrect: string;
@@ -73,6 +74,31 @@ export interface Strings {
73
74
  right: string;
74
75
  };
75
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
+ };
76
102
  link: {
77
103
  menuLabel: string;
78
104
  noUrl: string;
@@ -89,12 +115,6 @@ export interface Strings {
89
115
  invalidUrl: string;
90
116
  apply: string;
91
117
  };
92
- tableDialog: {
93
- title: string;
94
- rows: string;
95
- columns: string;
96
- headerRow: string;
97
- };
98
118
  imageDialog: {
99
119
  titleInsert: string;
100
120
  titleEdit: 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;KACxB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE;QACP,gEAAgE;QAChE,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;QACtC,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE;YACjB,SAAS,EAAE,MAAM,CAAC;YAClB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;YACnC,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,MAAM,CAAC;YACnB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,YAAY,EAAE;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,GAAG,EAAE,MAAM,CAAC;YACZ,OAAO,EAAE,MAAM,CAAC;YAChB,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;IACF,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,MAAM,CAAC;QAC1E,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,MAAM,EAAE;YACN,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;YACrB,eAAe,EAAE,MAAM,CAAC;YACxB,gBAAgB,EAAE,MAAM,CAAC;YACzB,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,eAAe,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,CAAC,OAAO,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,KAAK,MAAM,CAAC;QACxE,MAAM,EAAE;YACN,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,SAAS,EAAE;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAqbD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAI3C,CAAC;AAEF;;sDAEsD;AACtD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD"}
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;KACxB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE;QACP,gEAAgE;QAChE,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,0CAA0C;QAC1C,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;QACtC,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE;YACjB,SAAS,EAAE,MAAM,CAAC;YAClB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;YACnC,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,MAAM,CAAC;YACnB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,YAAY,EAAE;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,GAAG,EAAE,MAAM,CAAC;YACZ,OAAO,EAAE,MAAM,CAAC;YAChB,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;IACF,oGAAoG;IACpG,MAAM,EAAE;QACN,0GAA0G;QAC1G,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;QACxC,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,2DAA2D;QAC3D,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QACtC,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QACzC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;QAClC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;QAC7B,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,MAAM,CAAC;QAC1E,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,MAAM,EAAE;YACN,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;YACrB,eAAe,EAAE,MAAM,CAAC;YACxB,gBAAgB,EAAE,MAAM,CAAC;YACzB,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,eAAe,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,CAAC,OAAO,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,KAAK,MAAM,CAAC;QACxE,MAAM,EAAE;YACN,cAAc,EAAE,MAAM,CAAC;YACvB,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,SAAS,EAAE;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAqeD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAI3C,CAAC;AAEF;;sDAEsD;AACtD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD"}