@inshapardaz/likhari-react 0.1.13 → 0.1.14-dev.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +2 -1
- package/dist/blocks/LayoutNode.d.ts +54 -0
- package/dist/blocks/LayoutNode.d.ts.map +1 -0
- package/dist/blocks/LayoutNode.js +138 -0
- package/dist/blocks/layoutActions.d.ts +12 -0
- package/dist/blocks/layoutActions.d.ts.map +1 -0
- package/dist/blocks/layoutActions.js +39 -0
- package/dist/components/LayoutDialog.d.ts +11 -0
- package/dist/components/LayoutDialog.d.ts.map +1 -0
- package/dist/components/LayoutDialog.js +24 -0
- package/dist/components/TableDialog.d.ts +2 -2
- package/dist/components/TableDialog.js +2 -2
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +49 -12
- package/dist/i18n/strings.d.ts +7 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +21 -0
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +3 -0
- package/dist/plugins/LayoutPlugin.d.ts +9 -0
- package/dist/plugins/LayoutPlugin.d.ts.map +1 -0
- package/dist/plugins/LayoutPlugin.js +23 -0
- package/dist/table/tableActions.d.ts +13 -0
- package/dist/table/tableActions.d.ts.map +1 -1
- package/dist/table/tableActions.js +86 -2
- package/dist/theme/editor.css +37 -4
- package/dist/theme/editorTheme.d.ts.map +1 -1
- package/dist/theme/editorTheme.js +8 -0
- package/package.json +1 -1
package/dist/EditorRoot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,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,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAYhF,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMnE,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,CAAC;IACjB,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;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,eAAO,MAAM,UAAU,uGAkXrB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -21,6 +21,7 @@ import { Toolbar } from './components/Toolbar.js';
|
|
|
21
21
|
import { injectUrduWebFontsCss } from './fonts/index.js';
|
|
22
22
|
import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
|
|
23
23
|
import { PageBreakPlugin } from './plugins/PageBreakPlugin.js';
|
|
24
|
+
import { LayoutPlugin } from './plugins/LayoutPlugin.js';
|
|
24
25
|
import { DraftRestore } from './components/DraftRestore.js';
|
|
25
26
|
import { LeaveDialog } from './components/LeaveDialog.js';
|
|
26
27
|
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
@@ -277,7 +278,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
277
278
|
const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
|
|
278
279
|
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: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
|
|
279
280
|
? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
|
|
280
|
-
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.tables && _jsx(TablePlugin, { hasCellMerge:
|
|
281
|
+
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.columns && _jsx(LayoutPlugin, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
|
|
281
282
|
handleSave();
|
|
282
283
|
settleLeave(true);
|
|
283
284
|
}, onSaveDraft: () => {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalCommand, type LexicalNode, type NodeKey, type SerializedElementNode, type Spread, ElementNode } from 'lexical';
|
|
2
|
+
/** Payload: how many (equal-width) columns to create. Ratio support beyond
|
|
3
|
+
* equal widths is deferred (lexical-editor-spec.md §4.9 mentions it as a
|
|
4
|
+
* "configurable" possibility, not a committed requirement for Phase 3). */
|
|
5
|
+
export declare const INSERT_LAYOUT_COMMAND: LexicalCommand<{
|
|
6
|
+
columnCount: number;
|
|
7
|
+
}>;
|
|
8
|
+
export type SerializedLayoutContainerNode = Spread<{
|
|
9
|
+
templateColumns: string;
|
|
10
|
+
}, SerializedElementNode>;
|
|
11
|
+
export type SerializedLayoutItemNode = SerializedElementNode;
|
|
12
|
+
/** A multi-column row (editor-architecture-design.md §3.2): an ElementNode
|
|
13
|
+
* holding one LayoutItemNode per column, laid out with CSS grid. Reused as
|
|
14
|
+
* the structural basis for two-column poetry layouts (spec §4.11). */
|
|
15
|
+
export declare class LayoutContainerNode extends ElementNode {
|
|
16
|
+
__templateColumns: string;
|
|
17
|
+
constructor(templateColumns?: string, key?: NodeKey);
|
|
18
|
+
static getType(): string;
|
|
19
|
+
static clone(node: LayoutContainerNode): LayoutContainerNode;
|
|
20
|
+
getTemplateColumns(): string;
|
|
21
|
+
setTemplateColumns(templateColumns: string): this;
|
|
22
|
+
static importJSON(serializedNode: SerializedLayoutContainerNode): LayoutContainerNode;
|
|
23
|
+
exportJSON(): SerializedLayoutContainerNode;
|
|
24
|
+
static importDOM(): DOMConversionMap | null;
|
|
25
|
+
exportDOM(): DOMExportOutput;
|
|
26
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
27
|
+
updateDOM(prevNode: LayoutContainerNode, dom: HTMLElement): boolean;
|
|
28
|
+
canBeEmpty(): false;
|
|
29
|
+
isShadowRoot(): boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function $createLayoutContainerNode(templateColumns?: string): LayoutContainerNode;
|
|
32
|
+
export declare function $isLayoutContainerNode(node: LexicalNode | null | undefined): node is LayoutContainerNode;
|
|
33
|
+
/** One column's content area inside a LayoutContainerNode. Always keeps at
|
|
34
|
+
* least an empty paragraph (mirrors TableCellNode), so a column can never
|
|
35
|
+
* collapse to an unselectable, unfocusable void. */
|
|
36
|
+
export declare class LayoutItemNode extends ElementNode {
|
|
37
|
+
static getType(): string;
|
|
38
|
+
static clone(node: LayoutItemNode): LayoutItemNode;
|
|
39
|
+
static importJSON(): LayoutItemNode;
|
|
40
|
+
exportJSON(): SerializedLayoutItemNode;
|
|
41
|
+
static importDOM(): DOMConversionMap | null;
|
|
42
|
+
exportDOM(): DOMExportOutput;
|
|
43
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
44
|
+
updateDOM(): boolean;
|
|
45
|
+
canBeEmpty(): false;
|
|
46
|
+
canIndent(): false;
|
|
47
|
+
}
|
|
48
|
+
export declare function $createLayoutItemNode(): LayoutItemNode;
|
|
49
|
+
export declare function $isLayoutItemNode(node: LexicalNode | null | undefined): node is LayoutItemNode;
|
|
50
|
+
/** Builds a container with `columnCount` empty items — the shape every
|
|
51
|
+
* LAYOUT insert produces, and what the node-transform below re-normalizes
|
|
52
|
+
* a malformed tree back toward. */
|
|
53
|
+
export declare function $createLayoutWithColumns(columnCount: number): LayoutContainerNode;
|
|
54
|
+
//# sourceMappingURL=LayoutNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LayoutNode.d.ts","sourceRoot":"","sources":["../../src/blocks/LayoutNode.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EAEX,WAAW,EACZ,MAAM,SAAS,CAAC;AAGjB;;2EAE2E;AAC3E,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAA0C,CAAC;AAErH,MAAM,MAAM,6BAA6B,GAAG,MAAM,CAAC;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,EAAE,qBAAqB,CAAC,CAAC;AACvG,MAAM,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAE7D;;sEAEsE;AACtE,qBAAa,mBAAoB,SAAQ,WAAW;IAClD,iBAAiB,EAAE,MAAM,CAAC;gBAEd,eAAe,SAAmB,EAAE,GAAG,CAAC,EAAE,OAAO;IAK7D,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,mBAAmB,GAAG,mBAAmB;IAI5D,kBAAkB,IAAI,MAAM;IAI5B,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI;IAMjD,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,6BAA6B,GAAG,mBAAmB;IAIrF,UAAU,IAAI,6BAA6B;IAI3C,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAU3C,SAAS,IAAI,eAAe;IAQ5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAO5C,SAAS,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO;IAOnE,UAAU,IAAI,KAAK;IAInB,YAAY,IAAI,OAAO;CAGxB;AAED,wBAAgB,0BAA0B,CAAC,eAAe,SAAmB,GAAG,mBAAmB,CAElG;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,mBAAmB,CAExG;AAED;;oDAEoD;AACpD,qBAAa,cAAe,SAAQ,WAAW;IAC7C,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc;IAIlD,MAAM,CAAC,UAAU,IAAI,cAAc;IAInC,UAAU,IAAI,wBAAwB;IAItC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAS3C,SAAS,IAAI,eAAe;IAM5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,SAAS,IAAI,OAAO;IAIpB,UAAU,IAAI,KAAK;IAInB,SAAS,IAAI,KAAK;CAGnB;AAED,wBAAgB,qBAAqB,IAAI,cAAc,CAEtD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,cAAc,CAE9F;AAED;;mCAEmC;AACnC,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,CAOjF"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { $applyNodeReplacement, $createParagraphNode, createCommand, ElementNode, } from 'lexical';
|
|
2
|
+
import { addClassNamesToElement } from '@lexical/utils';
|
|
3
|
+
/** Payload: how many (equal-width) columns to create. Ratio support beyond
|
|
4
|
+
* equal widths is deferred (lexical-editor-spec.md §4.9 mentions it as a
|
|
5
|
+
* "configurable" possibility, not a committed requirement for Phase 3). */
|
|
6
|
+
export const INSERT_LAYOUT_COMMAND = createCommand('INSERT_LAYOUT_COMMAND');
|
|
7
|
+
/** A multi-column row (editor-architecture-design.md §3.2): an ElementNode
|
|
8
|
+
* holding one LayoutItemNode per column, laid out with CSS grid. Reused as
|
|
9
|
+
* the structural basis for two-column poetry layouts (spec §4.11). */
|
|
10
|
+
export class LayoutContainerNode extends ElementNode {
|
|
11
|
+
constructor(templateColumns = 'repeat(2, 1fr)', key) {
|
|
12
|
+
super(key);
|
|
13
|
+
this.__templateColumns = templateColumns;
|
|
14
|
+
}
|
|
15
|
+
static getType() {
|
|
16
|
+
return 'layout-container';
|
|
17
|
+
}
|
|
18
|
+
static clone(node) {
|
|
19
|
+
return new LayoutContainerNode(node.__templateColumns, node.__key);
|
|
20
|
+
}
|
|
21
|
+
getTemplateColumns() {
|
|
22
|
+
return this.getLatest().__templateColumns;
|
|
23
|
+
}
|
|
24
|
+
setTemplateColumns(templateColumns) {
|
|
25
|
+
const writable = this.getWritable();
|
|
26
|
+
writable.__templateColumns = templateColumns;
|
|
27
|
+
return writable;
|
|
28
|
+
}
|
|
29
|
+
static importJSON(serializedNode) {
|
|
30
|
+
return $createLayoutContainerNode(serializedNode.templateColumns);
|
|
31
|
+
}
|
|
32
|
+
exportJSON() {
|
|
33
|
+
return { ...super.exportJSON(), type: 'layout-container', version: 1, templateColumns: this.getTemplateColumns() };
|
|
34
|
+
}
|
|
35
|
+
static importDOM() {
|
|
36
|
+
return {
|
|
37
|
+
div: (domNode) => {
|
|
38
|
+
if (!domNode.hasAttribute('data-likhari-layout-container'))
|
|
39
|
+
return null;
|
|
40
|
+
const templateColumns = domNode.style.gridTemplateColumns || undefined;
|
|
41
|
+
return { conversion: () => ({ node: $createLayoutContainerNode(templateColumns) }), priority: 2 };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
exportDOM() {
|
|
46
|
+
const element = document.createElement('div');
|
|
47
|
+
element.setAttribute('data-likhari-layout-container', '');
|
|
48
|
+
element.style.display = 'grid';
|
|
49
|
+
element.style.gridTemplateColumns = this.getTemplateColumns();
|
|
50
|
+
return { element };
|
|
51
|
+
}
|
|
52
|
+
createDOM(config) {
|
|
53
|
+
const element = document.createElement('div');
|
|
54
|
+
addClassNamesToElement(element, config.theme.layoutContainer ?? 'likhari-layout-container');
|
|
55
|
+
element.style.gridTemplateColumns = this.__templateColumns;
|
|
56
|
+
return element;
|
|
57
|
+
}
|
|
58
|
+
updateDOM(prevNode, dom) {
|
|
59
|
+
if (prevNode.__templateColumns !== this.__templateColumns) {
|
|
60
|
+
dom.style.gridTemplateColumns = this.__templateColumns;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
canBeEmpty() {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
isShadowRoot() {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function $createLayoutContainerNode(templateColumns = 'repeat(2, 1fr)') {
|
|
72
|
+
return $applyNodeReplacement(new LayoutContainerNode(templateColumns));
|
|
73
|
+
}
|
|
74
|
+
export function $isLayoutContainerNode(node) {
|
|
75
|
+
return node instanceof LayoutContainerNode;
|
|
76
|
+
}
|
|
77
|
+
/** One column's content area inside a LayoutContainerNode. Always keeps at
|
|
78
|
+
* least an empty paragraph (mirrors TableCellNode), so a column can never
|
|
79
|
+
* collapse to an unselectable, unfocusable void. */
|
|
80
|
+
export class LayoutItemNode extends ElementNode {
|
|
81
|
+
static getType() {
|
|
82
|
+
return 'layout-item';
|
|
83
|
+
}
|
|
84
|
+
static clone(node) {
|
|
85
|
+
return new LayoutItemNode(node.__key);
|
|
86
|
+
}
|
|
87
|
+
static importJSON() {
|
|
88
|
+
return $createLayoutItemNode();
|
|
89
|
+
}
|
|
90
|
+
exportJSON() {
|
|
91
|
+
return { ...super.exportJSON(), type: 'layout-item', version: 1 };
|
|
92
|
+
}
|
|
93
|
+
static importDOM() {
|
|
94
|
+
return {
|
|
95
|
+
div: (domNode) => {
|
|
96
|
+
if (!domNode.hasAttribute('data-likhari-layout-item'))
|
|
97
|
+
return null;
|
|
98
|
+
return { conversion: () => ({ node: $createLayoutItemNode() }), priority: 2 };
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
exportDOM() {
|
|
103
|
+
const element = document.createElement('div');
|
|
104
|
+
element.setAttribute('data-likhari-layout-item', '');
|
|
105
|
+
return { element };
|
|
106
|
+
}
|
|
107
|
+
createDOM(config) {
|
|
108
|
+
const element = document.createElement('div');
|
|
109
|
+
addClassNamesToElement(element, config.theme.layoutItem ?? 'likhari-layout-item');
|
|
110
|
+
return element;
|
|
111
|
+
}
|
|
112
|
+
updateDOM() {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
canBeEmpty() {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
canIndent() {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function $createLayoutItemNode() {
|
|
123
|
+
return $applyNodeReplacement(new LayoutItemNode());
|
|
124
|
+
}
|
|
125
|
+
export function $isLayoutItemNode(node) {
|
|
126
|
+
return node instanceof LayoutItemNode;
|
|
127
|
+
}
|
|
128
|
+
/** Builds a container with `columnCount` empty items — the shape every
|
|
129
|
+
* LAYOUT insert produces, and what the node-transform below re-normalizes
|
|
130
|
+
* a malformed tree back toward. */
|
|
131
|
+
export function $createLayoutWithColumns(columnCount) {
|
|
132
|
+
const count = Math.max(2, Math.min(6, Math.round(columnCount)));
|
|
133
|
+
const container = $createLayoutContainerNode(`repeat(${count}, 1fr)`);
|
|
134
|
+
for (let i = 0; i < count; i++) {
|
|
135
|
+
container.append($createLayoutItemNode().append($createParagraphNode()));
|
|
136
|
+
}
|
|
137
|
+
return container;
|
|
138
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LayoutContainerNode, LayoutItemNode } from './LayoutNode.js';
|
|
2
|
+
/** Inserts a `columnCount`-column layout right after the selection's
|
|
3
|
+
* top-level block, and focuses its first column. Returns false (so the
|
|
4
|
+
* command falls through) when there's no usable selection to anchor on. */
|
|
5
|
+
export declare function $insertLayout(columnCount: number): boolean;
|
|
6
|
+
/** Keeps LayoutContainerNode/LayoutItemNode nesting valid against malformed
|
|
7
|
+
* trees from paste, undo or JSON import — a LayoutItemNode can only live
|
|
8
|
+
* directly inside a LayoutContainerNode and vice versa. */
|
|
9
|
+
export declare function $normalizeLayoutItem(node: LayoutItemNode): void;
|
|
10
|
+
/** See $normalizeLayoutItem — the container-side half of the same rule. */
|
|
11
|
+
export declare function $normalizeLayoutContainer(node: LayoutContainerNode): void;
|
|
12
|
+
//# sourceMappingURL=layoutActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layoutActions.d.ts","sourceRoot":"","sources":["../../src/blocks/layoutActions.ts"],"names":[],"mappings":"AACA,OAAO,EAAuE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAExI;;2EAE2E;AAC3E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAQ1D;AAED;;2DAE2D;AAC3D,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAM/D;AAED,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CASzE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
2
|
+
import { $createLayoutWithColumns, $isLayoutContainerNode, $isLayoutItemNode } from './LayoutNode.js';
|
|
3
|
+
/** Inserts a `columnCount`-column layout right after the selection's
|
|
4
|
+
* top-level block, and focuses its first column. Returns false (so the
|
|
5
|
+
* command falls through) when there's no usable selection to anchor on. */
|
|
6
|
+
export function $insertLayout(columnCount) {
|
|
7
|
+
const selection = $getSelection();
|
|
8
|
+
if (!$isRangeSelection(selection))
|
|
9
|
+
return false;
|
|
10
|
+
const anchorTopLevel = selection.anchor.getNode().getTopLevelElementOrThrow();
|
|
11
|
+
const container = $createLayoutWithColumns(columnCount);
|
|
12
|
+
anchorTopLevel.insertAfter(container);
|
|
13
|
+
container.getFirstDescendant()?.selectStart();
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
/** Keeps LayoutContainerNode/LayoutItemNode nesting valid against malformed
|
|
17
|
+
* trees from paste, undo or JSON import — a LayoutItemNode can only live
|
|
18
|
+
* directly inside a LayoutContainerNode and vice versa. */
|
|
19
|
+
export function $normalizeLayoutItem(node) {
|
|
20
|
+
const parent = node.getParent();
|
|
21
|
+
if ($isLayoutContainerNode(parent))
|
|
22
|
+
return;
|
|
23
|
+
// Orphaned: unwrap into plain content rather than leaving a stray node.
|
|
24
|
+
for (const child of node.getChildren())
|
|
25
|
+
node.insertBefore(child);
|
|
26
|
+
node.remove();
|
|
27
|
+
}
|
|
28
|
+
/** See $normalizeLayoutItem — the container-side half of the same rule. */
|
|
29
|
+
export function $normalizeLayoutContainer(node) {
|
|
30
|
+
const children = node.getChildren();
|
|
31
|
+
if (children.length === 0) {
|
|
32
|
+
node.remove();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
for (const child of children) {
|
|
36
|
+
if (!$isLayoutItemNode(child))
|
|
37
|
+
child.remove();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface LayoutDialogValue {
|
|
2
|
+
columnCount: number;
|
|
3
|
+
}
|
|
4
|
+
export interface LayoutDialogProps {
|
|
5
|
+
opened: boolean;
|
|
6
|
+
onSubmit: (value: LayoutDialogValue) => void;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
/** Insert-columns dialog: how many equal-width columns to create. */
|
|
10
|
+
export declare function LayoutDialog({ opened, onSubmit, onClose }: LayoutDialogProps): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=LayoutDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LayoutDialog.d.ts","sourceRoot":"","sources":["../../src/components/LayoutDialog.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,iBAAiB,+BAmC5E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Button, Group, Modal, NumberInput } from '@mantine/core';
|
|
4
|
+
import { useUiStrings } from '../i18n/useStrings.js';
|
|
5
|
+
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
6
|
+
const MIN_COLUMNS = 2;
|
|
7
|
+
const MAX_COLUMNS = 6;
|
|
8
|
+
const DEFAULT_COLUMNS = 2;
|
|
9
|
+
/** Insert-columns dialog: how many equal-width columns to create. */
|
|
10
|
+
export function LayoutDialog({ opened, onSubmit, onClose }) {
|
|
11
|
+
const strings = useUiStrings();
|
|
12
|
+
const portalTarget = usePortalTarget();
|
|
13
|
+
const [columnCount, setColumnCount] = useState(DEFAULT_COLUMNS);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!opened)
|
|
16
|
+
return;
|
|
17
|
+
setColumnCount(DEFAULT_COLUMNS);
|
|
18
|
+
}, [opened]);
|
|
19
|
+
const handleSubmit = (e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
onSubmit({ columnCount });
|
|
22
|
+
};
|
|
23
|
+
return (_jsx(Modal, { opened: opened, onClose: onClose, title: strings.layoutDialog.title, centered: true, size: "sm", portalProps: { target: portalTarget }, children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(NumberInput, { label: strings.layoutDialog.columnCount, min: MIN_COLUMNS, max: MAX_COLUMNS, value: columnCount, onChange: (v) => setColumnCount(typeof v === 'number' ? v : DEFAULT_COLUMNS), "data-autofocus": true }), _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 })] })] }) }));
|
|
24
|
+
}
|
|
@@ -10,8 +10,8 @@ export interface TableDialogProps {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Insert-table dialog: rows x columns and an optional header row. Cell
|
|
13
|
-
* merge/split is
|
|
14
|
-
* table
|
|
13
|
+
* merge/split is a selection-time action (table context menu), not a
|
|
14
|
+
* dimension of the table at creation, so it has no place in this dialog.
|
|
15
15
|
*/
|
|
16
16
|
export declare function TableDialog({ opened, onSubmit, onClose }: TableDialogProps): import("react").JSX.Element;
|
|
17
17
|
//# sourceMappingURL=TableDialog.d.ts.map
|
|
@@ -9,8 +9,8 @@ const DEFAULT_ROWS = 3;
|
|
|
9
9
|
const DEFAULT_COLUMNS = 3;
|
|
10
10
|
/**
|
|
11
11
|
* Insert-table dialog: rows x columns and an optional header row. Cell
|
|
12
|
-
* merge/split is
|
|
13
|
-
* table
|
|
12
|
+
* merge/split is a selection-time action (table context menu), not a
|
|
13
|
+
* dimension of the table at creation, so it has no place in this dialog.
|
|
14
14
|
*/
|
|
15
15
|
export function TableDialog({ opened, onSubmit, onClose }) {
|
|
16
16
|
const strings = useUiStrings();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA6DA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA0avD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAw9BxJ"}
|
|
@@ -12,9 +12,11 @@ import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontal
|
|
|
12
12
|
import { ImageDialog } from '../image/ImageDialog.js';
|
|
13
13
|
import { $createImageNode } from '../image/ImageNode.js';
|
|
14
14
|
import { INSERT_PAGE_BREAK_COMMAND } from '../blocks/PageBreakNode.js';
|
|
15
|
+
import { INSERT_LAYOUT_COMMAND } from '../blocks/LayoutNode.js';
|
|
15
16
|
import { TableDialog } from './TableDialog.js';
|
|
17
|
+
import { LayoutDialog } from './LayoutDialog.js';
|
|
16
18
|
import { $getTableCellNodeFromLexicalNode, $isTableSelection, INSERT_TABLE_COMMAND } from '@lexical/table';
|
|
17
|
-
import { $deleteTable, $deleteTableColumns, $deleteTableRows, $getTableSelectionSize, $insertTableColumns, $insertTableRows, } from '../table/tableActions.js';
|
|
19
|
+
import { $canMergeSelectedCells, $canUnmergeSelectedCell, $deleteTable, $deleteTableColumns, $deleteTableRows, $getTableSelectionSize, $insertTableColumns, $insertTableRows, $mergeTableCells, $unmergeTableCell, } from '../table/tableActions.js';
|
|
18
20
|
import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
|
|
19
21
|
import { LinkDialog } from './LinkDialog.js';
|
|
20
22
|
import { DraftsDialog } from './DraftsDialog.js';
|
|
@@ -23,7 +25,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fo
|
|
|
23
25
|
import { setStyleProperty } from '../utils/style.js';
|
|
24
26
|
import { useStrings } from '../i18n/useStrings.js';
|
|
25
27
|
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
26
|
-
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconTable, IconTableMinus, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
28
|
+
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconTable, IconTableMinus, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
27
29
|
/** Sentinel for "no inline style": Lexical returns the default it is given when
|
|
28
30
|
* the selection has none, so this tells "unstyled" (show the canvas default)
|
|
29
31
|
* apart from "styled with a value the dropdown doesn't list" (show nothing). */
|
|
@@ -40,12 +42,14 @@ const INITIAL_STATE = {
|
|
|
40
42
|
inTable: false,
|
|
41
43
|
tableRows: 1,
|
|
42
44
|
tableColumns: 1,
|
|
45
|
+
canMergeCells: false,
|
|
46
|
+
canUnmergeCell: false,
|
|
43
47
|
canUndo: false,
|
|
44
48
|
canRedo: false,
|
|
45
49
|
};
|
|
46
50
|
const ICON_SIZE = 17;
|
|
47
51
|
const ICON_STROKE = 1.75;
|
|
48
|
-
const
|
|
52
|
+
const ALIGN_ICONS_LTR = {
|
|
49
53
|
start: IconAlignLeft,
|
|
50
54
|
left: IconAlignLeft,
|
|
51
55
|
center: IconAlignCenter,
|
|
@@ -53,6 +57,14 @@ const ALIGN_ICONS = {
|
|
|
53
57
|
right: IconAlignRight,
|
|
54
58
|
justify: IconAlignJustified,
|
|
55
59
|
};
|
|
60
|
+
// UI spec §5: "start"/"end" are direction-relative — in RTL, start points
|
|
61
|
+
// right, so the icon has to swap glyphs. "left"/"right" are the poetry
|
|
62
|
+
// preset's literal per-couplet overrides and never mirror.
|
|
63
|
+
const ALIGN_ICONS_RTL = {
|
|
64
|
+
...ALIGN_ICONS_LTR,
|
|
65
|
+
start: IconAlignRight,
|
|
66
|
+
end: IconAlignLeft,
|
|
67
|
+
};
|
|
56
68
|
const HEADING_ICONS = {
|
|
57
69
|
1: IconH1,
|
|
58
70
|
2: IconH2,
|
|
@@ -131,7 +143,7 @@ function LinkMenuItems({ url, onEdit, onRemove, strings }) {
|
|
|
131
143
|
/** The table actions menu's contents — shared by the toolbar button and the
|
|
132
144
|
* right-click menu. Each acts on the selected cell, or on every row/column a
|
|
133
145
|
* multi-cell selection covers. */
|
|
134
|
-
function TableMenuItems({ strings, direction, rows, columns, onAction, }) {
|
|
146
|
+
function TableMenuItems({ strings, direction, rows, columns, canMergeCells, canUnmergeCell, onAction, }) {
|
|
135
147
|
const t = strings.tableMenu;
|
|
136
148
|
// "Before" is the start side, which is on the right in an RTL table.
|
|
137
149
|
const rtl = direction === 'rtl';
|
|
@@ -141,7 +153,7 @@ function TableMenuItems({ strings, direction, rows, columns, onAction, }) {
|
|
|
141
153
|
const Icon = icon;
|
|
142
154
|
return (_jsx(Menu.Item, { color: color, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onAction(action), children: label }));
|
|
143
155
|
};
|
|
144
|
-
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconRowInsertTop, manyRows ? t.insertRowsBefore : t.insertRowBefore, () => $insertTableRows(false)), item(IconRowInsertBottom, manyRows ? t.insertRowsAfter : t.insertRowAfter, () => $insertTableRows(true)), item(rtl ? IconColumnInsertRight : IconColumnInsertLeft, manyColumns ? t.insertColumnsBefore : t.insertColumnBefore, () => $insertTableColumns(false)), item(rtl ? IconColumnInsertLeft : IconColumnInsertRight, manyColumns ? t.insertColumnsAfter : t.insertColumnAfter, () => $insertTableColumns(true)), _jsx(Menu.Divider, {}), item(IconRowRemove, manyRows ? t.deleteRows : t.deleteRow, $deleteTableRows), item(IconColumnRemove, manyColumns ? t.deleteColumns : t.deleteColumn, $deleteTableColumns), item(IconTableMinus, t.deleteTable, $deleteTable, 'red')] }));
|
|
156
|
+
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconRowInsertTop, manyRows ? t.insertRowsBefore : t.insertRowBefore, () => $insertTableRows(false)), item(IconRowInsertBottom, manyRows ? t.insertRowsAfter : t.insertRowAfter, () => $insertTableRows(true)), item(rtl ? IconColumnInsertRight : IconColumnInsertLeft, manyColumns ? t.insertColumnsBefore : t.insertColumnBefore, () => $insertTableColumns(false)), item(rtl ? IconColumnInsertLeft : IconColumnInsertRight, manyColumns ? t.insertColumnsAfter : t.insertColumnAfter, () => $insertTableColumns(true)), (canMergeCells || canUnmergeCell) && _jsx(Menu.Divider, {}), canMergeCells && item(IconArrowMergeBoth, t.mergeCells, $mergeTableCells), canUnmergeCell && item(IconArrowsSplit2, t.unmergeCell, $unmergeTableCell), _jsx(Menu.Divider, {}), item(IconRowRemove, manyRows ? t.deleteRows : t.deleteRow, $deleteTableRows), item(IconColumnRemove, manyColumns ? t.deleteColumns : t.deleteColumn, $deleteTableColumns), item(IconTableMinus, t.deleteTable, $deleteTable, 'red')] }));
|
|
145
157
|
}
|
|
146
158
|
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
147
159
|
const [editor] = useLexicalComposerContext();
|
|
@@ -149,6 +161,8 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
149
161
|
const strings = useStrings(locale);
|
|
150
162
|
const portalTarget = usePortalTarget();
|
|
151
163
|
const [draftsOpen, setDraftsOpen] = useState(false);
|
|
164
|
+
const rtl = direction === 'rtl';
|
|
165
|
+
const ALIGN_ICONS = rtl ? ALIGN_ICONS_RTL : ALIGN_ICONS_LTR;
|
|
152
166
|
// Responsive "priority+" overflow: the toolbar's outer container and a
|
|
153
167
|
// hidden nowrap clone (rendered with every movable group forced inline)
|
|
154
168
|
// are both measured; whenever the clone's natural width exceeds the
|
|
@@ -164,7 +178,8 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
164
178
|
// A selection of several table cells isn't a range selection.
|
|
165
179
|
if ($isTableSelection(selection)) {
|
|
166
180
|
const { rows, columns } = $getTableSelectionSize();
|
|
167
|
-
|
|
181
|
+
const canMergeCells = $canMergeSelectedCells();
|
|
182
|
+
setState((s) => ({ ...s, inTable: true, tableRows: rows, tableColumns: columns, canMergeCells, canUnmergeCell: false }));
|
|
168
183
|
return;
|
|
169
184
|
}
|
|
170
185
|
if (!$isRangeSelection(selection))
|
|
@@ -172,6 +187,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
172
187
|
const anchorNode = selection.anchor.getNode();
|
|
173
188
|
const inTable = $getTableCellNodeFromLexicalNode(anchorNode) !== null;
|
|
174
189
|
const tableSize = inTable ? $getTableSelectionSize() : { rows: 1, columns: 1 };
|
|
190
|
+
const canUnmergeCell = inTable && $canUnmergeSelectedCell();
|
|
175
191
|
const element = anchorNode.getKey() === 'root' ? anchorNode : (anchorNode.getTopLevelElement() ?? anchorNode);
|
|
176
192
|
const listParent = $findMatchingParent(anchorNode, $isListNode);
|
|
177
193
|
const listType = listParent && $isListNode(listParent) ? listParent.getListType() : null;
|
|
@@ -210,6 +226,8 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
210
226
|
inTable,
|
|
211
227
|
tableRows: tableSize.rows,
|
|
212
228
|
tableColumns: tableSize.columns,
|
|
229
|
+
canMergeCells: false,
|
|
230
|
+
canUnmergeCell,
|
|
213
231
|
}));
|
|
214
232
|
});
|
|
215
233
|
}, [editor]);
|
|
@@ -329,6 +347,22 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
329
347
|
});
|
|
330
348
|
closeTableDialog();
|
|
331
349
|
};
|
|
350
|
+
const [layoutDialogOpen, setLayoutDialogOpen] = useState(false);
|
|
351
|
+
const openLayoutDialog = () => {
|
|
352
|
+
snapshotSelection();
|
|
353
|
+
setLayoutDialogOpen(true);
|
|
354
|
+
};
|
|
355
|
+
const closeLayoutDialog = () => {
|
|
356
|
+
setLayoutDialogOpen(false);
|
|
357
|
+
editor.focus();
|
|
358
|
+
};
|
|
359
|
+
const insertLayout = ({ columnCount }) => {
|
|
360
|
+
const saved = menuSelectionRef.current;
|
|
361
|
+
if (saved)
|
|
362
|
+
editor.update(() => $setSelection(saved.clone()), { discrete: true });
|
|
363
|
+
editor.dispatchCommand(INSERT_LAYOUT_COMMAND, { columnCount });
|
|
364
|
+
closeLayoutDialog();
|
|
365
|
+
};
|
|
332
366
|
/** Applies a font property per `config.font.scope`: to the selection, to
|
|
333
367
|
* every text node in the document, or (for 'both') to the selection when
|
|
334
368
|
* there is a range and to the whole document when there isn't. */
|
|
@@ -531,6 +565,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
531
565
|
config.tables ||
|
|
532
566
|
config.blocks.horizontalRule ||
|
|
533
567
|
config.blocks.pageBreak ||
|
|
568
|
+
config.columns ||
|
|
534
569
|
config.poetry.enabled;
|
|
535
570
|
const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
|
|
536
571
|
// The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
|
|
@@ -573,17 +608,19 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
573
608
|
if (fmt.clearFormatting) {
|
|
574
609
|
scriptCleanupItems.push({ key: 'clear', icon: IconClearFormatting, label: strings.toolbar.clearFormatting, onClick: clearFormatting });
|
|
575
610
|
}
|
|
611
|
+
// UI spec §5: indent/outdent arrows are direction-relative and swap in RTL
|
|
612
|
+
// (outdent moves content toward the start edge, which is the right in RTL).
|
|
576
613
|
const indentItems = config.indent
|
|
577
614
|
? [
|
|
578
615
|
{
|
|
579
616
|
key: 'outdent',
|
|
580
|
-
icon: IconIndentDecrease,
|
|
617
|
+
icon: rtl ? IconIndentIncrease : IconIndentDecrease,
|
|
581
618
|
label: strings.toolbar.outdent,
|
|
582
619
|
onClick: () => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined),
|
|
583
620
|
},
|
|
584
621
|
{
|
|
585
622
|
key: 'indent',
|
|
586
|
-
icon: IconIndentIncrease,
|
|
623
|
+
icon: rtl ? IconIndentDecrease : IconIndentIncrease,
|
|
587
624
|
label: strings.toolbar.indent,
|
|
588
625
|
onClick: () => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined),
|
|
589
626
|
},
|
|
@@ -701,8 +738,8 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
701
738
|
restoreSelection();
|
|
702
739
|
openLinkDialog();
|
|
703
740
|
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
|
|
704
|
-
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.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
|
|
705
|
-
config.tables && state.inTable && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.tableOptions, title: strings.toolbar.tableOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconTableOptions, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, onAction: runTableAction }) })] }) }, "tableActions")),
|
|
741
|
+
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.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
|
|
742
|
+
config.tables && state.inTable && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.tableOptions, title: strings.toolbar.tableOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconTableOptions, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runTableAction }) })] }) }, "tableActions")),
|
|
706
743
|
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")),
|
|
707
744
|
];
|
|
708
745
|
return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
|
|
@@ -718,7 +755,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
718
755
|
}, onRemove: removeLink, strings: strings }) })] })), config.tables && (_jsxs(Menu, { opened: tableContext !== null, onChange: (opened) => {
|
|
719
756
|
if (!opened)
|
|
720
757
|
setTableContext(null);
|
|
721
|
-
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: tableContext?.x ?? -9999, top: tableContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: tableContext?.rows ?? 1, columns: tableContext?.columns ?? 1, onAction: runTableAction }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
|
|
758
|
+
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: tableContext?.x ?? -9999, top: tableContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: tableContext?.rows ?? 1, columns: tableContext?.columns ?? 1, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runTableAction }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
|
|
722
759
|
position: 'absolute',
|
|
723
760
|
visibility: 'hidden',
|
|
724
761
|
pointerEvents: 'none',
|
|
@@ -727,5 +764,5 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
727
764
|
display: 'flex',
|
|
728
765
|
flexWrap: 'nowrap',
|
|
729
766
|
gap: 8,
|
|
730
|
-
}, 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 })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
|
|
767
|
+
}, 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.columns && _jsx(LayoutDialog, { opened: layoutDialogOpen, onSubmit: insertLayout, onClose: closeLayoutDialog }), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
|
|
731
768
|
}
|
package/dist/i18n/strings.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ export interface Strings {
|
|
|
46
46
|
insertImage: string;
|
|
47
47
|
insertPageBreak: string;
|
|
48
48
|
insertTable: string;
|
|
49
|
+
insertColumns: string;
|
|
49
50
|
insertHorizontalRule: string;
|
|
50
51
|
poetryBlocks: string;
|
|
51
52
|
comingSoon: (label: string) => string;
|
|
@@ -131,6 +132,8 @@ export interface Strings {
|
|
|
131
132
|
insertColumnsAfter: string;
|
|
132
133
|
deleteRows: string;
|
|
133
134
|
deleteColumns: string;
|
|
135
|
+
mergeCells: string;
|
|
136
|
+
unmergeCell: string;
|
|
134
137
|
};
|
|
135
138
|
link: {
|
|
136
139
|
menuLabel: string;
|
|
@@ -154,6 +157,10 @@ export interface Strings {
|
|
|
154
157
|
columns: string;
|
|
155
158
|
headerRow: string;
|
|
156
159
|
};
|
|
160
|
+
layoutDialog: {
|
|
161
|
+
title: string;
|
|
162
|
+
columnCount: string;
|
|
163
|
+
};
|
|
157
164
|
imageDialog: {
|
|
158
165
|
titleInsert: string;
|
|
159
166
|
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":"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"}
|
package/dist/i18n/strings.js
CHANGED
|
@@ -37,6 +37,7 @@ const en = {
|
|
|
37
37
|
insertImage: 'Insert image',
|
|
38
38
|
insertPageBreak: 'Insert page break',
|
|
39
39
|
insertTable: 'Insert table',
|
|
40
|
+
insertColumns: 'Insert columns',
|
|
40
41
|
insertHorizontalRule: 'Insert horizontal rule',
|
|
41
42
|
poetryBlocks: 'Poetry blocks',
|
|
42
43
|
comingSoon: (label) => `${label} (coming soon)`,
|
|
@@ -115,6 +116,8 @@ const en = {
|
|
|
115
116
|
insertColumnsAfter: 'Insert columns after',
|
|
116
117
|
deleteRows: 'Delete rows',
|
|
117
118
|
deleteColumns: 'Delete columns',
|
|
119
|
+
mergeCells: 'Merge cells',
|
|
120
|
+
unmergeCell: 'Unmerge cell',
|
|
118
121
|
},
|
|
119
122
|
link: {
|
|
120
123
|
menuLabel: 'Link',
|
|
@@ -138,6 +141,10 @@ const en = {
|
|
|
138
141
|
columns: 'Columns',
|
|
139
142
|
headerRow: 'Header row',
|
|
140
143
|
},
|
|
144
|
+
layoutDialog: {
|
|
145
|
+
title: 'Insert columns',
|
|
146
|
+
columnCount: 'Number of columns',
|
|
147
|
+
},
|
|
141
148
|
imageDialog: {
|
|
142
149
|
titleInsert: 'Insert image',
|
|
143
150
|
titleEdit: 'Edit image',
|
|
@@ -230,6 +237,7 @@ const ur = {
|
|
|
230
237
|
insertImage: 'تصویر شامل کریں',
|
|
231
238
|
insertPageBreak: 'صفحے کی تقسیم شامل کریں',
|
|
232
239
|
insertTable: 'جدول شامل کریں',
|
|
240
|
+
insertColumns: 'کالم شامل کریں',
|
|
233
241
|
insertHorizontalRule: 'افقی لکیر شامل کریں',
|
|
234
242
|
poetryBlocks: 'شاعری کے بلاکس',
|
|
235
243
|
comingSoon: (label) => `${label} (جلد آ رہا ہے)`,
|
|
@@ -308,6 +316,8 @@ const ur = {
|
|
|
308
316
|
insertColumnsAfter: 'بعد میں کالمز شامل کریں',
|
|
309
317
|
deleteRows: 'قطاریں حذف کریں',
|
|
310
318
|
deleteColumns: 'کالمز حذف کریں',
|
|
319
|
+
mergeCells: 'خانے ضم کریں',
|
|
320
|
+
unmergeCell: 'خانہ الگ کریں',
|
|
311
321
|
},
|
|
312
322
|
link: {
|
|
313
323
|
menuLabel: 'لنک',
|
|
@@ -331,6 +341,10 @@ const ur = {
|
|
|
331
341
|
columns: 'کالم',
|
|
332
342
|
headerRow: 'سرِ فہرست قطار',
|
|
333
343
|
},
|
|
344
|
+
layoutDialog: {
|
|
345
|
+
title: 'کالم شامل کریں',
|
|
346
|
+
columnCount: 'کالموں کی تعداد',
|
|
347
|
+
},
|
|
334
348
|
imageDialog: {
|
|
335
349
|
titleInsert: 'تصویر شامل کریں',
|
|
336
350
|
titleEdit: 'تصویر میں ترمیم کریں',
|
|
@@ -423,6 +437,7 @@ const paShahmukhi = {
|
|
|
423
437
|
insertImage: 'تصویر پاؤ',
|
|
424
438
|
insertPageBreak: 'صفحے دی ونڈ پاؤ',
|
|
425
439
|
insertTable: 'ٹیبل پاؤ',
|
|
440
|
+
insertColumns: 'کالم پاؤ',
|
|
426
441
|
insertHorizontalRule: 'لیٹی لکیر پاؤ',
|
|
427
442
|
poetryBlocks: 'شاعری بلاک',
|
|
428
443
|
comingSoon: (label) => `${label} (چھیتی آ رہا اے)`,
|
|
@@ -501,6 +516,8 @@ const paShahmukhi = {
|
|
|
501
516
|
insertColumnsAfter: 'بعد وچ کالماں پاؤ',
|
|
502
517
|
deleteRows: 'قطاراں مٹاؤ',
|
|
503
518
|
deleteColumns: 'کالماں مٹاؤ',
|
|
519
|
+
mergeCells: 'خانے رلاؤ',
|
|
520
|
+
unmergeCell: 'خانہ الگ کرو',
|
|
504
521
|
},
|
|
505
522
|
link: {
|
|
506
523
|
menuLabel: 'لنک',
|
|
@@ -524,6 +541,10 @@ const paShahmukhi = {
|
|
|
524
541
|
columns: 'کالم',
|
|
525
542
|
headerRow: 'سرکڑی قطار',
|
|
526
543
|
},
|
|
544
|
+
layoutDialog: {
|
|
545
|
+
title: 'کالم پاؤ',
|
|
546
|
+
columnCount: 'کالماں دی گنتی',
|
|
547
|
+
},
|
|
527
548
|
imageDialog: {
|
|
528
549
|
titleInsert: 'تصویر پاؤ',
|
|
529
550
|
titleEdit: 'تصویر وچ تبدیلی کرو',
|
package/dist/nodes.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAUlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAc5C,CAAC"}
|
package/dist/nodes.js
CHANGED
|
@@ -5,6 +5,7 @@ import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
|
5
5
|
import { TableCellNode, TableNode, TableRowNode } from '@lexical/table';
|
|
6
6
|
import { ImageNode } from './image/ImageNode.js';
|
|
7
7
|
import { PageBreakNode } from './blocks/PageBreakNode.js';
|
|
8
|
+
import { LayoutContainerNode, LayoutItemNode } from './blocks/LayoutNode.js';
|
|
8
9
|
/**
|
|
9
10
|
* Node types registered on the editor at construction time. Per
|
|
10
11
|
* editor-architecture-design.md §2, node registration cannot be conditional
|
|
@@ -26,4 +27,6 @@ export const EDITOR_NODES = [
|
|
|
26
27
|
TableCellNode,
|
|
27
28
|
ImageNode,
|
|
28
29
|
PageBreakNode,
|
|
30
|
+
LayoutContainerNode,
|
|
31
|
+
LayoutItemNode,
|
|
29
32
|
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers INSERT_LAYOUT_COMMAND (mirrors PageBreakPlugin) and two node
|
|
3
|
+
* transforms that keep the container/item pairing structurally valid —
|
|
4
|
+
* needed because paste, undo and JSON import can all produce a tree the UI
|
|
5
|
+
* itself never would. The actual logic lives in blocks/layoutActions.ts so
|
|
6
|
+
* it's testable without mounting a React tree.
|
|
7
|
+
*/
|
|
8
|
+
export declare function LayoutPlugin(): null;
|
|
9
|
+
//# sourceMappingURL=LayoutPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LayoutPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/LayoutPlugin.tsx"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,wBAAgB,YAAY,SAgB3B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { mergeRegister } from '@lexical/utils';
|
|
4
|
+
import { COMMAND_PRIORITY_EDITOR } from 'lexical';
|
|
5
|
+
import { INSERT_LAYOUT_COMMAND, LayoutContainerNode, LayoutItemNode } from '../blocks/LayoutNode.js';
|
|
6
|
+
import { $insertLayout, $normalizeLayoutContainer, $normalizeLayoutItem } from '../blocks/layoutActions.js';
|
|
7
|
+
/**
|
|
8
|
+
* Registers INSERT_LAYOUT_COMMAND (mirrors PageBreakPlugin) and two node
|
|
9
|
+
* transforms that keep the container/item pairing structurally valid —
|
|
10
|
+
* needed because paste, undo and JSON import can all produce a tree the UI
|
|
11
|
+
* itself never would. The actual logic lives in blocks/layoutActions.ts so
|
|
12
|
+
* it's testable without mounting a React tree.
|
|
13
|
+
*/
|
|
14
|
+
export function LayoutPlugin() {
|
|
15
|
+
const [editor] = useLexicalComposerContext();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!editor.hasNodes([LayoutContainerNode, LayoutItemNode])) {
|
|
18
|
+
throw new Error('LayoutPlugin: LayoutContainerNode/LayoutItemNode not registered on the editor');
|
|
19
|
+
}
|
|
20
|
+
return mergeRegister(editor.registerCommand(INSERT_LAYOUT_COMMAND, ({ columnCount }) => $insertLayout(columnCount), COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(LayoutItemNode, $normalizeLayoutItem), editor.registerNodeTransform(LayoutContainerNode, $normalizeLayoutContainer));
|
|
21
|
+
}, [editor]);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -18,4 +18,17 @@ export declare function $deleteTableRows(): void;
|
|
|
18
18
|
export declare function $deleteTableColumns(): void;
|
|
19
19
|
/** Removes the whole table the selection is in. */
|
|
20
20
|
export declare function $deleteTable(): void;
|
|
21
|
+
/** True for a selection spanning more than one cell — the only shape a merge can apply to. */
|
|
22
|
+
export declare function $canMergeSelectedCells(): boolean;
|
|
23
|
+
/** True for a caret parked in a single cell that already spans more than one row/column. */
|
|
24
|
+
export declare function $canUnmergeSelectedCell(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Merges every cell the (multi-cell) selection spans into a single cell at
|
|
27
|
+
* its top-left corner, moving their content there and growing its colSpan/
|
|
28
|
+
* rowSpan to cover the vacated space. No-ops on an irregular selection shape
|
|
29
|
+
* (one that would cut an already-merged cell in half).
|
|
30
|
+
*/
|
|
31
|
+
export declare function $mergeTableCells(): void;
|
|
32
|
+
/** Splits the selected (already-merged) cell back into separate 1x1 cells. */
|
|
33
|
+
export declare function $unmergeTableCell(): void;
|
|
21
34
|
//# sourceMappingURL=tableActions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tableActions.d.ts","sourceRoot":"","sources":["../../src/table/tableActions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tableActions.d.ts","sourceRoot":"","sources":["../../src/table/tableActions.ts"],"names":[],"mappings":"AA8BA,0GAA0G;AAC1G,wBAAgB,sBAAsB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAS1E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAS3D;AAED;wEACwE;AACxE,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAQ9D;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,mDAAmD;AACnD,wBAAgB,YAAY,IAAI,IAAI,CAInC;AAED,8FAA8F;AAC9F,wBAAgB,sBAAsB,IAAI,OAAO,CAIhD;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,IAAI,OAAO,CAKjD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAgEvC;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $getTableCellNodeFromLexicalNode, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $insertTableColumn__EXPERIMENTAL, $insertTableRow__EXPERIMENTAL, $isTableCellNode, $isTableSelection, } from '@lexical/table';
|
|
2
|
-
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
1
|
+
import { $computeTableMap, $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $getTableCellNodeFromLexicalNode, $getTableCellNodeRect, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $insertTableColumn__EXPERIMENTAL, $insertTableRow__EXPERIMENTAL, $isTableCellNode, $isTableSelection, $unmergeCell, } from '@lexical/table';
|
|
2
|
+
import { $createParagraphNode, $getSelection, $isRangeSelection } from 'lexical';
|
|
3
3
|
/** Table cells the selection covers: the caret's own cell, or every cell of a
|
|
4
4
|
* multi-cell selection. Empty when the selection isn't in a table. */
|
|
5
5
|
function $selectedCells() {
|
|
@@ -69,3 +69,87 @@ export function $deleteTable() {
|
|
|
69
69
|
if (anchor)
|
|
70
70
|
$getTableNodeFromLexicalNodeOrThrow(anchor).remove();
|
|
71
71
|
}
|
|
72
|
+
/** True for a selection spanning more than one cell — the only shape a merge can apply to. */
|
|
73
|
+
export function $canMergeSelectedCells() {
|
|
74
|
+
const selection = $getSelection();
|
|
75
|
+
if (!$isTableSelection(selection))
|
|
76
|
+
return false;
|
|
77
|
+
return selection.getNodes().filter($isTableCellNode).length > 1;
|
|
78
|
+
}
|
|
79
|
+
/** True for a caret parked in a single cell that already spans more than one row/column. */
|
|
80
|
+
export function $canUnmergeSelectedCell() {
|
|
81
|
+
const selection = $getSelection();
|
|
82
|
+
if (!$isRangeSelection(selection))
|
|
83
|
+
return false;
|
|
84
|
+
const cell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
|
|
85
|
+
return cell !== null && (cell.getColSpan() > 1 || cell.getRowSpan() > 1);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Merges every cell the (multi-cell) selection spans into a single cell at
|
|
89
|
+
* its top-left corner, moving their content there and growing its colSpan/
|
|
90
|
+
* rowSpan to cover the vacated space. No-ops on an irregular selection shape
|
|
91
|
+
* (one that would cut an already-merged cell in half).
|
|
92
|
+
*/
|
|
93
|
+
export function $mergeTableCells() {
|
|
94
|
+
const selection = $getSelection();
|
|
95
|
+
if (!$isTableSelection(selection))
|
|
96
|
+
return;
|
|
97
|
+
if (selection.getNodes().filter($isTableCellNode).length < 2)
|
|
98
|
+
return;
|
|
99
|
+
const anchorCell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
|
|
100
|
+
const focusCell = $getTableCellNodeFromLexicalNode(selection.focus.getNode());
|
|
101
|
+
if (!anchorCell || !focusCell)
|
|
102
|
+
return;
|
|
103
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(anchorCell);
|
|
104
|
+
const [map, anchorValue, focusValue] = $computeTableMap(tableNode, anchorCell, focusCell);
|
|
105
|
+
// Bounding rectangle of the two endpoint cells, each expanded by its own
|
|
106
|
+
// span, so a selection that starts or ends on an already-merged cell still
|
|
107
|
+
// captures that cell's full extent.
|
|
108
|
+
const boundary = {
|
|
109
|
+
minRow: Math.min(anchorValue.startRow, focusValue.startRow),
|
|
110
|
+
minColumn: Math.min(anchorValue.startColumn, focusValue.startColumn),
|
|
111
|
+
maxRow: Math.max(anchorValue.startRow + anchorValue.cell.getRowSpan() - 1, focusValue.startRow + focusValue.cell.getRowSpan() - 1),
|
|
112
|
+
maxColumn: Math.max(anchorValue.startColumn + anchorValue.cell.getColSpan() - 1, focusValue.startColumn + focusValue.cell.getColSpan() - 1),
|
|
113
|
+
};
|
|
114
|
+
// Collect the distinct cell nodes the rectangle covers, bailing out if any
|
|
115
|
+
// of them extends past the rectangle (merging would silently truncate it).
|
|
116
|
+
const cellsInRect = new Set();
|
|
117
|
+
for (let r = boundary.minRow; r <= boundary.maxRow; r++) {
|
|
118
|
+
for (let c = boundary.minColumn; c <= boundary.maxColumn; c++) {
|
|
119
|
+
cellsInRect.add(map[r][c].cell);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const cell of cellsInRect) {
|
|
123
|
+
const rect = $getTableCellNodeRect(cell);
|
|
124
|
+
if (!rect)
|
|
125
|
+
continue;
|
|
126
|
+
const exceedsTop = rect.rowIndex < boundary.minRow;
|
|
127
|
+
const exceedsLeft = rect.columnIndex < boundary.minColumn;
|
|
128
|
+
const exceedsBottom = rect.rowIndex + rect.rowSpan - 1 > boundary.maxRow;
|
|
129
|
+
const exceedsRight = rect.columnIndex + rect.colSpan - 1 > boundary.maxColumn;
|
|
130
|
+
if (exceedsTop || exceedsLeft || exceedsBottom || exceedsRight)
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const topLeft = map[boundary.minRow][boundary.minColumn].cell;
|
|
134
|
+
for (const cell of cellsInRect) {
|
|
135
|
+
if (cell === topLeft)
|
|
136
|
+
continue;
|
|
137
|
+
for (const child of cell.getChildren()) {
|
|
138
|
+
topLeft.append(child);
|
|
139
|
+
}
|
|
140
|
+
const row = cell.getParent();
|
|
141
|
+
cell.remove();
|
|
142
|
+
if (row && row.getChildrenSize() === 0)
|
|
143
|
+
row.remove();
|
|
144
|
+
}
|
|
145
|
+
if (topLeft.getChildrenSize() === 0) {
|
|
146
|
+
topLeft.append($createParagraphNode());
|
|
147
|
+
}
|
|
148
|
+
topLeft.setRowSpan(boundary.maxRow - boundary.minRow + 1);
|
|
149
|
+
topLeft.setColSpan(boundary.maxColumn - boundary.minColumn + 1);
|
|
150
|
+
topLeft.selectEnd();
|
|
151
|
+
}
|
|
152
|
+
/** Splits the selected (already-merged) cell back into separate 1x1 cells. */
|
|
153
|
+
export function $unmergeTableCell() {
|
|
154
|
+
$unmergeCell();
|
|
155
|
+
}
|
package/dist/theme/editor.css
CHANGED
|
@@ -186,6 +186,25 @@
|
|
|
186
186
|
line-height: 2.3;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/* Per-block direction (requirements doc §3.2, "mixed-direction documents"):
|
|
190
|
+
Lexical detects each block's own direction from its first strong-direction
|
|
191
|
+
character and tags it with one of these theme classes independently of
|
|
192
|
+
the document's overall locale — an Urdu paragraph inside an English essay,
|
|
193
|
+
or an English heading inside an Urdu document, gets its own script-correct
|
|
194
|
+
font rather than inheriting the canvas-wide default. Set directly on the
|
|
195
|
+
block, so it wins over the inherited rule above regardless of specificity. */
|
|
196
|
+
.likhari-rtl {
|
|
197
|
+
font-family: 'Noto Nastaliq Urdu', serif;
|
|
198
|
+
font-size: 20px;
|
|
199
|
+
line-height: 2.3;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.likhari-ltr {
|
|
203
|
+
font-family: 'IBM Plex Serif', Georgia, serif;
|
|
204
|
+
font-size: 15px;
|
|
205
|
+
line-height: 1.75;
|
|
206
|
+
}
|
|
207
|
+
|
|
189
208
|
.likhari-placeholder {
|
|
190
209
|
position: absolute;
|
|
191
210
|
top: 20px;
|
|
@@ -298,10 +317,24 @@
|
|
|
298
317
|
}
|
|
299
318
|
}
|
|
300
319
|
|
|
301
|
-
/*
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
320
|
+
/* Columns layout (lexical-editor-spec.md §4.9) — a CSS grid row whose
|
|
321
|
+
column count/widths come from the node's own inline style, not this
|
|
322
|
+
stylesheet, so each container can differ. */
|
|
323
|
+
.likhari-layout-container {
|
|
324
|
+
display: grid;
|
|
325
|
+
gap: 24px;
|
|
326
|
+
margin: 0 0 1em 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.likhari-layout-item {
|
|
330
|
+
min-width: 0;
|
|
331
|
+
overflow-wrap: break-word;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.likhari-layout-item > :last-child {
|
|
335
|
+
margin-bottom: 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
305
338
|
.likhari-table {
|
|
306
339
|
border-collapse: collapse;
|
|
307
340
|
margin: 1em 0;
|
|
@@ -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,
|
|
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,kBA4CzB,CAAC"}
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* (the "headless + default theme" requirement, UI spec §10).
|
|
5
5
|
*/
|
|
6
6
|
export const editorTheme = {
|
|
7
|
+
// Applied by Lexical's reconciler to each top-level block based on that
|
|
8
|
+
// block's own detected direction — see the comment in editor.css. This is
|
|
9
|
+
// the per-block half of mixed-direction document support (requirements
|
|
10
|
+
// doc §3.2); the EditorRoot `dir` prop handles the whole-document default.
|
|
11
|
+
ltr: 'likhari-ltr',
|
|
12
|
+
rtl: 'likhari-rtl',
|
|
7
13
|
paragraph: 'likhari-paragraph',
|
|
8
14
|
quote: 'likhari-quote',
|
|
9
15
|
heading: {
|
|
@@ -34,6 +40,8 @@ export const editorTheme = {
|
|
|
34
40
|
hr: 'likhari-hr',
|
|
35
41
|
image: 'likhari-image-block',
|
|
36
42
|
pageBreak: 'likhari-page-break',
|
|
43
|
+
layoutContainer: 'likhari-layout-container',
|
|
44
|
+
layoutItem: 'likhari-layout-item',
|
|
37
45
|
table: 'likhari-table',
|
|
38
46
|
tableRow: 'likhari-table-row',
|
|
39
47
|
tableCell: 'likhari-table-cell',
|