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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/EditorRoot.d.ts +57 -0
  2. package/dist/EditorRoot.d.ts.map +1 -1
  3. package/dist/EditorRoot.js +174 -11
  4. package/dist/components/DraftRestore.d.ts +24 -0
  5. package/dist/components/DraftRestore.d.ts.map +1 -0
  6. package/dist/components/DraftRestore.js +61 -0
  7. package/dist/components/DraftsDialog.d.ts +25 -0
  8. package/dist/components/DraftsDialog.d.ts.map +1 -0
  9. package/dist/components/DraftsDialog.js +66 -0
  10. package/dist/components/LeaveDialog.d.ts +22 -0
  11. package/dist/components/LeaveDialog.d.ts.map +1 -0
  12. package/dist/components/LeaveDialog.js +15 -0
  13. package/dist/components/Toolbar.d.ts +4 -1
  14. package/dist/components/Toolbar.d.ts.map +1 -1
  15. package/dist/components/Toolbar.js +8 -6
  16. package/dist/i18n/strings.d.ts +38 -5
  17. package/dist/i18n/strings.d.ts.map +1 -1
  18. package/dist/i18n/strings.js +96 -9
  19. package/dist/index.d.ts +2 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/nodes.d.ts.map +1 -1
  22. package/dist/nodes.js +0 -2
  23. package/dist/persistence/draftStorage.d.ts +55 -0
  24. package/dist/persistence/draftStorage.d.ts.map +1 -0
  25. package/dist/persistence/draftStorage.js +198 -0
  26. package/dist/persistence/draftUi.d.ts +9 -0
  27. package/dist/persistence/draftUi.d.ts.map +1 -0
  28. package/dist/persistence/draftUi.js +23 -0
  29. package/dist/theme/editor.css +74 -39
  30. package/dist/theme/editorTheme.d.ts.map +1 -1
  31. package/dist/theme/editorTheme.js +0 -1
  32. package/package.json +1 -1
  33. package/dist/blocks/PageBreakNode.d.ts +0 -30
  34. package/dist/blocks/PageBreakNode.d.ts.map +0 -1
  35. package/dist/blocks/PageBreakNode.js +0 -110
  36. package/dist/plugins/PageBreakPlugin.d.ts +0 -3
  37. package/dist/plugins/PageBreakPlugin.d.ts.map +0 -1
  38. package/dist/plugins/PageBreakPlugin.js +0 -19
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draftUi.d.ts","sourceRoot":"","sources":["../../src/persistence/draftUi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC;;qDAEqD;AACrD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO/E;AAID,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOvE"}
@@ -0,0 +1,23 @@
1
+ /** Replaces the editor's content with a stored draft's Lexical JSON. Returns
2
+ * false (leaving the editor untouched) if the draft is corrupt or from an
3
+ * incompatible node schema, rather than throwing. */
4
+ export function applyDraftToEditor(editor, json) {
5
+ try {
6
+ editor.setEditorState(editor.parseEditorState(json));
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ const INTL_LOCALES = { en: 'en', ur: 'ur', 'pa-shahmukhi': 'pa-Arab' };
14
+ /** A draft's save time as a short localized date and time. */
15
+ export function formatDraftTime(savedAt, locale) {
16
+ const date = new Date(savedAt);
17
+ try {
18
+ return new Intl.DateTimeFormat(INTL_LOCALES[locale], { dateStyle: 'medium', timeStyle: 'short' }).format(date);
19
+ }
20
+ catch {
21
+ return date.toLocaleString();
22
+ }
23
+ }
@@ -253,45 +253,6 @@
253
253
  margin: 1.5em 0;
254
254
  }
255
255
 
256
- /* Page break — a print/export concept with no visual meaning while editing
257
- on screen, so it gets its own clearly-labeled dashed marker rather than
258
- masquerading as a horizontal rule. The actual page-break behavior only
259
- takes effect when printing (lexical-editor-spec.md §4.3/§4.6). */
260
- .likhari-page-break {
261
- display: flex;
262
- align-items: center;
263
- gap: 8px;
264
- margin: 1.5em 0;
265
- font-size: 12px;
266
- cursor: default;
267
- }
268
-
269
- .likhari-page-break-line {
270
- flex: 1 1 auto;
271
- border-top: 1px dashed var(--editor-line);
272
- }
273
-
274
- .likhari-page-break-label {
275
- color: var(--editor-ink-soft);
276
- white-space: nowrap;
277
- }
278
-
279
- .likhari-page-break-selected {
280
- outline: 2px solid var(--editor-accent);
281
- outline-offset: 2px;
282
- border-radius: 4px;
283
- }
284
-
285
- @media print {
286
- .likhari-page-break {
287
- page-break-after: always;
288
- /* Hide the on-screen marker itself when actually printing — the
289
- browser's own pagination (triggered by page-break-after) is what
290
- matters there, not the editing-time label. */
291
- visibility: hidden;
292
- }
293
- }
294
-
295
256
  /* Images — block-level figure; the caption is static text (edited in the
296
257
  image's dialog), and a selected image gets a corner resize handle. */
297
258
  .likhari-image-block {
@@ -409,3 +370,77 @@
409
370
  text-overflow: ellipsis;
410
371
  white-space: nowrap;
411
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
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"editorTheme.d.ts","sourceRoot":"","sources":["../../src/theme/editorTheme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,kBA+BzB,CAAC"}
1
+ {"version":3,"file":"editorTheme.d.ts","sourceRoot":"","sources":["../../src/theme/editorTheme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,kBA8BzB,CAAC"}
@@ -33,5 +33,4 @@ export const editorTheme = {
33
33
  },
34
34
  hr: 'likhari-hr',
35
35
  image: 'likhari-image-block',
36
- pageBreak: 'likhari-page-break',
37
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.7-dev.81",
3
+ "version": "0.1.7-dev.86",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,30 +0,0 @@
1
- import { type ReactElement } from 'react';
2
- import { DecoratorNode, type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalCommand, type LexicalNode, type SerializedLexicalNode } from 'lexical';
3
- export declare const INSERT_PAGE_BREAK_COMMAND: LexicalCommand<void>;
4
- export type SerializedPageBreakNode = SerializedLexicalNode;
5
- /**
6
- * A manual page break — a print/paginated-export concept with no native
7
- * Lexical node (lexical-editor-spec.md §4.3/§4.6). Renders as a visible
8
- * dashed marker while editing, and as `page-break-after: always` both live
9
- * (a print stylesheet, editor.css) and in exported HTML (`exportDOM`).
10
- *
11
- * Mirrors @lexical/react's HorizontalRuleNode pattern: `createDOM` returns
12
- * the actual visible element (the click target `PageBreakComponent` wires
13
- * selection/delete against), rather than `decorate()` rendering visuals.
14
- */
15
- export declare class PageBreakNode extends DecoratorNode<ReactElement> {
16
- static getType(): string;
17
- static clone(node: PageBreakNode): PageBreakNode;
18
- static importJSON(): PageBreakNode;
19
- exportJSON(): SerializedPageBreakNode;
20
- static importDOM(): DOMConversionMap | null;
21
- exportDOM(): DOMExportOutput;
22
- createDOM(config: EditorConfig): HTMLElement;
23
- getTextContent(): string;
24
- isInline(): false;
25
- updateDOM(): boolean;
26
- decorate(): import("react").JSX.Element;
27
- }
28
- export declare function $createPageBreakNode(): PageBreakNode;
29
- export declare function $isPageBreakNode(node: LexicalNode | null | undefined): node is PageBreakNode;
30
- //# sourceMappingURL=PageBreakNode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PageBreakNode.d.ts","sourceRoot":"","sources":["../../src/blocks/PageBreakNode.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAKlE,OAAO,EAOL,aAAa,EAGb,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,qBAAqB,EAC3B,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,yBAAyB,EAAE,cAAc,CAAC,IAAI,CAA8C,CAAC;AAE1G,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AA2D5D;;;;;;;;;GASG;AACH,qBAAa,aAAc,SAAQ,aAAa,CAAC,YAAY,CAAC;IAC5D,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa;IAIhD,MAAM,CAAC,UAAU,IAAI,aAAa;IAIlC,UAAU,IAAI,uBAAuB;IAIrC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAS3C,SAAS,IAAI,eAAe;IAO5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,cAAc,IAAI,MAAM;IAIxB,QAAQ,IAAI,KAAK;IAIjB,SAAS,IAAI,OAAO;IAIpB,QAAQ;CAGT;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,aAAa,CAE5F"}
@@ -1,110 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect } from 'react';
3
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
- import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
5
- import { addClassNamesToElement, mergeRegister, removeClassNamesFromElement } from '@lexical/utils';
6
- import { useUiStrings } from '../i18n/useStrings.js';
7
- import { $applyNodeReplacement, $getSelection, $isNodeSelection, CLICK_COMMAND, COMMAND_PRIORITY_LOW, createCommand, DecoratorNode, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, } from 'lexical';
8
- export const INSERT_PAGE_BREAK_COMMAND = createCommand('INSERT_PAGE_BREAK_COMMAND');
9
- const SELECTED_CLASS_NAME = 'likhari-page-break-selected';
10
- function PageBreakComponent({ nodeKey }) {
11
- const [editor] = useLexicalComposerContext();
12
- const strings = useUiStrings();
13
- const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
14
- const onDelete = useCallback((event) => {
15
- const selection = $getSelection();
16
- if (isSelected && $isNodeSelection(selection)) {
17
- event.preventDefault();
18
- editor.update(() => {
19
- selection.getNodes().forEach((node) => {
20
- if ($isPageBreakNode(node))
21
- node.remove();
22
- });
23
- });
24
- }
25
- return false;
26
- }, [editor, isSelected]);
27
- useEffect(() => {
28
- return mergeRegister(editor.registerCommand(CLICK_COMMAND, (event) => {
29
- const element = editor.getElementByKey(nodeKey);
30
- if (event.target !== element)
31
- return false;
32
- if (!event.shiftKey)
33
- clearSelection();
34
- setSelected(!isSelected);
35
- return true;
36
- }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_BACKSPACE_COMMAND, onDelete, COMMAND_PRIORITY_LOW));
37
- }, [clearSelection, editor, isSelected, nodeKey, onDelete, setSelected]);
38
- useEffect(() => {
39
- const element = editor.getElementByKey(nodeKey);
40
- if (!element)
41
- return;
42
- if (isSelected)
43
- addClassNamesToElement(element, SELECTED_CLASS_NAME);
44
- else
45
- removeClassNamesFromElement(element, SELECTED_CLASS_NAME);
46
- }, [editor, isSelected, nodeKey]);
47
- return (_jsxs(_Fragment, { children: [_jsx("span", { className: "likhari-page-break-line", "aria-hidden": "true" }), _jsx("span", { className: "likhari-page-break-label", children: strings.editor.pageBreakLabel }), _jsx("span", { className: "likhari-page-break-line", "aria-hidden": "true" })] }));
48
- }
49
- /**
50
- * A manual page break — a print/paginated-export concept with no native
51
- * Lexical node (lexical-editor-spec.md §4.3/§4.6). Renders as a visible
52
- * dashed marker while editing, and as `page-break-after: always` both live
53
- * (a print stylesheet, editor.css) and in exported HTML (`exportDOM`).
54
- *
55
- * Mirrors @lexical/react's HorizontalRuleNode pattern: `createDOM` returns
56
- * the actual visible element (the click target `PageBreakComponent` wires
57
- * selection/delete against), rather than `decorate()` rendering visuals.
58
- */
59
- export class PageBreakNode extends DecoratorNode {
60
- static getType() {
61
- return 'page-break';
62
- }
63
- static clone(node) {
64
- return new PageBreakNode(node.__key);
65
- }
66
- static importJSON() {
67
- return $createPageBreakNode();
68
- }
69
- exportJSON() {
70
- return { type: 'page-break', version: 1 };
71
- }
72
- static importDOM() {
73
- return {
74
- div: (domNode) => {
75
- if (!domNode.hasAttribute('data-likhari-page-break'))
76
- return null;
77
- return { conversion: () => ({ node: $createPageBreakNode() }), priority: 2 };
78
- },
79
- };
80
- }
81
- exportDOM() {
82
- const element = document.createElement('div');
83
- element.setAttribute('data-likhari-page-break', '');
84
- element.style.pageBreakAfter = 'always';
85
- return { element };
86
- }
87
- createDOM(config) {
88
- const element = document.createElement('div');
89
- addClassNamesToElement(element, config.theme.pageBreak ?? 'likhari-page-break');
90
- return element;
91
- }
92
- getTextContent() {
93
- return '\n';
94
- }
95
- isInline() {
96
- return false;
97
- }
98
- updateDOM() {
99
- return false;
100
- }
101
- decorate() {
102
- return _jsx(PageBreakComponent, { nodeKey: this.__key });
103
- }
104
- }
105
- export function $createPageBreakNode() {
106
- return $applyNodeReplacement(new PageBreakNode());
107
- }
108
- export function $isPageBreakNode(node) {
109
- return node instanceof PageBreakNode;
110
- }
@@ -1,3 +0,0 @@
1
- /** Registers INSERT_PAGE_BREAK_COMMAND — mirrors @lexical/react's own HorizontalRulePlugin. */
2
- export declare function PageBreakPlugin(): null;
3
- //# sourceMappingURL=PageBreakPlugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PageBreakPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PageBreakPlugin.tsx"],"names":[],"mappings":"AAMA,+FAA+F;AAC/F,wBAAgB,eAAe,SAiB9B"}
@@ -1,19 +0,0 @@
1
- import { useEffect } from 'react';
2
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
- import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical';
4
- import { $insertNodeToNearestRoot } from '@lexical/utils';
5
- import { $createPageBreakNode, INSERT_PAGE_BREAK_COMMAND } from '../blocks/PageBreakNode.js';
6
- /** Registers INSERT_PAGE_BREAK_COMMAND — mirrors @lexical/react's own HorizontalRulePlugin. */
7
- export function PageBreakPlugin() {
8
- const [editor] = useLexicalComposerContext();
9
- useEffect(() => {
10
- return editor.registerCommand(INSERT_PAGE_BREAK_COMMAND, () => {
11
- const selection = $getSelection();
12
- if (!$isRangeSelection(selection))
13
- return false;
14
- $insertNodeToNearestRoot($createPageBreakNode());
15
- return true;
16
- }, COMMAND_PRIORITY_EDITOR);
17
- }, [editor]);
18
- return null;
19
- }