@inshapardaz/likhari-react 0.1.7-dev.88 → 0.1.8

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.
@@ -8,6 +8,7 @@ const en = {
8
8
  editor: {
9
9
  placeholder: 'Start writing…',
10
10
  contentLabel: 'Editor content',
11
+ confirmDiscard: 'You have unsaved changes. Discard them?',
11
12
  },
12
13
  common: {
13
14
  cancel: 'Cancel',
@@ -17,7 +18,6 @@ const en = {
17
18
  toolbar: {
18
19
  ariaLabel: 'Formatting',
19
20
  save: 'Save',
20
- drafts: 'Drafts',
21
21
  undo: 'Undo',
22
22
  redo: 'Redo',
23
23
  formattingLabel: 'Formatting',
@@ -65,37 +65,6 @@ const en = {
65
65
  right: 'Align right',
66
66
  },
67
67
  },
68
- leaveDialog: {
69
- title: 'Unsaved changes',
70
- message: 'You have unsaved changes. What would you like to do before leaving?',
71
- save: 'Save',
72
- saveDraft: 'Save draft',
73
- discard: 'Discard changes',
74
- stay: 'Stay',
75
- saveDraftFailed: "The draft couldn't be saved (the document may be too large, or browser storage is unavailable).",
76
- },
77
- drafts: {
78
- bannerMessage: (when) => `An unsaved draft of this document from ${when} was found.`,
79
- restore: 'Restore',
80
- ignore: 'Ignore',
81
- remove: 'Remove draft',
82
- dialogTitle: 'Saved drafts',
83
- intro: 'Drafts are saved in this browser as you type, and removed when you save.',
84
- empty: 'No drafts yet.',
85
- noText: '(no text)',
86
- thisDocument: 'Current document',
87
- untitled: 'Untitled draft',
88
- documentLabel: (id) => `Document “${id}”`,
89
- earlierUntitled: 'Earlier version of an untitled draft',
90
- earlierVersionOf: (id) => `Earlier version of “${id}”`,
91
- savedAt: (when) => `Saved ${when}`,
92
- size: (kb) => `${kb} KB`,
93
- replacePrompt: 'Replace the current content with this draft?',
94
- replace: 'Replace',
95
- deletePrompt: 'Delete this draft?',
96
- delete: 'Delete',
97
- close: 'Close',
98
- },
99
68
  link: {
100
69
  menuLabel: 'Link',
101
70
  noUrl: '(no URL)',
@@ -175,6 +144,7 @@ const ur = {
175
144
  editor: {
176
145
  placeholder: 'لکھنا شروع کریں…',
177
146
  contentLabel: 'ایڈیٹر کا مواد',
147
+ confirmDiscard: 'آپ کی غیر محفوظ شدہ تبدیلیاں ہیں۔ کیا انہیں رد کر دیا جائے؟',
178
148
  },
179
149
  common: {
180
150
  cancel: 'منسوخ کریں',
@@ -184,7 +154,6 @@ const ur = {
184
154
  toolbar: {
185
155
  ariaLabel: 'فارمیٹنگ',
186
156
  save: 'محفوظ کریں',
187
- drafts: 'مسودے',
188
157
  undo: 'کالعدم کریں',
189
158
  redo: 'دوبارہ کریں',
190
159
  formattingLabel: 'فارمیٹنگ',
@@ -232,37 +201,6 @@ const ur = {
232
201
  right: 'دائیں سیدھ',
233
202
  },
234
203
  },
235
- leaveDialog: {
236
- title: 'غیر محفوظ شدہ تبدیلیاں',
237
- message: 'آپ کی تبدیلیاں محفوظ نہیں ہوئیں۔ جانے سے پہلے آپ کیا کرنا چاہیں گے؟',
238
- save: 'محفوظ کریں',
239
- saveDraft: 'مسودہ محفوظ کریں',
240
- discard: 'تبدیلیاں رد کریں',
241
- stay: 'یہیں رہیں',
242
- saveDraftFailed: 'مسودہ محفوظ نہیں ہو سکا (دستاویز بہت بڑی ہو سکتی ہے، یا براؤزر کا اسٹوریج دستیاب نہیں)۔',
243
- },
244
- drafts: {
245
- bannerMessage: (when) => `اس دستاویز کا ایک غیر محفوظ شدہ مسودہ (${when}) ملا ہے۔`,
246
- restore: 'بحال کریں',
247
- ignore: 'نظر انداز کریں',
248
- remove: 'مسودہ ہٹائیں',
249
- dialogTitle: 'محفوظ شدہ مسودے',
250
- intro: 'لکھتے وقت مسودے اس براؤزر میں خود بخود محفوظ ہوتے ہیں اور محفوظ کرنے پر ہٹا دیے جاتے ہیں۔',
251
- empty: 'ابھی کوئی مسودہ نہیں۔',
252
- noText: '(کوئی متن نہیں)',
253
- thisDocument: 'موجودہ دستاویز',
254
- untitled: 'بلا عنوان مسودہ',
255
- documentLabel: (id) => `دستاویز «${id}»`,
256
- earlierUntitled: 'ایک بلا عنوان مسودے کا پرانا ورژن',
257
- earlierVersionOf: (id) => `«${id}» کا پرانا ورژن`,
258
- savedAt: (when) => `محفوظ شدہ: ${when}`,
259
- size: (kb) => `${kb} کے بی`,
260
- replacePrompt: 'موجودہ مواد کو اس مسودے سے بدل دیں؟',
261
- replace: 'بدل دیں',
262
- deletePrompt: 'یہ مسودہ حذف کریں؟',
263
- delete: 'حذف کریں',
264
- close: 'بند کریں',
265
- },
266
204
  link: {
267
205
  menuLabel: 'لنک',
268
206
  noUrl: '(کوئی یو آر ایل نہیں)',
@@ -342,6 +280,7 @@ const paShahmukhi = {
342
280
  editor: {
343
281
  placeholder: 'لکھنا شروع کرو…',
344
282
  contentLabel: 'ایڈیٹر دی سامگری',
283
+ confirmDiscard: 'تہاڈیاں کجھ تبدیلیاں سنبھالیاں نئیں گئیاں۔ کی ایہناں نوں رد کر دیئے؟',
345
284
  },
346
285
  common: {
347
286
  cancel: 'رد کرو',
@@ -351,7 +290,6 @@ const paShahmukhi = {
351
290
  toolbar: {
352
291
  ariaLabel: 'فارمیٹنگ',
353
292
  save: 'سنبھالو',
354
- drafts: 'سودھے',
355
293
  undo: 'پہلاں جیہا کرو',
356
294
  redo: 'مُڑ کرو',
357
295
  formattingLabel: 'فارمیٹنگ',
@@ -399,37 +337,6 @@ const paShahmukhi = {
399
337
  right: 'سجے سیدھ',
400
338
  },
401
339
  },
402
- leaveDialog: {
403
- title: 'غیر محفوظ تبدیلیاں',
404
- message: 'تہاڈیاں تبدیلیاں سنبھالیاں نئیں گئیاں۔ جان توں پہلاں تسی کی کرنا چاہو گے؟',
405
- save: 'سنبھالو',
406
- saveDraft: 'سودھا سنبھالو',
407
- discard: 'تبدیلیاں رد کرو',
408
- stay: 'ایتھے ای رہو',
409
- saveDraftFailed: 'سودھا سنبھالیا نئیں جا سکیا (دستاویز بہت وڈی ہو سکدی اے، یا براؤزر دی سٹوریج نئیں اے)۔',
410
- },
411
- drafts: {
412
- bannerMessage: (when) => `ایس دستاویز دا اک غیر محفوظ سودھا (${when}) لبھیا اے۔`,
413
- restore: 'بحال کرو',
414
- ignore: 'نظر انداز کرو',
415
- remove: 'سودھا ہٹاؤ',
416
- dialogTitle: 'سنبھالے ہوئے سودھے',
417
- intro: 'لکھدیاں ہویاں سودھے ایس براؤزر وچ آپ سنبھلدے نیں تے سنبھالن تے ہٹ جاندے نیں۔',
418
- empty: 'ہلے کوئی سودھا نئیں۔',
419
- noText: '(کوئی متن نئیں)',
420
- thisDocument: 'موجودہ دستاویز',
421
- untitled: 'بنا ناں دا سودھا',
422
- documentLabel: (id) => `دستاویز «${id}»`,
423
- earlierUntitled: 'اک بنا ناں دے سودھے دا پرانا ورژن',
424
- earlierVersionOf: (id) => `«${id}» دا پرانا ورژن`,
425
- savedAt: (when) => `سنبھالیا: ${when}`,
426
- size: (kb) => `${kb} کے بی`,
427
- replacePrompt: 'موجودہ مواد نوں ایس سودھے نال بدل دیئے؟',
428
- replace: 'بدل دیو',
429
- deletePrompt: 'ایہ سودھا مٹا دیئے؟',
430
- delete: 'مٹاؤ',
431
- close: 'بند کرو',
432
- },
433
340
  link: {
434
341
  menuLabel: 'لنک',
435
342
  noUrl: '(کوئی یو آر ایل نئیں)',
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { EditorRoot } from './EditorRoot.js';
2
- export type { EditorRootProps, EditorRef, EditorInitialContent, NavigationGuardMode } from './EditorRoot.js';
3
- export type { DraftRestoreMode } from './components/DraftRestore.js';
2
+ export type { EditorRootProps, EditorRef, EditorInitialContent } from './EditorRoot.js';
4
3
  export { defaultMantineTheme } from './theme/mantineTheme.js';
5
4
  export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
6
5
  export type { ImagePayload, ImageLinkType, SerializedImageNode } from './image/ImageNode.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { type MantineThemeOverride } from '@mantine/core';
2
- import '@mantine/core/styles.css';
3
2
  import './editor.css';
4
3
  export interface EditorThemeProviderProps {
5
4
  theme?: MantineThemeOverride;
@@ -1 +1 @@
1
- {"version":3,"file":"EditorThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/EditorThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,0BAA0B,CAAC;AAIlC,OAAO,cAAc,CAAC;AAsBtB,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;iFAC6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BA+C1H"}
1
+ {"version":3,"file":"EditorThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/EditorThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAO3E,OAAO,cAAc,CAAC;AAsBtB,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;iFAC6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BA+C1H"}
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { MantineProvider } from '@mantine/core';
4
- import '@mantine/core/styles.css';
4
+ // Mantine is a peer dependency (docs/mantine-headless-theming.md §4 rule 1):
5
+ // the host imports '@mantine/core/styles.css' itself, once, in its own entry
6
+ // point, so it controls load order. This package must not import it itself.
5
7
  import { buildThemeCss } from '@inshapardaz/likhari-core';
6
8
  import { defaultMantineTheme } from './mantineTheme.js';
7
9
  import { generateAccentCssVars, generateAccentShades } from './accentColor.js';
@@ -370,77 +370,3 @@
370
370
  text-overflow: ellipsis;
371
371
  white-space: nowrap;
372
372
  }
373
-
374
- /* Restore-draft banner: a strip between the toolbar and the canvas. Uses the
375
- editor's own tokens, so it follows the accent color and dark mode. */
376
- .likhari-draft-banner {
377
- display: flex;
378
- flex-shrink: 0;
379
- flex-wrap: wrap;
380
- align-items: center;
381
- gap: 8px 12px;
382
- padding: 8px 12px;
383
- background: var(--editor-accent-soft);
384
- border-bottom: 1px solid var(--editor-line);
385
- color: var(--editor-ink);
386
- font-size: 13px;
387
- }
388
-
389
- .likhari-draft-banner-message {
390
- display: flex;
391
- flex: 1 1 240px;
392
- align-items: center;
393
- gap: 8px;
394
- }
395
-
396
- .likhari-draft-banner-message svg {
397
- flex-shrink: 0;
398
- color: var(--editor-accent);
399
- }
400
-
401
- .likhari-draft-banner-actions {
402
- display: flex;
403
- flex-wrap: wrap;
404
- gap: 6px;
405
- }
406
-
407
- .likhari-draft-banner-button {
408
- height: 28px;
409
- padding: 0 12px;
410
- border: 1px solid var(--editor-line-strong);
411
- border-radius: 6px;
412
- background: var(--editor-paper);
413
- color: var(--editor-ink);
414
- font: inherit;
415
- font-weight: 500;
416
- cursor: pointer;
417
- }
418
-
419
- .likhari-draft-banner-button:hover {
420
- background: var(--editor-paper-dim);
421
- }
422
-
423
- .likhari-draft-banner-button[data-variant='primary'] {
424
- border-color: var(--editor-accent);
425
- background: var(--editor-accent);
426
- color: var(--editor-paper);
427
- }
428
-
429
- .likhari-draft-banner-button[data-variant='primary']:hover {
430
- filter: brightness(1.1);
431
- }
432
-
433
- .likhari-draft-banner-button[data-variant='danger'] {
434
- border-color: transparent;
435
- background: transparent;
436
- color: var(--editor-danger);
437
- }
438
-
439
- .likhari-draft-banner-button[data-variant='danger']:hover {
440
- background: var(--editor-paper-dim);
441
- }
442
-
443
- .likhari-draft-banner-button:focus-visible {
444
- outline: 2px solid var(--editor-accent);
445
- outline-offset: 1px;
446
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.7-dev.88",
3
+ "version": "0.1.8",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,6 +16,7 @@
16
16
  "development": "./src/index.ts",
17
17
  "import": "./dist/index.js"
18
18
  },
19
+ "./styles.css": "./dist/theme/editor.css",
19
20
  "./package.json": "./package.json"
20
21
  },
21
22
  "files": [
@@ -40,13 +41,17 @@
40
41
  "@lexical/utils": "^0.20.0",
41
42
  "@inshapardaz/likhari-converters": "^0.1.0",
42
43
  "@inshapardaz/likhari-core": "^0.1.0",
43
- "@mantine/core": "^9.6.3",
44
- "@mantine/hooks": "^9.6.3",
45
44
  "@tabler/icons-react": "^3.46.0",
46
45
  "lexical": "^0.20.0"
47
46
  },
48
47
  "peerDependencies": {
48
+ "@mantine/core": "^9.6.3",
49
+ "@mantine/hooks": "^9.6.3",
49
50
  "react": "^19.2.0",
50
51
  "react-dom": "^19.2.0"
52
+ },
53
+ "devDependencies": {
54
+ "@mantine/core": "^9.6.3",
55
+ "@mantine/hooks": "^9.6.3"
51
56
  }
52
57
  }
@@ -1,24 +0,0 @@
1
- import { type DraftEntry } from '../persistence/draftStorage.js';
2
- import type { Locale } from '../i18n/index.js';
3
- /** What to do on mount with a stored draft of this document that is newer than the initial content. */
4
- export type DraftRestoreMode = 'prompt' | 'auto' | 'off';
5
- export interface DraftRestoreProps {
6
- /** The host's `documentId` — the key the draft is stored under. */
7
- draftId: string;
8
- mode: DraftRestoreMode;
9
- /** The Lexical JSON the editor started with; a draft identical to it isn't offered. */
10
- initialJson?: string;
11
- locale: Locale;
12
- onRestored: (entry: DraftEntry) => void;
13
- }
14
- /**
15
- * On mount, handles a draft of this document newer than the initial content
16
- * (lexical-editor-spec.md §6.2), per `mode`:
17
- * - `prompt` (default): a banner — Restore, Ignore (kept in the drafts list,
18
- * since new edits would otherwise overwrite it) or Remove. Never picks silently.
19
- * - `auto`: loads the draft straight away, no banner.
20
- * - `off`: leaves the editor on its initial content, but moves the draft aside
21
- * as an earlier version so new edits can't overwrite it unseen.
22
- */
23
- export declare function DraftRestore({ draftId, mode, initialJson, locale, onRestored }: DraftRestoreProps): import("react").JSX.Element | null;
24
- //# sourceMappingURL=DraftRestore.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DraftRestore.d.ts","sourceRoot":"","sources":["../../src/components/DraftRestore.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAwD,KAAK,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AAGtI,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,uGAAuG;AACvG,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;IACvB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,sCA8DjG"}
@@ -1,61 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, 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
- * On mount, handles a draft of this document newer than the initial content
10
- * (lexical-editor-spec.md §6.2), per `mode`:
11
- * - `prompt` (default): a banner — Restore, Ignore (kept in the drafts list,
12
- * since new edits would otherwise overwrite it) or Remove. Never picks silently.
13
- * - `auto`: loads the draft straight away, no banner.
14
- * - `off`: leaves the editor on its initial content, but moves the draft aside
15
- * as an earlier version so new edits can't overwrite it unseen.
16
- */
17
- export function DraftRestore({ draftId, mode, initialJson, locale, onRestored }) {
18
- const [editor] = useLexicalComposerContext();
19
- const strings = useUiStrings();
20
- const [draft, setDraft] = useState(() => {
21
- const stored = readDraft(draftId);
22
- return stored && hasDraftContent(stored.json) && stored.json !== initialJson ? stored : null;
23
- });
24
- const handled = useRef(false);
25
- const restore = () => {
26
- if (!draft)
27
- return;
28
- if (applyDraftToEditor(editor, draft.json))
29
- onRestored({ id: draftId, draft });
30
- // A corrupt or incompatible draft can't be restored: drop the banner and keep the initial content.
31
- setDraft(null);
32
- };
33
- const ignore = () => {
34
- // Move it aside, so edits from here on can't overwrite it unseen.
35
- if (archiveDraft(draftId))
36
- clearDraft(draftId);
37
- setDraft(null);
38
- };
39
- // Non-interactive modes act once, on mount.
40
- useEffect(() => {
41
- if (handled.current || !draft)
42
- return;
43
- handled.current = true;
44
- if (mode === 'auto')
45
- restore();
46
- else if (mode === 'off')
47
- ignore();
48
- // Deliberately runs once on mount only.
49
- // eslint-disable-next-line react-hooks/exhaustive-deps
50
- }, []);
51
- const remove = () => {
52
- clearDraft(draftId);
53
- setDraft(null);
54
- };
55
- if (!draft || mode !== 'prompt')
56
- return null;
57
- // Plain elements styled with the editor's own tokens (editor.css): Mantine's
58
- // theme variables are scoped to the portal target, so an in-flow Mantine
59
- // button here would fall back to Mantine's default blue.
60
- 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 })] })] }));
61
- }
@@ -1,25 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,66 +0,0 @@
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,22 +0,0 @@
1
- export interface LeaveDialogProps {
2
- opened: boolean;
3
- /** Offer "Save" (calls the host's `onSave`, then lets the user leave). */
4
- canSave: boolean;
5
- /** Offer "Save draft" (keeps the work in this browser, then lets the user leave). */
6
- canSaveDraft: boolean;
7
- /** A "Save draft" attempt failed; explains why the dialog is still open. */
8
- draftFailed: boolean;
9
- onSave: () => void;
10
- onSaveDraft: () => void;
11
- onDiscard: () => void;
12
- /** Stay on the page (also Escape, the close button, and clicking outside). */
13
- onStay: () => void;
14
- }
15
- /**
16
- * The in-editor popup behind `EditorRef.confirmDiscard()`: what to do with
17
- * unsaved changes before leaving. It replaces `window.confirm()`, which can't
18
- * be styled, translated per editor, or offer more than OK/Cancel. (The browser's
19
- * own tab-close prompt can't be replaced — see `navigationGuard`.)
20
- */
21
- export declare function LeaveDialog({ opened, canSave, canSaveDraft, draftFailed, onSave, onSaveDraft, onDiscard, onStay }: LeaveDialogProps): import("react").JSX.Element;
22
- //# sourceMappingURL=LeaveDialog.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LeaveDialog.d.ts","sourceRoot":"","sources":["../../src/components/LeaveDialog.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,YAAY,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,gBAAgB,+BAoCnI"}
@@ -1,15 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Alert, Button, Group, Modal, Stack, Text } from '@mantine/core';
3
- import { useUiStrings } from '../i18n/useStrings.js';
4
- import { usePortalTarget } from '../PortalTargetContext.js';
5
- /**
6
- * The in-editor popup behind `EditorRef.confirmDiscard()`: what to do with
7
- * unsaved changes before leaving. It replaces `window.confirm()`, which can't
8
- * be styled, translated per editor, or offer more than OK/Cancel. (The browser's
9
- * own tab-close prompt can't be replaced — see `navigationGuard`.)
10
- */
11
- export function LeaveDialog({ opened, canSave, canSaveDraft, draftFailed, onSave, onSaveDraft, onDiscard, onStay }) {
12
- const strings = useUiStrings();
13
- const portalTarget = usePortalTarget();
14
- return (_jsx(Modal, { opened: opened, onClose: onStay, title: strings.leaveDialog.title, centered: true, size: "md", portalProps: portalTarget ? { target: portalTarget } : undefined, children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "sm", children: strings.leaveDialog.message }), draftFailed && (_jsx(Alert, { color: "red", variant: "light", p: "xs", children: strings.leaveDialog.saveDraftFailed })), _jsxs(Group, { justify: "flex-end", gap: "xs", wrap: "wrap", children: [_jsx(Button, { variant: "default", onClick: onStay, "data-autofocus": true, children: strings.leaveDialog.stay }), _jsx(Button, { variant: "subtle", color: "red", onClick: onDiscard, children: strings.leaveDialog.discard }), canSaveDraft && (_jsx(Button, { variant: "default", onClick: onSaveDraft, children: strings.leaveDialog.saveDraft })), canSave && _jsx(Button, { onClick: onSave, children: strings.leaveDialog.save })] })] }) }));
15
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * Autosave-to-localStorage drafts (lexical-editor-spec.md §6.2). Each draft is
3
- * keyed by an id: the host's `documentId` when it gives one (§6.1), otherwise
4
- * a generated unique id, so editors without a `documentId` never overwrite each
5
- * other's drafts. Every function is a no-op (never throws) when `localStorage`
6
- * is unavailable (SSR, privacy mode) or a stored value is corrupt — autosave is
7
- * best-effort, never a hard dependency.
8
- */
9
- export interface StoredDraft {
10
- /** Lexical JSON (as produced by `EditorState.toJSON()`, stringified). */
11
- json: string;
12
- /** `Date.now()` at write time — lets a host distinguish a stale draft from a genuinely recent one. */
13
- savedAt: number;
14
- /** Bumped only if the stored shape itself changes; lets old drafts be recognised/discarded across an upgrade. */
15
- version: 1;
16
- /** First words of the text, for listing drafts without parsing every document. */
17
- preview?: string;
18
- /** Set on a draft archived from `documentId` (the id it was an earlier version of). */
19
- sourceId?: string;
20
- }
21
- export interface DraftEntry {
22
- id: string;
23
- draft: StoredDraft;
24
- }
25
- export declare function draftKey(id: string): string;
26
- /** A new unique id for a draft whose editor has no `documentId`. */
27
- export declare function createDraftId(): string;
28
- /** Whether an id came from createDraftId() rather than a host's `documentId`. */
29
- export declare function isGeneratedDraftId(id: string): boolean;
30
- /** One-line text preview of a Lexical JSON document ('' when it has no text). */
31
- export declare function extractPreview(json: string): string;
32
- /** Whether a Lexical JSON document has any text or image (a blank one isn't worth a draft). */
33
- export declare function hasDraftContent(json: string): boolean;
34
- export declare function readDraft(id: string): StoredDraft | null;
35
- /**
36
- * Best-effort write; returns whether the draft was actually stored (callers that
37
- * promise the user a saved draft must check). Skips (rather than throwing) when `json` exceeds
38
- * `maxBytes` — localStorage has a shared ~5-10MB per-origin ceiling (§6.2), so a
39
- * single large document (e.g. embedded base64 images) must not be allowed to
40
- * autosave past a caller-configured limit — or when the write itself fails
41
- * (quota exceeded, privacy mode).
42
- */
43
- export declare function writeDraft(id: string, json: string, maxBytes: number, sourceId?: string): boolean;
44
- export declare function clearDraft(id: string): void;
45
- /** Every stored draft, newest first. */
46
- export declare function listDrafts(): DraftEntry[];
47
- /**
48
- * Keeps a draft that is about to be overwritten or ignored: copies it to a new
49
- * generated id (remembering `sourceId`) so it stays in the drafts list.
50
- * Returns the new id, or null if nothing could be copied.
51
- */
52
- export declare function archiveDraft(id: string): string | null;
53
- /** Deletes the oldest drafts beyond `max`, never touching `keepIds`. */
54
- export declare function pruneDrafts(max: number, keepIds?: string[]): void;
55
- //# sourceMappingURL=draftStorage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"draftStorage.d.ts","sourceRoot":"","sources":["../../src/persistence/draftStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,OAAO,EAAE,CAAC,CAAC;IACX,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,MAAM,CAOtC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEtD;AAgDD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAaD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQxD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAgBjG;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQ3C;AAED,wCAAwC;AACxC,wBAAgB,UAAU,IAAI,UAAU,EAAE,CAezC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWtD;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,IAAI,CAQrE"}