@inshapardaz/likhari-react 0.1.7-dev.75 → 0.1.7-dev.79

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,24 +70,6 @@ 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`, namespaced
75
- * by `documentId` (lexical-editor-spec.md §6.1–§6.2) — a resilience net
76
- * against an accidental tab close, independent of `onSave`/the Save
77
- * button. Requires `documentId` to be set; a draft newer than the initial
78
- * content prompts (via `window.confirm`) to restore it on mount, and is
79
- * cleared on a successful explicit save. Defaults to `true`.
80
- */
81
- autosave?: boolean;
82
- /** Debounce delay before writing an autosave draft, in ms. Default `750`. */
83
- autosaveDelayMs?: number;
84
- /**
85
- * Skips (rather than throwing) an autosave write whose JSON would exceed
86
- * this size — `localStorage` has a shared ~5-10MB per-origin ceiling, so a
87
- * single document with embedded base64 images must not be allowed to
88
- * autosave past a sane limit. Default `2_000_000` (~2MB).
89
- */
90
- autosaveMaxBytes?: number;
91
73
  }
92
74
  export interface EditorRef {
93
75
  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;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;AAMjE,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKnE,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;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;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,uGA6NrB,CAAC"}
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"}
@@ -7,6 +7,7 @@ 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';
10
11
  import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
11
12
  import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
12
13
  import { defaultFormatRegistry } from '@inshapardaz/likhari-converters';
@@ -17,24 +18,19 @@ import { EditorThemeProvider } from './theme/EditorThemeProvider.js';
17
18
  import { Toolbar } from './components/Toolbar.js';
18
19
  import { injectUrduWebFontsCss } from './fonts/index.js';
19
20
  import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
20
- import { AutosaveRestorePlugin } from './plugins/AutosaveRestorePlugin.js';
21
- import { clearDraft, writeDraft } from './persistence/draftStorage.js';
22
21
  import { ImageOptionsContext } from './image/ImageOptionsContext.js';
23
22
  import { PortalTargetContext } from './PortalTargetContext.js';
24
23
  import { UiStringsContext, getStrings } from './i18n/index.js';
25
- const DEFAULT_AUTOSAVE_DELAY_MS = 750;
26
- const DEFAULT_AUTOSAVE_MAX_BYTES = 2000000;
27
24
  function initialEditorStateJson(initialContent) {
28
25
  if (!initialContent)
29
26
  return undefined;
30
27
  const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
31
28
  return JSON.stringify(state);
32
29
  }
33
- 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, }, ref) {
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
31
  const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
35
32
  const strings = useMemo(() => getStrings(locale), [locale]);
36
33
  const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
37
- const autosaveEnabled = autosave && Boolean(documentId);
38
34
  // The urdu-web-fonts stylesheets are needed for the font dropdown and for
39
35
  // RTL content, whose canvas font (editor.css) is one of those families.
40
36
  useEffect(() => {
@@ -52,48 +48,17 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
52
48
  const editorStateRef = useRef(null);
53
49
  const lastSavedJsonRef = useRef(null);
54
50
  const [isDirty, setIsDirty] = useState(false);
55
- const isDirtyRef = useRef(isDirty);
56
- isDirtyRef.current = isDirty;
57
- const autosaveTimerRef = useRef(null);
58
- // Computed once, before the first autosave write could possibly run, so
59
- // the restore-prompt plugin below can tell a stored draft that's merely
60
- // identical to the initial content apart (no prompt needed) from one that
61
- // actually represents newer, unsaved work.
62
- const initialContentJsonRef = useRef(undefined);
63
- if (initialContentJsonRef.current === undefined) {
64
- initialContentJsonRef.current = initialEditorStateJson(initialContent);
65
- }
66
51
  const initialConfig = useMemo(() => ({
67
52
  namespace: `likhari-editor-${documentId ?? 'anonymous'}`,
68
53
  nodes: EDITOR_NODES,
69
54
  theme: editorTheme,
70
- editorState: initialContentJsonRef.current,
55
+ editorState: initialEditorStateJson(initialContent),
71
56
  onError(error) {
72
57
  throw error;
73
58
  },
74
59
  }),
75
60
  // eslint-disable-next-line react-hooks/exhaustive-deps
76
61
  []);
77
- // beforeunload (lexical-editor-spec.md §6.4): the automatic half of the
78
- // navigation guard — hasUnsavedChanges()/confirmDiscard() below are the
79
- // imperative half a host's own router guard calls for in-app navigation,
80
- // which the editor can't intercept on its own.
81
- useEffect(() => {
82
- if (typeof window === 'undefined')
83
- return;
84
- const handleBeforeUnload = (event) => {
85
- if (!isDirtyRef.current)
86
- return;
87
- event.preventDefault();
88
- event.returnValue = '';
89
- };
90
- window.addEventListener('beforeunload', handleBeforeUnload);
91
- return () => window.removeEventListener('beforeunload', handleBeforeUnload);
92
- }, []);
93
- useEffect(() => () => {
94
- if (autosaveTimerRef.current)
95
- clearTimeout(autosaveTimerRef.current);
96
- }, []);
97
62
  useImperativeHandle(ref, () => ({
98
63
  getContent(format) {
99
64
  if (!editorStateRef.current)
@@ -130,13 +95,6 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
130
95
  const json = JSON.stringify(state.toJSON());
131
96
  setIsDirty(json !== lastSavedJsonRef.current);
132
97
  onChange?.(state.toJSON());
133
- if (autosaveEnabled && documentId) {
134
- if (autosaveTimerRef.current)
135
- clearTimeout(autosaveTimerRef.current);
136
- autosaveTimerRef.current = setTimeout(() => {
137
- writeDraft(documentId, json, autosaveMaxBytes);
138
- }, autosaveDelayMs);
139
- }
140
98
  };
141
99
  const handleSave = () => {
142
100
  if (!editorStateRef.current)
@@ -146,15 +104,6 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
146
104
  const content = defaultFormatRegistry.serialize(format, json);
147
105
  lastSavedJsonRef.current = JSON.stringify(json);
148
106
  setIsDirty(false);
149
- // A successful explicit save supersedes any in-progress/queued autosave
150
- // draft — don't let a stale one linger and falsely prompt to "restore"
151
- // older content next time this document is opened (§6.2).
152
- if (autosaveTimerRef.current) {
153
- clearTimeout(autosaveTimerRef.current);
154
- autosaveTimerRef.current = null;
155
- }
156
- if (documentId)
157
- clearDraft(documentId);
158
107
  onSave?.(content, format);
159
108
  };
160
109
  const dir = locale === 'en' ? 'ltr' : 'rtl';
@@ -163,5 +112,5 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
163
112
  // rather than colliding on a shared one.
164
113
  const portalTargetId = useId();
165
114
  const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
166
- 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, {}), _jsx(OnChangePlugin, { onChange: handleChange }), _jsx(AutosaveRestorePlugin, { documentId: documentId, enabled: autosaveEnabled, initialJson: initialContentJsonRef.current, confirmMessage: strings.editor.restoreDraftConfirm })] }) }) }) })] }) }));
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 })] }) }) }) })] }) }));
167
116
  });
@@ -0,0 +1,17 @@
1
+ export interface TableDialogValue {
2
+ rows: number;
3
+ columns: number;
4
+ headerRow: boolean;
5
+ }
6
+ export interface TableDialogProps {
7
+ opened: boolean;
8
+ onSubmit: (value: TableDialogValue) => void;
9
+ onClose: () => void;
10
+ }
11
+ /**
12
+ * Insert-table dialog: rows x columns and an optional header row. Cell
13
+ * merge/split is intentionally not offered anywhere in this dialog or the
14
+ * table it creates — see the comment on `TablePlugin` in `EditorRoot.tsx`.
15
+ */
16
+ export declare function TableDialog({ opened, onSubmit, onClose }: TableDialogProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=TableDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableDialog.d.ts","sourceRoot":"","sources":["../../src/components/TableDialog.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAOD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,gBAAgB,+BAiD1E"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Button, Checkbox, Group, Modal, NumberInput } from '@mantine/core';
4
+ import { useUiStrings } from '../i18n/useStrings.js';
5
+ import { usePortalTarget } from '../PortalTargetContext.js';
6
+ const MIN_DIMENSION = 1;
7
+ const MAX_DIMENSION = 20;
8
+ const DEFAULT_ROWS = 3;
9
+ const DEFAULT_COLUMNS = 3;
10
+ /**
11
+ * Insert-table dialog: rows x columns and an optional header row. Cell
12
+ * merge/split is intentionally not offered anywhere in this dialog or the
13
+ * table it creates — see the comment on `TablePlugin` in `EditorRoot.tsx`.
14
+ */
15
+ export function TableDialog({ opened, onSubmit, onClose }) {
16
+ const strings = useUiStrings();
17
+ const portalTarget = usePortalTarget();
18
+ const [rows, setRows] = useState(DEFAULT_ROWS);
19
+ const [columns, setColumns] = useState(DEFAULT_COLUMNS);
20
+ const [headerRow, setHeaderRow] = useState(true);
21
+ useEffect(() => {
22
+ if (!opened)
23
+ return;
24
+ setRows(DEFAULT_ROWS);
25
+ setColumns(DEFAULT_COLUMNS);
26
+ setHeaderRow(true);
27
+ }, [opened]);
28
+ const handleSubmit = (e) => {
29
+ e.preventDefault();
30
+ onSubmit({ rows, columns, headerRow });
31
+ };
32
+ return (_jsx(Modal, { opened: opened, onClose: onClose, title: strings.tableDialog.title, centered: true, size: "sm", portalProps: { target: portalTarget }, children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(Group, { grow: true, align: "flex-end", children: [_jsx(NumberInput, { label: strings.tableDialog.rows, min: MIN_DIMENSION, max: MAX_DIMENSION, value: rows, onChange: (v) => setRows(typeof v === 'number' ? v : DEFAULT_ROWS), "data-autofocus": true }), _jsx(NumberInput, { label: strings.tableDialog.columns, min: MIN_DIMENSION, max: MAX_DIMENSION, value: columns, onChange: (v) => setColumns(typeof v === 'number' ? v : DEFAULT_COLUMNS) })] }), _jsx(Checkbox, { mt: "sm", label: strings.tableDialog.headerRow, checked: headerRow, onChange: (e) => setHeaderRow(e.currentTarget.checked) }), _jsxs(Group, { justify: "flex-end", gap: "xs", mt: "md", children: [_jsx(Button, { variant: "default", onClick: onClose, children: strings.common.cancel }), _jsx(Button, { type: "submit", children: strings.common.insert })] })] }) }));
33
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA4CA,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;AAqUvD,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,+BA8wBhJ"}
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"}
@@ -10,6 +10,8 @@ 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';
13
15
  import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
14
16
  import { LinkDialog } from './LinkDialog.js';
15
17
  import { normalizeLinkUrl } from '../utils/linkUrl.js';
@@ -17,7 +19,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fo
17
19
  import { setStyleProperty } from '../utils/style.js';
18
20
  import { useStrings } from '../i18n/useStrings.js';
19
21
  import { usePortalTarget } from '../PortalTargetContext.js';
20
- 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, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
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
23
  /** Sentinel for "no inline style": Lexical returns the default it is given when
22
24
  * the selection has none, so this tells "unstyled" (show the canvas default)
23
25
  * apart from "styled with a value the dropdown doesn't list" (show nothing). */
@@ -273,6 +275,26 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
273
275
  });
274
276
  closeImageDialog();
275
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
+ };
276
298
  /** Applies a font property per `config.font.scope`: to the selection, to
277
299
  * every text node in the document, or (for 'both') to the selection when
278
300
  * there is a range and to the whole document when there isn't. */
@@ -426,7 +448,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
426
448
  const showFormattingGroup = headingLevels.length > 0 || config.lists.bullet || config.lists.numbered || config.lists.check || config.blocks.quote;
427
449
  const showInlineGroup = fmt.bold || fmt.italic || fmt.underline;
428
450
  const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
429
- const showStubInsertGroup = config.images.linked || config.images.embedded || config.poetry.enabled;
451
+ const showStubInsertGroup = config.images.linked || config.images.embedded || config.tables || config.poetry.enabled;
430
452
  const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
431
453
  // The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
432
454
  // as normal inline toolbar groups whenever there's room, and only move
@@ -595,7 +617,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
595
617
  restoreSelection();
596
618
  openLinkDialog();
597
619
  }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
598
- 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")),
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")),
599
621
  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")),
600
622
  ];
601
623
  return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
@@ -617,5 +639,5 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
617
639
  display: 'flex',
618
640
  flexWrap: 'nowrap',
619
641
  gap: 8,
620
- }, 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 }))] }));
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 }))] }));
621
643
  }
@@ -15,8 +15,6 @@ export interface Strings {
15
15
  contentLabel: string;
16
16
  /** window.confirm() prompt shown by EditorRef.confirmDiscard() when there are unsaved changes. */
17
17
  confirmDiscard: string;
18
- /** window.confirm() prompt offering to restore a localStorage draft newer than the initial content (lexical-editor-spec.md §6.2). */
19
- restoreDraftConfirm: string;
20
18
  };
21
19
  common: {
22
20
  cancel: string;
@@ -42,6 +40,7 @@ export interface Strings {
42
40
  linkOptions: string;
43
41
  insertLink: string;
44
42
  insertImage: string;
43
+ insertTable: string;
45
44
  poetryBlocks: string;
46
45
  comingSoon: (label: string) => string;
47
46
  autocorrect: string;
@@ -90,6 +89,12 @@ export interface Strings {
90
89
  invalidUrl: string;
91
90
  apply: string;
92
91
  };
92
+ tableDialog: {
93
+ title: string;
94
+ rows: string;
95
+ columns: string;
96
+ headerRow: string;
97
+ };
93
98
  imageDialog: {
94
99
  titleInsert: string;
95
100
  titleEdit: string;
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,6 @@ const en = {
9
9
  placeholder: 'Start writing…',
10
10
  contentLabel: 'Editor content',
11
11
  confirmDiscard: 'You have unsaved changes. Discard them?',
12
- restoreDraftConfirm: 'An unsaved draft of this document was found. Restore it?',
13
12
  },
14
13
  common: {
15
14
  cancel: 'Cancel',
@@ -34,6 +33,7 @@ const en = {
34
33
  linkOptions: 'Link options',
35
34
  insertLink: 'Insert link (Ctrl+K)',
36
35
  insertImage: 'Insert image',
36
+ insertTable: 'Insert table',
37
37
  poetryBlocks: 'Poetry blocks',
38
38
  comingSoon: (label) => `${label} (coming soon)`,
39
39
  autocorrect: 'Auto-correct',
@@ -82,6 +82,12 @@ const en = {
82
82
  invalidUrl: 'Enter a valid http(s), mailto, tel or relative URL',
83
83
  apply: 'Apply',
84
84
  },
85
+ tableDialog: {
86
+ title: 'Insert table',
87
+ rows: 'Rows',
88
+ columns: 'Columns',
89
+ headerRow: 'Header row',
90
+ },
85
91
  imageDialog: {
86
92
  titleInsert: 'Insert image',
87
93
  titleEdit: 'Edit image',
@@ -146,7 +152,6 @@ const ur = {
146
152
  placeholder: 'لکھنا شروع کریں…',
147
153
  contentLabel: 'ایڈیٹر کا مواد',
148
154
  confirmDiscard: 'آپ کی غیر محفوظ شدہ تبدیلیاں ہیں۔ کیا انہیں رد کر دیا جائے؟',
149
- restoreDraftConfirm: 'اس دستاویز کا ایک غیر محفوظ شدہ مسودہ ملا ہے۔ کیا اسے بحال کیا جائے؟',
150
155
  },
151
156
  common: {
152
157
  cancel: 'منسوخ کریں',
@@ -171,6 +176,7 @@ const ur = {
171
176
  linkOptions: 'لنک کے اختیارات',
172
177
  insertLink: 'لنک شامل کریں (Ctrl+K)',
173
178
  insertImage: 'تصویر شامل کریں',
179
+ insertTable: 'جدول شامل کریں',
174
180
  poetryBlocks: 'شاعری کے بلاکس',
175
181
  comingSoon: (label) => `${label} (جلد آ رہا ہے)`,
176
182
  autocorrect: 'خودکار تصحیح',
@@ -219,6 +225,12 @@ const ur = {
219
225
  invalidUrl: 'براہِ کرم ایک درست http(s)، mailto، tel یا رشتہ دار یو آر ایل درج کریں',
220
226
  apply: 'لاگو کریں',
221
227
  },
228
+ tableDialog: {
229
+ title: 'جدول شامل کریں',
230
+ rows: 'قطاریں',
231
+ columns: 'کالم',
232
+ headerRow: 'سرِ فہرست قطار',
233
+ },
222
234
  imageDialog: {
223
235
  titleInsert: 'تصویر شامل کریں',
224
236
  titleEdit: 'تصویر میں ترمیم کریں',
@@ -283,7 +295,6 @@ const paShahmukhi = {
283
295
  placeholder: 'لکھنا شروع کرو…',
284
296
  contentLabel: 'ایڈیٹر دی سامگری',
285
297
  confirmDiscard: 'تہاڈیاں کجھ تبدیلیاں سنبھالیاں نئیں گئیاں۔ کی ایہناں نوں رد کر دیئے؟',
286
- restoreDraftConfirm: 'ایس دستاویز دا اک غیر محفوظ سودھا لبھیا اے۔ کیہ ایہنوں بحال کيتا جاۓ؟',
287
298
  },
288
299
  common: {
289
300
  cancel: 'رد کرو',
@@ -308,6 +319,7 @@ const paShahmukhi = {
308
319
  linkOptions: 'لنک دے اختیار',
309
320
  insertLink: 'لنک پاؤ (Ctrl+K)',
310
321
  insertImage: 'تصویر پاؤ',
322
+ insertTable: 'ٹیبل پاؤ',
311
323
  poetryBlocks: 'شاعری بلاک',
312
324
  comingSoon: (label) => `${label} (چھیتی آ رہا اے)`,
313
325
  autocorrect: 'خودکار درستی',
@@ -356,6 +368,12 @@ const paShahmukhi = {
356
368
  invalidUrl: 'کِرپا کر کے صحیح http(s)، mailto، tel یا نسبتی یو آر ایل پاؤ',
357
369
  apply: 'لاگو کرو',
358
370
  },
371
+ tableDialog: {
372
+ title: 'ٹیبل پاؤ',
373
+ rows: 'قطاراں',
374
+ columns: 'کالم',
375
+ headerRow: 'سرکڑی قطار',
376
+ },
359
377
  imageDialog: {
360
378
  titleInsert: 'تصویر پاؤ',
361
379
  titleEdit: 'تصویر وچ تبدیلی کرو',
@@ -1 +1 @@
1
- {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAOlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAQ5C,CAAC"}
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAQlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAW5C,CAAC"}
package/dist/nodes.js CHANGED
@@ -2,6 +2,7 @@ import { HeadingNode, QuoteNode } from '@lexical/rich-text';
2
2
  import { ListItemNode, ListNode } from '@lexical/list';
3
3
  import { LinkNode } from '@lexical/link';
4
4
  import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
5
+ import { TableCellNode, TableNode, TableRowNode } from '@lexical/table';
5
6
  import { ImageNode } from './image/ImageNode.js';
6
7
  /**
7
8
  * Node types registered on the editor at construction time. Per
@@ -19,5 +20,8 @@ export const EDITOR_NODES = [
19
20
  ListItemNode,
20
21
  LinkNode,
21
22
  HorizontalRuleNode,
23
+ TableNode,
24
+ TableRowNode,
25
+ TableCellNode,
22
26
  ImageNode,
23
27
  ];
@@ -253,6 +253,37 @@
253
253
  margin: 1.5em 0;
254
254
  }
255
255
 
256
+ /* Tables — cell merge/split is deliberately out of scope for now (a flagged
257
+ open risk, lexical-editor-spec.md §12 item 3, needing its own scope
258
+ decision); TablePlugin is configured with hasCellMerge={false} to keep
259
+ every table a plain grid until that's resolved. */
260
+ .likhari-table {
261
+ border-collapse: collapse;
262
+ margin: 1em 0;
263
+ width: 100%;
264
+ }
265
+
266
+ .likhari-table-row {
267
+ border-bottom: 1px solid var(--editor-line);
268
+ }
269
+
270
+ .likhari-table-cell {
271
+ border: 1px solid var(--editor-line);
272
+ padding: 6px 10px;
273
+ min-width: 72px;
274
+ vertical-align: top;
275
+ }
276
+
277
+ .likhari-table-cell-header {
278
+ background: var(--editor-paper-dim);
279
+ font-weight: 600;
280
+ text-align: start;
281
+ }
282
+
283
+ .likhari-table-cell-selected {
284
+ background: var(--editor-accent-soft);
285
+ }
286
+
256
287
  /* Images — block-level figure; the caption is static text (edited in the
257
288
  image's dialog), and a selected image gets a corner resize handle. */
258
289
  .likhari-image-block {
@@ -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,kBA8BzB,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,kBAmCzB,CAAC"}
@@ -33,4 +33,9 @@ export const editorTheme = {
33
33
  },
34
34
  hr: 'likhari-hr',
35
35
  image: 'likhari-image-block',
36
+ table: 'likhari-table',
37
+ tableRow: 'likhari-table-row',
38
+ tableCell: 'likhari-table-cell',
39
+ tableCellHeader: 'likhari-table-cell-header',
40
+ tableCellSelected: 'likhari-table-cell-selected',
36
41
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.7-dev.75",
3
+ "version": "0.1.7-dev.79",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,6 +37,7 @@
37
37
  "@lexical/react": "^0.20.0",
38
38
  "@lexical/rich-text": "^0.20.0",
39
39
  "@lexical/selection": "^0.20.0",
40
+ "@lexical/table": "^0.20.0",
40
41
  "@lexical/utils": "^0.20.0",
41
42
  "@inshapardaz/likhari-converters": "^0.1.0",
42
43
  "@inshapardaz/likhari-core": "^0.1.0",
@@ -1,26 +0,0 @@
1
- /**
2
- * Autosave-to-localStorage drafts (lexical-editor-spec.md §6.2), namespaced
3
- * by `documentId` (§6.1). Every function is a no-op (never throws) when
4
- * `localStorage` is unavailable (SSR, privacy mode) or the stored value is
5
- * corrupt — autosave is best-effort, never a hard dependency.
6
- */
7
- export interface StoredDraft {
8
- /** Lexical JSON (as produced by `EditorState.toJSON()`, stringified). */
9
- json: string;
10
- /** `Date.now()` at write time — lets a host distinguish a stale draft from a genuinely recent one. */
11
- savedAt: number;
12
- /** Bumped only if the stored shape itself changes; lets old drafts be recognised/discarded across an upgrade. */
13
- version: 1;
14
- }
15
- export declare function draftKey(documentId: string): string;
16
- export declare function readDraft(documentId: string): StoredDraft | null;
17
- /**
18
- * Best-effort debounced write. Silently skips (rather than throwing) when
19
- * `json` exceeds `maxBytes` — localStorage has a shared ~5-10MB per-origin
20
- * ceiling (§6.2), so a single large document (e.g. embedded base64 images)
21
- * must not be allowed to autosave past a caller-configured limit — or when
22
- * the write itself fails (quota exceeded, privacy mode).
23
- */
24
- export declare function writeDraft(documentId: string, json: string, maxBytes: number): void;
25
- export declare function clearDraft(documentId: string): void;
26
- //# sourceMappingURL=draftStorage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"draftStorage.d.ts","sourceRoot":"","sources":["../../src/persistence/draftStorage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,OAAO,EAAE,CAAC,CAAC;CACZ;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnD;AAWD,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAYhE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAanF;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAQnD"}
@@ -1,71 +0,0 @@
1
- /**
2
- * Autosave-to-localStorage drafts (lexical-editor-spec.md §6.2), namespaced
3
- * by `documentId` (§6.1). Every function is a no-op (never throws) when
4
- * `localStorage` is unavailable (SSR, privacy mode) or the stored value is
5
- * corrupt — autosave is best-effort, never a hard dependency.
6
- */
7
- const KEY_PREFIX = 'likhari:draft:';
8
- export function draftKey(documentId) {
9
- return `${KEY_PREFIX}${documentId}`;
10
- }
11
- function getLocalStorage() {
12
- try {
13
- return typeof localStorage === 'undefined' ? null : localStorage;
14
- }
15
- catch {
16
- // Some browsers throw on accessing localStorage in certain privacy modes.
17
- return null;
18
- }
19
- }
20
- export function readDraft(documentId) {
21
- const storage = getLocalStorage();
22
- if (!storage)
23
- return null;
24
- try {
25
- const raw = storage.getItem(draftKey(documentId));
26
- if (!raw)
27
- return null;
28
- const parsed = JSON.parse(raw);
29
- if (typeof parsed.json !== 'string' || typeof parsed.savedAt !== 'number' || parsed.version !== 1)
30
- return null;
31
- return parsed;
32
- }
33
- catch {
34
- return null;
35
- }
36
- }
37
- /**
38
- * Best-effort debounced write. Silently skips (rather than throwing) when
39
- * `json` exceeds `maxBytes` — localStorage has a shared ~5-10MB per-origin
40
- * ceiling (§6.2), so a single large document (e.g. embedded base64 images)
41
- * must not be allowed to autosave past a caller-configured limit — or when
42
- * the write itself fails (quota exceeded, privacy mode).
43
- */
44
- export function writeDraft(documentId, json, maxBytes) {
45
- const storage = getLocalStorage();
46
- if (!storage)
47
- return;
48
- // .length (UTF-16 code units) over-estimates UTF-8 byte size for any
49
- // non-Latin text, which only makes this check more conservative — fine
50
- // for a soft cap that doesn't need to be exact.
51
- if (json.length > maxBytes)
52
- return;
53
- const draft = { json, savedAt: Date.now(), version: 1 };
54
- try {
55
- storage.setItem(draftKey(documentId), JSON.stringify(draft));
56
- }
57
- catch {
58
- // Quota exceeded or similar — autosave is best-effort.
59
- }
60
- }
61
- export function clearDraft(documentId) {
62
- const storage = getLocalStorage();
63
- if (!storage)
64
- return;
65
- try {
66
- storage.removeItem(draftKey(documentId));
67
- }
68
- catch {
69
- // ignore
70
- }
71
- }
@@ -1,16 +0,0 @@
1
- export interface AutosaveRestorePluginProps {
2
- documentId?: string;
3
- enabled: boolean;
4
- /** The Lexical JSON the editor was constructed with (stringified), or
5
- * undefined for a blank document — compared against a stored draft so an
6
- * identical draft doesn't trigger a pointless prompt. */
7
- initialJson?: string;
8
- confirmMessage: string;
9
- }
10
- /**
11
- * On mount, offers to restore a newer localStorage draft over the initial
12
- * content passed in (lexical-editor-spec.md §6.2) — data-loss prevention is
13
- * the explicit goal, so this never silently picks one over the other.
14
- */
15
- export declare function AutosaveRestorePlugin({ documentId, enabled, initialJson, confirmMessage }: AutosaveRestorePluginProps): null;
16
- //# sourceMappingURL=AutosaveRestorePlugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AutosaveRestorePlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/AutosaveRestorePlugin.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB;;6DAEyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,EAAE,0BAA0B,QAwBrH"}
@@ -1,33 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
- import { readDraft } from '../persistence/draftStorage.js';
4
- /**
5
- * On mount, offers to restore a newer localStorage draft over the initial
6
- * content passed in (lexical-editor-spec.md §6.2) — data-loss prevention is
7
- * the explicit goal, so this never silently picks one over the other.
8
- */
9
- export function AutosaveRestorePlugin({ documentId, enabled, initialJson, confirmMessage }) {
10
- const [editor] = useLexicalComposerContext();
11
- const checked = useRef(false);
12
- useEffect(() => {
13
- if (checked.current || !enabled || !documentId)
14
- return;
15
- checked.current = true;
16
- const draft = readDraft(documentId);
17
- if (!draft || draft.json === initialJson)
18
- return;
19
- if (typeof window === 'undefined' || !window.confirm(confirmMessage))
20
- return;
21
- try {
22
- const parsed = editor.parseEditorState(draft.json);
23
- editor.setEditorState(parsed);
24
- }
25
- catch {
26
- // Corrupt or incompatible draft (e.g. from a much older node schema) —
27
- // ignore it and keep the initial content rather than crashing.
28
- }
29
- // Deliberately runs once on mount only.
30
- // eslint-disable-next-line react-hooks/exhaustive-deps
31
- }, []);
32
- return null;
33
- }