@inshapardaz/likhari-react 0.1.16 → 0.1.18-dev.124
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 +3 -1
- package/dist/blocks/FootnoteListNode.d.ts +46 -0
- package/dist/blocks/FootnoteListNode.d.ts.map +1 -0
- package/dist/blocks/FootnoteListNode.js +117 -0
- package/dist/blocks/FootnoteNode.d.ts +38 -0
- package/dist/blocks/FootnoteNode.d.ts.map +1 -0
- package/dist/blocks/FootnoteNode.js +90 -0
- package/dist/blocks/PoetryNode.d.ts +42 -0
- package/dist/blocks/PoetryNode.d.ts.map +1 -0
- package/dist/blocks/PoetryNode.js +106 -0
- package/dist/blocks/footnoteActions.d.ts +9 -0
- package/dist/blocks/footnoteActions.d.ts.map +1 -0
- package/dist/blocks/footnoteActions.js +42 -0
- package/dist/blocks/poetryActions.d.ts +19 -0
- package/dist/blocks/poetryActions.d.ts.map +1 -0
- package/dist/blocks/poetryActions.js +76 -0
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +40 -8
- package/dist/i18n/strings.d.ts +9 -1
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +24 -3
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +7 -0
- package/dist/plugins/FootnotePlugin.d.ts +9 -0
- package/dist/plugins/FootnotePlugin.d.ts.map +1 -0
- package/dist/plugins/FootnotePlugin.js +51 -0
- package/dist/plugins/PoetryPlugin.d.ts +14 -0
- package/dist/plugins/PoetryPlugin.d.ts.map +1 -0
- package/dist/plugins/PoetryPlugin.js +27 -0
- package/dist/theme/editor.css +67 -0
- package/dist/theme/editorTheme.d.ts.map +1 -1
- package/dist/theme/editorTheme.js +4 -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;AAMjE,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,uGAoXrB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -22,6 +22,8 @@ import { injectUrduWebFontsCss } from './fonts/index.js';
|
|
|
22
22
|
import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
|
|
23
23
|
import { PageBreakPlugin } from './plugins/PageBreakPlugin.js';
|
|
24
24
|
import { LayoutPlugin } from './plugins/LayoutPlugin.js';
|
|
25
|
+
import { FootnotePlugin } from './plugins/FootnotePlugin.js';
|
|
26
|
+
import { PoetryPlugin } from './plugins/PoetryPlugin.js';
|
|
25
27
|
import { DraftRestore } from './components/DraftRestore.js';
|
|
26
28
|
import { LeaveDialog } from './components/LeaveDialog.js';
|
|
27
29
|
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
@@ -278,7 +280,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
278
280
|
const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
|
|
279
281
|
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
|
|
280
282
|
? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
|
|
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: () => {
|
|
283
|
+
: 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.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), 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: () => {
|
|
282
284
|
handleSave();
|
|
283
285
|
settleLeave(true);
|
|
284
286
|
}, onSaveDraft: () => {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalNode, type NodeKey, type SerializedElementNode, type Spread, ElementNode } from 'lexical';
|
|
2
|
+
export type SerializedFootnoteListNode = SerializedElementNode;
|
|
3
|
+
export type SerializedFootnoteItemNode = Spread<{
|
|
4
|
+
footnoteId: string;
|
|
5
|
+
}, SerializedElementNode>;
|
|
6
|
+
/** The document's single footnote list, rendered at the end (requirements
|
|
7
|
+
* doc §4.10). An ordered-list-like container of FootnoteItemNode; its items'
|
|
8
|
+
* displayed numbers are a CSS counter (editor.css), same mechanism as
|
|
9
|
+
* FootnoteReferenceNode, so both sequences stay in sync as long as items
|
|
10
|
+
* are appended in the same order their references were inserted — which
|
|
11
|
+
* FootnotePlugin's insert command always does. */
|
|
12
|
+
export declare class FootnoteListNode extends ElementNode {
|
|
13
|
+
static getType(): string;
|
|
14
|
+
static clone(node: FootnoteListNode): FootnoteListNode;
|
|
15
|
+
static importJSON(): FootnoteListNode;
|
|
16
|
+
exportJSON(): SerializedFootnoteListNode;
|
|
17
|
+
static importDOM(): DOMConversionMap | null;
|
|
18
|
+
exportDOM(): DOMExportOutput;
|
|
19
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
20
|
+
updateDOM(): boolean;
|
|
21
|
+
isShadowRoot(): boolean;
|
|
22
|
+
insertNewAfter(): null;
|
|
23
|
+
}
|
|
24
|
+
export declare function $createFootnoteListNode(): FootnoteListNode;
|
|
25
|
+
export declare function $isFootnoteListNode(node: LexicalNode | null | undefined): node is FootnoteListNode;
|
|
26
|
+
/** One entry in the FootnoteListNode — the editable note text for a given
|
|
27
|
+
* footnoteId. Always keeps at least an empty paragraph (mirrors
|
|
28
|
+
* TableCellNode/LayoutItemNode). */
|
|
29
|
+
export declare class FootnoteItemNode extends ElementNode {
|
|
30
|
+
__footnoteId: string;
|
|
31
|
+
constructor(footnoteId: string, key?: NodeKey);
|
|
32
|
+
static getType(): string;
|
|
33
|
+
static clone(node: FootnoteItemNode): FootnoteItemNode;
|
|
34
|
+
getFootnoteId(): string;
|
|
35
|
+
static importJSON(serializedNode: SerializedFootnoteItemNode): FootnoteItemNode;
|
|
36
|
+
exportJSON(): SerializedFootnoteItemNode;
|
|
37
|
+
static importDOM(): DOMConversionMap | null;
|
|
38
|
+
exportDOM(): DOMExportOutput;
|
|
39
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
40
|
+
updateDOM(): boolean;
|
|
41
|
+
canBeEmpty(): false;
|
|
42
|
+
canIndent(): false;
|
|
43
|
+
}
|
|
44
|
+
export declare function $createFootnoteItemNode(footnoteId: string): FootnoteItemNode;
|
|
45
|
+
export declare function $isFootnoteItemNode(node: LexicalNode | null | undefined): node is FootnoteItemNode;
|
|
46
|
+
//# sourceMappingURL=FootnoteListNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FootnoteListNode.d.ts","sourceRoot":"","sources":["../../src/blocks/FootnoteListNode.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACX,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,0BAA0B,GAAG,qBAAqB,CAAC;AAC/D,MAAM,MAAM,0BAA0B,GAAG,MAAM,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,qBAAqB,CAAC,CAAC;AAE/F;;;;;kDAKkD;AAClD,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,GAAG,gBAAgB;IAItD,MAAM,CAAC,UAAU,IAAI,gBAAgB;IAIrC,UAAU,IAAI,0BAA0B;IAIxC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAS3C,SAAS,IAAI,eAAe;IAM5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,SAAS,IAAI,OAAO;IAIpB,YAAY,IAAI,OAAO;IAIvB,cAAc,IAAI,IAAI;CAGvB;AAED,wBAAgB,uBAAuB,IAAI,gBAAgB,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,gBAAgB,CAElG;AAED;;oCAEoC;AACpC,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,YAAY,EAAE,MAAM,CAAC;gBAET,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO;IAK7C,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,GAAG,gBAAgB;IAItD,aAAa,IAAI,MAAM;IAIvB,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,0BAA0B,GAAG,gBAAgB;IAI/E,UAAU,IAAI,0BAA0B;IAIxC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAU3C,SAAS,IAAI,eAAe;IAO5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAO5C,SAAS,IAAI,OAAO;IAIpB,UAAU,IAAI,KAAK;IAInB,SAAS,IAAI,KAAK;CAGnB;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAE5E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,gBAAgB,CAElG"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { addClassNamesToElement } from '@lexical/utils';
|
|
2
|
+
import { $applyNodeReplacement, ElementNode, } from 'lexical';
|
|
3
|
+
/** The document's single footnote list, rendered at the end (requirements
|
|
4
|
+
* doc §4.10). An ordered-list-like container of FootnoteItemNode; its items'
|
|
5
|
+
* displayed numbers are a CSS counter (editor.css), same mechanism as
|
|
6
|
+
* FootnoteReferenceNode, so both sequences stay in sync as long as items
|
|
7
|
+
* are appended in the same order their references were inserted — which
|
|
8
|
+
* FootnotePlugin's insert command always does. */
|
|
9
|
+
export class FootnoteListNode extends ElementNode {
|
|
10
|
+
static getType() {
|
|
11
|
+
return 'footnote-list';
|
|
12
|
+
}
|
|
13
|
+
static clone(node) {
|
|
14
|
+
return new FootnoteListNode(node.__key);
|
|
15
|
+
}
|
|
16
|
+
static importJSON() {
|
|
17
|
+
return $createFootnoteListNode();
|
|
18
|
+
}
|
|
19
|
+
exportJSON() {
|
|
20
|
+
return { ...super.exportJSON(), type: 'footnote-list', version: 1 };
|
|
21
|
+
}
|
|
22
|
+
static importDOM() {
|
|
23
|
+
return {
|
|
24
|
+
ol: (domNode) => {
|
|
25
|
+
if (!domNode.hasAttribute('data-likhari-footnote-list'))
|
|
26
|
+
return null;
|
|
27
|
+
return { conversion: () => ({ node: $createFootnoteListNode() }), priority: 2 };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
exportDOM() {
|
|
32
|
+
const element = document.createElement('ol');
|
|
33
|
+
element.setAttribute('data-likhari-footnote-list', '');
|
|
34
|
+
return { element };
|
|
35
|
+
}
|
|
36
|
+
createDOM(config) {
|
|
37
|
+
const element = document.createElement('ol');
|
|
38
|
+
addClassNamesToElement(element, config.theme.footnoteList ?? 'likhari-footnote-list');
|
|
39
|
+
return element;
|
|
40
|
+
}
|
|
41
|
+
updateDOM() {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
isShadowRoot() {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
insertNewAfter() {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function $createFootnoteListNode() {
|
|
52
|
+
return $applyNodeReplacement(new FootnoteListNode());
|
|
53
|
+
}
|
|
54
|
+
export function $isFootnoteListNode(node) {
|
|
55
|
+
return node instanceof FootnoteListNode;
|
|
56
|
+
}
|
|
57
|
+
/** One entry in the FootnoteListNode — the editable note text for a given
|
|
58
|
+
* footnoteId. Always keeps at least an empty paragraph (mirrors
|
|
59
|
+
* TableCellNode/LayoutItemNode). */
|
|
60
|
+
export class FootnoteItemNode extends ElementNode {
|
|
61
|
+
constructor(footnoteId, key) {
|
|
62
|
+
super(key);
|
|
63
|
+
this.__footnoteId = footnoteId;
|
|
64
|
+
}
|
|
65
|
+
static getType() {
|
|
66
|
+
return 'footnote-item';
|
|
67
|
+
}
|
|
68
|
+
static clone(node) {
|
|
69
|
+
return new FootnoteItemNode(node.__footnoteId, node.__key);
|
|
70
|
+
}
|
|
71
|
+
getFootnoteId() {
|
|
72
|
+
return this.getLatest().__footnoteId;
|
|
73
|
+
}
|
|
74
|
+
static importJSON(serializedNode) {
|
|
75
|
+
return $createFootnoteItemNode(serializedNode.footnoteId);
|
|
76
|
+
}
|
|
77
|
+
exportJSON() {
|
|
78
|
+
return { ...super.exportJSON(), type: 'footnote-item', version: 1, footnoteId: this.getFootnoteId() };
|
|
79
|
+
}
|
|
80
|
+
static importDOM() {
|
|
81
|
+
return {
|
|
82
|
+
li: (domNode) => {
|
|
83
|
+
const id = domNode.getAttribute('data-likhari-footnote-item');
|
|
84
|
+
if (!id)
|
|
85
|
+
return null;
|
|
86
|
+
return { conversion: () => ({ node: $createFootnoteItemNode(id) }), priority: 2 };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
exportDOM() {
|
|
91
|
+
const element = document.createElement('li');
|
|
92
|
+
element.setAttribute('id', `fn-${this.getFootnoteId()}`);
|
|
93
|
+
element.setAttribute('data-likhari-footnote-item', this.getFootnoteId());
|
|
94
|
+
return { element };
|
|
95
|
+
}
|
|
96
|
+
createDOM(config) {
|
|
97
|
+
const element = document.createElement('li');
|
|
98
|
+
addClassNamesToElement(element, config.theme.footnoteItem ?? 'likhari-footnote-item');
|
|
99
|
+
element.id = `fn-${this.getFootnoteId()}`;
|
|
100
|
+
return element;
|
|
101
|
+
}
|
|
102
|
+
updateDOM() {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
canBeEmpty() {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
canIndent() {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function $createFootnoteItemNode(footnoteId) {
|
|
113
|
+
return $applyNodeReplacement(new FootnoteItemNode(footnoteId));
|
|
114
|
+
}
|
|
115
|
+
export function $isFootnoteItemNode(node) {
|
|
116
|
+
return node instanceof FootnoteItemNode;
|
|
117
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import { DecoratorNode, type DOMConversionMap, type DOMExportOutput, type EditorConfig, type EditorState, type LexicalCommand, type LexicalNode, type NodeKey, type SerializedLexicalNode, type Spread } from 'lexical';
|
|
3
|
+
export declare const INSERT_FOOTNOTE_COMMAND: LexicalCommand<void>;
|
|
4
|
+
export type SerializedFootnoteReferenceNode = Spread<{
|
|
5
|
+
footnoteId: string;
|
|
6
|
+
}, SerializedLexicalNode>;
|
|
7
|
+
/** Inline marker in the text flow (requirements doc §4.10, architecture doc
|
|
8
|
+
* §3.3). Holds only an id — its displayed number is never stored, it's
|
|
9
|
+
* rendered by a CSS counter (editor.css) that counts these markers in DOM
|
|
10
|
+
* order, so insert/delete/reorder auto-renumbers with no JS bookkeeping.
|
|
11
|
+
* The note's actual content lives in a matching FootnoteItemNode inside the
|
|
12
|
+
* document's single FootnoteListNode, not here (same split as Lexical's own
|
|
13
|
+
* comments/annotations pattern: reference in the flow, content out-of-band). */
|
|
14
|
+
export declare class FootnoteReferenceNode extends DecoratorNode<ReactElement> {
|
|
15
|
+
__footnoteId: string;
|
|
16
|
+
constructor(footnoteId: string, key?: NodeKey);
|
|
17
|
+
static getType(): string;
|
|
18
|
+
static clone(node: FootnoteReferenceNode): FootnoteReferenceNode;
|
|
19
|
+
getFootnoteId(): string;
|
|
20
|
+
static importJSON(serializedNode: SerializedFootnoteReferenceNode): FootnoteReferenceNode;
|
|
21
|
+
exportJSON(): SerializedFootnoteReferenceNode;
|
|
22
|
+
static importDOM(): DOMConversionMap | null;
|
|
23
|
+
exportDOM(): DOMExportOutput;
|
|
24
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
25
|
+
updateDOM(): boolean;
|
|
26
|
+
getTextContent(): string;
|
|
27
|
+
isInline(): true;
|
|
28
|
+
decorate(): ReactElement;
|
|
29
|
+
}
|
|
30
|
+
export declare function $createFootnoteReferenceNode(footnoteId: string): FootnoteReferenceNode;
|
|
31
|
+
export declare function $isFootnoteReferenceNode(node: LexicalNode | null | undefined): node is FootnoteReferenceNode;
|
|
32
|
+
/** Resolves a reference's footnoteId from the editor state just before its
|
|
33
|
+
* deletion committed — a mutation listener's 'destroyed' entries only carry
|
|
34
|
+
* the NodeKey, and by the time the listener fires the *current* state no
|
|
35
|
+
* longer has the node, so the caller must pass the listener's own
|
|
36
|
+
* `prevEditorState` here to know which FootnoteItemNode to clean up. */
|
|
37
|
+
export declare function $getFootnoteIdByKey(prevEditorState: EditorState, key: NodeKey): string | null;
|
|
38
|
+
//# sourceMappingURL=FootnoteNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FootnoteNode.d.ts","sourceRoot":"","sources":["../../src/blocks/FootnoteNode.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAIL,aAAa,EAEb,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,IAAI,CAA4C,CAAC;AAEtG,MAAM,MAAM,+BAA+B,GAAG,MAAM,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,qBAAqB,CAAC,CAAC;AAEpG;;;;;;gFAMgF;AAChF,qBAAa,qBAAsB,SAAQ,aAAa,CAAC,YAAY,CAAC;IACpE,YAAY,EAAE,MAAM,CAAC;gBAET,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO;IAK7C,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,qBAAqB,GAAG,qBAAqB;IAIhE,aAAa,IAAI,MAAM;IAIvB,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,+BAA+B,GAAG,qBAAqB;IAIzF,UAAU,IAAI,+BAA+B;IAI7C,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAU3C,SAAS,IAAI,eAAe;IAU5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,SAAS,IAAI,OAAO;IAIpB,cAAc,IAAI,MAAM;IAIxB,QAAQ,IAAI,IAAI;IAIhB,QAAQ,IAAI,YAAY;CAKzB;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAEtF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,qBAAqB,CAE5G;AAED;;;;wEAIwE;AACxE,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAO7F"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { addClassNamesToElement } from '@lexical/utils';
|
|
3
|
+
import { $applyNodeReplacement, $getNodeByKey, createCommand, DecoratorNode, } from 'lexical';
|
|
4
|
+
export const INSERT_FOOTNOTE_COMMAND = createCommand('INSERT_FOOTNOTE_COMMAND');
|
|
5
|
+
/** Inline marker in the text flow (requirements doc §4.10, architecture doc
|
|
6
|
+
* §3.3). Holds only an id — its displayed number is never stored, it's
|
|
7
|
+
* rendered by a CSS counter (editor.css) that counts these markers in DOM
|
|
8
|
+
* order, so insert/delete/reorder auto-renumbers with no JS bookkeeping.
|
|
9
|
+
* The note's actual content lives in a matching FootnoteItemNode inside the
|
|
10
|
+
* document's single FootnoteListNode, not here (same split as Lexical's own
|
|
11
|
+
* comments/annotations pattern: reference in the flow, content out-of-band). */
|
|
12
|
+
export class FootnoteReferenceNode extends DecoratorNode {
|
|
13
|
+
constructor(footnoteId, key) {
|
|
14
|
+
super(key);
|
|
15
|
+
this.__footnoteId = footnoteId;
|
|
16
|
+
}
|
|
17
|
+
static getType() {
|
|
18
|
+
return 'footnote-reference';
|
|
19
|
+
}
|
|
20
|
+
static clone(node) {
|
|
21
|
+
return new FootnoteReferenceNode(node.__footnoteId, node.__key);
|
|
22
|
+
}
|
|
23
|
+
getFootnoteId() {
|
|
24
|
+
return this.getLatest().__footnoteId;
|
|
25
|
+
}
|
|
26
|
+
static importJSON(serializedNode) {
|
|
27
|
+
return $createFootnoteReferenceNode(serializedNode.footnoteId);
|
|
28
|
+
}
|
|
29
|
+
exportJSON() {
|
|
30
|
+
return { type: 'footnote-reference', version: 1, footnoteId: this.getFootnoteId() };
|
|
31
|
+
}
|
|
32
|
+
static importDOM() {
|
|
33
|
+
return {
|
|
34
|
+
sup: (domNode) => {
|
|
35
|
+
const id = domNode.getAttribute('data-likhari-footnote-ref');
|
|
36
|
+
if (!id)
|
|
37
|
+
return null;
|
|
38
|
+
return { conversion: () => ({ node: $createFootnoteReferenceNode(id) }), priority: 2 };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
exportDOM() {
|
|
43
|
+
const element = document.createElement('sup');
|
|
44
|
+
element.setAttribute('data-likhari-footnote-ref', this.getFootnoteId());
|
|
45
|
+
const anchor = document.createElement('a');
|
|
46
|
+
anchor.setAttribute('href', `#fn-${this.getFootnoteId()}`);
|
|
47
|
+
anchor.setAttribute('id', `fnref-${this.getFootnoteId()}`);
|
|
48
|
+
element.appendChild(anchor);
|
|
49
|
+
return { element };
|
|
50
|
+
}
|
|
51
|
+
createDOM(config) {
|
|
52
|
+
const element = document.createElement('sup');
|
|
53
|
+
addClassNamesToElement(element, config.theme.footnoteReference ?? 'likhari-footnote-ref');
|
|
54
|
+
return element;
|
|
55
|
+
}
|
|
56
|
+
updateDOM() {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
getTextContent() {
|
|
60
|
+
return `[^${this.getFootnoteId()}]`;
|
|
61
|
+
}
|
|
62
|
+
isInline() {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
decorate() {
|
|
66
|
+
// The bracketed number itself is a CSS ::after counter (editor.css) —
|
|
67
|
+
// nothing to render here but the counted element.
|
|
68
|
+
return _jsx(_Fragment, {});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function $createFootnoteReferenceNode(footnoteId) {
|
|
72
|
+
return $applyNodeReplacement(new FootnoteReferenceNode(footnoteId));
|
|
73
|
+
}
|
|
74
|
+
export function $isFootnoteReferenceNode(node) {
|
|
75
|
+
return node instanceof FootnoteReferenceNode;
|
|
76
|
+
}
|
|
77
|
+
/** Resolves a reference's footnoteId from the editor state just before its
|
|
78
|
+
* deletion committed — a mutation listener's 'destroyed' entries only carry
|
|
79
|
+
* the NodeKey, and by the time the listener fires the *current* state no
|
|
80
|
+
* longer has the node, so the caller must pass the listener's own
|
|
81
|
+
* `prevEditorState` here to know which FootnoteItemNode to clean up. */
|
|
82
|
+
export function $getFootnoteIdByKey(prevEditorState, key) {
|
|
83
|
+
let id = null;
|
|
84
|
+
prevEditorState.read(() => {
|
|
85
|
+
const node = $getNodeByKey(key);
|
|
86
|
+
if ($isFootnoteReferenceNode(node))
|
|
87
|
+
id = node.getFootnoteId();
|
|
88
|
+
});
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalNode, type NodeKey, type SerializedElementNode, type Spread, ElementNode } from 'lexical';
|
|
2
|
+
export type PoetryLayout = 'single' | 'two-column';
|
|
3
|
+
export type PoetryAlign = 'justify' | 'left' | 'right' | 'start';
|
|
4
|
+
export type SerializedPoetryBlockNode = Spread<{
|
|
5
|
+
layout: PoetryLayout;
|
|
6
|
+
align: PoetryAlign;
|
|
7
|
+
}, SerializedElementNode>;
|
|
8
|
+
/**
|
|
9
|
+
* A couplet (two-line verse unit), the one extensible primitive requirements
|
|
10
|
+
* doc §4.11 recommends in place of four fixed templates. `layout` picks
|
|
11
|
+
* single-column (two stacked misras) vs. two-column (built on the columns
|
|
12
|
+
* primitive, §4.9 — see blocks/poetryActions.ts for how children are
|
|
13
|
+
* restructured between the two); `align` is a literal per-instance override,
|
|
14
|
+
* independent of the document's own alignment setting (architecture doc
|
|
15
|
+
* §3.1). Both mutate in place via poetryActions so converting a couplet
|
|
16
|
+
* between layouts doesn't lose selection/undo coherence.
|
|
17
|
+
*/
|
|
18
|
+
export declare class PoetryBlockNode extends ElementNode {
|
|
19
|
+
__layout: PoetryLayout;
|
|
20
|
+
__align: PoetryAlign;
|
|
21
|
+
constructor(layout?: PoetryLayout, align?: PoetryAlign, key?: NodeKey);
|
|
22
|
+
static getType(): string;
|
|
23
|
+
static clone(node: PoetryBlockNode): PoetryBlockNode;
|
|
24
|
+
getLayout(): PoetryLayout;
|
|
25
|
+
getAlign(): PoetryAlign;
|
|
26
|
+
/** Attribute-only — restructuring children for a layout change is
|
|
27
|
+
* poetryActions.ts's $setPoetryLayout, not a node method, since it needs
|
|
28
|
+
* $-context tree mutations beyond this node's own state. */
|
|
29
|
+
setLayoutAttribute(layout: PoetryLayout): this;
|
|
30
|
+
setAlign(align: PoetryAlign): this;
|
|
31
|
+
static importJSON(serializedNode: SerializedPoetryBlockNode): PoetryBlockNode;
|
|
32
|
+
exportJSON(): SerializedPoetryBlockNode;
|
|
33
|
+
static importDOM(): DOMConversionMap | null;
|
|
34
|
+
exportDOM(): DOMExportOutput;
|
|
35
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
36
|
+
updateDOM(prevNode: PoetryBlockNode, dom: HTMLElement, config: EditorConfig): boolean;
|
|
37
|
+
canBeEmpty(): false;
|
|
38
|
+
isShadowRoot(): boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function $createPoetryBlockNode(layout?: PoetryLayout, align?: PoetryAlign): PoetryBlockNode;
|
|
41
|
+
export declare function $isPoetryBlockNode(node: LexicalNode | null | undefined): node is PoetryBlockNode;
|
|
42
|
+
//# sourceMappingURL=PoetryNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PoetryNode.d.ts","sourceRoot":"","sources":["../../src/blocks/PoetryNode.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACX,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AACnD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAKjE,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,EAAE,qBAAqB,CAAC,CAAC;AAEpH;;;;;;;;;GASG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;gBAET,MAAM,GAAE,YAAuB,EAAE,KAAK,GAAE,WAAuB,EAAE,GAAG,CAAC,EAAE,OAAO;IAM1F,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe;IAIpD,SAAS,IAAI,YAAY;IAIzB,QAAQ,IAAI,WAAW;IAIvB;;gEAE4D;IAC5D,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAM9C,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAMlC,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,yBAAyB,GAAG,eAAe;IAI7E,UAAU,IAAI,yBAAyB;IAIvC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAa3C,SAAS,IAAI,eAAe;IAQ5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAY5C,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO;IAYrF,UAAU,IAAI,KAAK;IAInB,YAAY,IAAI,OAAO;CAGxB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,GAAE,YAAuB,EAAE,KAAK,GAAE,WAAuB,GAAG,eAAe,CAEvH;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,eAAe,CAEhG"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { addClassNamesToElement } from '@lexical/utils';
|
|
2
|
+
import { $applyNodeReplacement, ElementNode, } from 'lexical';
|
|
3
|
+
const VALID_LAYOUTS = new Set(['single', 'two-column']);
|
|
4
|
+
const VALID_ALIGNS = new Set(['justify', 'left', 'right', 'start']);
|
|
5
|
+
/**
|
|
6
|
+
* A couplet (two-line verse unit), the one extensible primitive requirements
|
|
7
|
+
* doc §4.11 recommends in place of four fixed templates. `layout` picks
|
|
8
|
+
* single-column (two stacked misras) vs. two-column (built on the columns
|
|
9
|
+
* primitive, §4.9 — see blocks/poetryActions.ts for how children are
|
|
10
|
+
* restructured between the two); `align` is a literal per-instance override,
|
|
11
|
+
* independent of the document's own alignment setting (architecture doc
|
|
12
|
+
* §3.1). Both mutate in place via poetryActions so converting a couplet
|
|
13
|
+
* between layouts doesn't lose selection/undo coherence.
|
|
14
|
+
*/
|
|
15
|
+
export class PoetryBlockNode extends ElementNode {
|
|
16
|
+
constructor(layout = 'single', align = 'justify', key) {
|
|
17
|
+
super(key);
|
|
18
|
+
this.__layout = layout;
|
|
19
|
+
this.__align = align;
|
|
20
|
+
}
|
|
21
|
+
static getType() {
|
|
22
|
+
return 'poetry-couplet';
|
|
23
|
+
}
|
|
24
|
+
static clone(node) {
|
|
25
|
+
return new PoetryBlockNode(node.__layout, node.__align, node.__key);
|
|
26
|
+
}
|
|
27
|
+
getLayout() {
|
|
28
|
+
return this.getLatest().__layout;
|
|
29
|
+
}
|
|
30
|
+
getAlign() {
|
|
31
|
+
return this.getLatest().__align;
|
|
32
|
+
}
|
|
33
|
+
/** Attribute-only — restructuring children for a layout change is
|
|
34
|
+
* poetryActions.ts's $setPoetryLayout, not a node method, since it needs
|
|
35
|
+
* $-context tree mutations beyond this node's own state. */
|
|
36
|
+
setLayoutAttribute(layout) {
|
|
37
|
+
const writable = this.getWritable();
|
|
38
|
+
writable.__layout = layout;
|
|
39
|
+
return writable;
|
|
40
|
+
}
|
|
41
|
+
setAlign(align) {
|
|
42
|
+
const writable = this.getWritable();
|
|
43
|
+
writable.__align = align;
|
|
44
|
+
return writable;
|
|
45
|
+
}
|
|
46
|
+
static importJSON(serializedNode) {
|
|
47
|
+
return $createPoetryBlockNode(serializedNode.layout, serializedNode.align);
|
|
48
|
+
}
|
|
49
|
+
exportJSON() {
|
|
50
|
+
return { ...super.exportJSON(), type: 'poetry-couplet', version: 1, layout: this.getLayout(), align: this.getAlign() };
|
|
51
|
+
}
|
|
52
|
+
static importDOM() {
|
|
53
|
+
return {
|
|
54
|
+
div: (domNode) => {
|
|
55
|
+
const rawLayout = domNode.getAttribute('data-likhari-poetry-layout');
|
|
56
|
+
if (!rawLayout)
|
|
57
|
+
return null;
|
|
58
|
+
const layout = VALID_LAYOUTS.has(rawLayout) ? rawLayout : 'single';
|
|
59
|
+
const rawAlign = domNode.getAttribute('data-likhari-poetry-align') ?? 'justify';
|
|
60
|
+
const align = VALID_ALIGNS.has(rawAlign) ? rawAlign : 'justify';
|
|
61
|
+
return { conversion: () => ({ node: $createPoetryBlockNode(layout, align) }), priority: 2 };
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
exportDOM() {
|
|
66
|
+
const element = document.createElement('div');
|
|
67
|
+
element.setAttribute('data-likhari-poetry-layout', this.getLayout());
|
|
68
|
+
element.setAttribute('data-likhari-poetry-align', this.getAlign());
|
|
69
|
+
element.style.textAlign = this.getAlign();
|
|
70
|
+
return { element };
|
|
71
|
+
}
|
|
72
|
+
createDOM(config) {
|
|
73
|
+
const element = document.createElement('div');
|
|
74
|
+
const base = config.theme.poetry ?? 'likhari-poetry';
|
|
75
|
+
addClassNamesToElement(element, base, `${base}--${this.__layout}`);
|
|
76
|
+
element.style.textAlign = this.__align;
|
|
77
|
+
return element;
|
|
78
|
+
}
|
|
79
|
+
// Only ever adds/removes the layout modifier class, never touches the
|
|
80
|
+
// element's full className — the reconciler separately classList.adds the
|
|
81
|
+
// per-block ltr/rtl theme class (editor.css) onto this same element, and
|
|
82
|
+
// a wholesale className reassignment here would silently wipe it out.
|
|
83
|
+
updateDOM(prevNode, dom, config) {
|
|
84
|
+
if (prevNode.__layout !== this.__layout) {
|
|
85
|
+
const base = config.theme.poetry ?? 'likhari-poetry';
|
|
86
|
+
dom.classList.remove(`${base}--${prevNode.__layout}`);
|
|
87
|
+
dom.classList.add(`${base}--${this.__layout}`);
|
|
88
|
+
}
|
|
89
|
+
if (prevNode.__align !== this.__align) {
|
|
90
|
+
dom.style.textAlign = this.__align;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
canBeEmpty() {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
isShadowRoot() {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function $createPoetryBlockNode(layout = 'single', align = 'justify') {
|
|
102
|
+
return $applyNodeReplacement(new PoetryBlockNode(layout, align));
|
|
103
|
+
}
|
|
104
|
+
export function $isPoetryBlockNode(node) {
|
|
105
|
+
return node instanceof PoetryBlockNode;
|
|
106
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Inserts a reference at the caret and a matching empty item at the end of
|
|
2
|
+
* the document's footnote list (creating the list if this is the first
|
|
3
|
+
* footnote), then focuses the new item so the user can type the note. */
|
|
4
|
+
export declare function $insertFootnote(): boolean;
|
|
5
|
+
/** Removes the FootnoteItemNode for `footnoteId`, and the FootnoteListNode
|
|
6
|
+
* itself if that was its last item — called by FootnotePlugin's mutation
|
|
7
|
+
* listener when a FootnoteReferenceNode is deleted from the text. */
|
|
8
|
+
export declare function $removeFootnoteItem(footnoteId: string): void;
|
|
9
|
+
//# sourceMappingURL=footnoteActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"footnoteActions.d.ts","sourceRoot":"","sources":["../../src/blocks/footnoteActions.ts"],"names":[],"mappings":"AAcA;;yEAEyE;AACzE,wBAAgB,eAAe,IAAI,OAAO,CAgBzC;AAED;;qEAEqE;AACrE,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAM5D"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { $createParagraphNode, $getRoot, $getSelection, $isRangeSelection } from 'lexical';
|
|
2
|
+
import { $createFootnoteReferenceNode } from './FootnoteNode.js';
|
|
3
|
+
import { $createFootnoteItemNode, $createFootnoteListNode, $isFootnoteItemNode, $isFootnoteListNode } from './FootnoteListNode.js';
|
|
4
|
+
let counter = 0;
|
|
5
|
+
/** Short, collision-resistant within a single document: a monotonic counter
|
|
6
|
+
* is enough here (ids never need to be stable/meaningful across documents),
|
|
7
|
+
* and avoids a hard dependency on crypto.randomUUID being available. */
|
|
8
|
+
function newFootnoteId() {
|
|
9
|
+
counter += 1;
|
|
10
|
+
return `${Date.now().toString(36)}${counter.toString(36)}`;
|
|
11
|
+
}
|
|
12
|
+
/** Inserts a reference at the caret and a matching empty item at the end of
|
|
13
|
+
* the document's footnote list (creating the list if this is the first
|
|
14
|
+
* footnote), then focuses the new item so the user can type the note. */
|
|
15
|
+
export function $insertFootnote() {
|
|
16
|
+
const selection = $getSelection();
|
|
17
|
+
if (!$isRangeSelection(selection))
|
|
18
|
+
return false;
|
|
19
|
+
const footnoteId = newFootnoteId();
|
|
20
|
+
selection.insertNodes([$createFootnoteReferenceNode(footnoteId)]);
|
|
21
|
+
const root = $getRoot();
|
|
22
|
+
const lastChild = root.getLastChild();
|
|
23
|
+
const list = $isFootnoteListNode(lastChild) ? lastChild : $createFootnoteListNode();
|
|
24
|
+
if (!$isFootnoteListNode(lastChild))
|
|
25
|
+
root.append(list);
|
|
26
|
+
const item = $createFootnoteItemNode(footnoteId).append($createParagraphNode());
|
|
27
|
+
list.append(item);
|
|
28
|
+
item.getFirstDescendant()?.selectStart();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/** Removes the FootnoteItemNode for `footnoteId`, and the FootnoteListNode
|
|
32
|
+
* itself if that was its last item — called by FootnotePlugin's mutation
|
|
33
|
+
* listener when a FootnoteReferenceNode is deleted from the text. */
|
|
34
|
+
export function $removeFootnoteItem(footnoteId) {
|
|
35
|
+
const list = $getRoot().getChildren().find($isFootnoteListNode);
|
|
36
|
+
if (!list)
|
|
37
|
+
return;
|
|
38
|
+
const item = list.getChildren().find((child) => $isFootnoteItemNode(child) && child.getFootnoteId() === footnoteId);
|
|
39
|
+
item?.remove();
|
|
40
|
+
if (list.getChildrenSize() === 0)
|
|
41
|
+
list.remove();
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ParagraphNode } from 'lexical';
|
|
2
|
+
import { type PoetryAlign, type PoetryLayout, type PoetryBlockNode } from './PoetryNode.js';
|
|
3
|
+
/** The couplet's two misra paragraphs, in document order, regardless of
|
|
4
|
+
* which layout currently holds them. */
|
|
5
|
+
export declare function $getMisraParagraphs(node: PoetryBlockNode): ParagraphNode[];
|
|
6
|
+
/** The PoetryBlockNode the current selection is inside, or null. */
|
|
7
|
+
export declare function $getPoetryBlockFromSelection(): PoetryBlockNode | null;
|
|
8
|
+
/**
|
|
9
|
+
* Converts a couplet between single-column (two stacked misras) and
|
|
10
|
+
* two-column (built on the columns primitive, §4.9) in place — pulls the
|
|
11
|
+
* two misra paragraphs out of whichever shape currently holds them and
|
|
12
|
+
* rebuilds the other shape around the same paragraph nodes, so content,
|
|
13
|
+
* selection and undo history all survive the conversion.
|
|
14
|
+
*/
|
|
15
|
+
export declare function $setPoetryLayout(node: PoetryBlockNode, layout: PoetryLayout): void;
|
|
16
|
+
/** Inserts a new two-misra couplet after the selection's top-level block,
|
|
17
|
+
* in `layout`/`align`, and focuses its first misra. */
|
|
18
|
+
export declare function $insertPoetryCouplet(layout: PoetryLayout, align: PoetryAlign): boolean;
|
|
19
|
+
//# sourceMappingURL=poetryActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poetryActions.d.ts","sourceRoot":"","sources":["../../src/blocks/poetryActions.ts"],"names":[],"mappings":"AACA,OAAO,EAA4E,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEvH,OAAO,EAA8C,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAErI;wCACwC;AACxC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,EAAE,CAW1E;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,IAAI,eAAe,GAAG,IAAI,CAKrE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAclF;AAED;uDACuD;AACvD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAmBtF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { $findMatchingParent } from '@lexical/utils';
|
|
2
|
+
import { $createParagraphNode, $getSelection, $isParagraphNode, $isRangeSelection } from 'lexical';
|
|
3
|
+
import { $createLayoutContainerNode, $createLayoutItemNode, $isLayoutContainerNode, $isLayoutItemNode } from './LayoutNode.js';
|
|
4
|
+
import { $createPoetryBlockNode, $isPoetryBlockNode } from './PoetryNode.js';
|
|
5
|
+
/** The couplet's two misra paragraphs, in document order, regardless of
|
|
6
|
+
* which layout currently holds them. */
|
|
7
|
+
export function $getMisraParagraphs(node) {
|
|
8
|
+
if (node.getLayout() === 'single') {
|
|
9
|
+
return node.getChildren().filter($isParagraphNode);
|
|
10
|
+
}
|
|
11
|
+
const container = node.getChildren().find($isLayoutContainerNode);
|
|
12
|
+
if (!container)
|
|
13
|
+
return [];
|
|
14
|
+
return container
|
|
15
|
+
.getChildren()
|
|
16
|
+
.filter($isLayoutItemNode)
|
|
17
|
+
.map((item) => item.getFirstChild())
|
|
18
|
+
.filter($isParagraphNode);
|
|
19
|
+
}
|
|
20
|
+
/** The PoetryBlockNode the current selection is inside, or null. */
|
|
21
|
+
export function $getPoetryBlockFromSelection() {
|
|
22
|
+
const selection = $getSelection();
|
|
23
|
+
if (!$isRangeSelection(selection))
|
|
24
|
+
return null;
|
|
25
|
+
const match = $findMatchingParent(selection.anchor.getNode(), $isPoetryBlockNode);
|
|
26
|
+
return match ?? null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Converts a couplet between single-column (two stacked misras) and
|
|
30
|
+
* two-column (built on the columns primitive, §4.9) in place — pulls the
|
|
31
|
+
* two misra paragraphs out of whichever shape currently holds them and
|
|
32
|
+
* rebuilds the other shape around the same paragraph nodes, so content,
|
|
33
|
+
* selection and undo history all survive the conversion.
|
|
34
|
+
*/
|
|
35
|
+
export function $setPoetryLayout(node, layout) {
|
|
36
|
+
if (node.getLayout() === layout)
|
|
37
|
+
return;
|
|
38
|
+
const misras = $getMisraParagraphs(node);
|
|
39
|
+
for (const misra of misras)
|
|
40
|
+
misra.remove();
|
|
41
|
+
for (const leftover of node.getChildren())
|
|
42
|
+
leftover.remove();
|
|
43
|
+
if (layout === 'single') {
|
|
44
|
+
for (const misra of misras)
|
|
45
|
+
node.append(misra);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const container = $createLayoutContainerNode('repeat(2, 1fr)');
|
|
49
|
+
for (const misra of misras)
|
|
50
|
+
container.append($createLayoutItemNode().append(misra));
|
|
51
|
+
node.append(container);
|
|
52
|
+
}
|
|
53
|
+
node.setLayoutAttribute(layout);
|
|
54
|
+
}
|
|
55
|
+
/** Inserts a new two-misra couplet after the selection's top-level block,
|
|
56
|
+
* in `layout`/`align`, and focuses its first misra. */
|
|
57
|
+
export function $insertPoetryCouplet(layout, align) {
|
|
58
|
+
const selection = $getSelection();
|
|
59
|
+
if (!$isRangeSelection(selection))
|
|
60
|
+
return false;
|
|
61
|
+
const anchorTopLevel = selection.anchor.getNode().getTopLevelElementOrThrow();
|
|
62
|
+
const node = $createPoetryBlockNode(layout, align);
|
|
63
|
+
const misraA = $createParagraphNode();
|
|
64
|
+
const misraB = $createParagraphNode();
|
|
65
|
+
if (layout === 'single') {
|
|
66
|
+
node.append(misraA, misraB);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const container = $createLayoutContainerNode('repeat(2, 1fr)');
|
|
70
|
+
container.append($createLayoutItemNode().append(misraA), $createLayoutItemNode().append(misraB));
|
|
71
|
+
node.append(container);
|
|
72
|
+
}
|
|
73
|
+
anchorTopLevel.insertAfter(node);
|
|
74
|
+
misraA.selectStart();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
@@ -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":"AAiEA,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;AAwevD,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,+BAqgCxJ"}
|
|
@@ -13,6 +13,9 @@ 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
15
|
import { INSERT_LAYOUT_COMMAND } from '../blocks/LayoutNode.js';
|
|
16
|
+
import { INSERT_FOOTNOTE_COMMAND } from '../blocks/FootnoteNode.js';
|
|
17
|
+
import { $getPoetryBlockFromSelection, $setPoetryLayout } from '../blocks/poetryActions.js';
|
|
18
|
+
import { INSERT_POETRY_COUPLET_COMMAND } from '../plugins/PoetryPlugin.js';
|
|
16
19
|
import { TableDialog } from './TableDialog.js';
|
|
17
20
|
import { LayoutDialog } from './LayoutDialog.js';
|
|
18
21
|
import { $getTableCellNodeFromLexicalNode, $isTableSelection, INSERT_TABLE_COMMAND } from '@lexical/table';
|
|
@@ -25,7 +28,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fo
|
|
|
25
28
|
import { setStyleProperty } from '../utils/style.js';
|
|
26
29
|
import { useStrings } from '../i18n/useStrings.js';
|
|
27
30
|
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
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';
|
|
31
|
+
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, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconTable, IconTableMinus, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
29
32
|
/** Sentinel for "no inline style": Lexical returns the default it is given when
|
|
30
33
|
* the selection has none, so this tells "unstyled" (show the canvas default)
|
|
31
34
|
* apart from "styled with a value the dropdown doesn't list" (show nothing). */
|
|
@@ -44,6 +47,9 @@ const INITIAL_STATE = {
|
|
|
44
47
|
tableColumns: 1,
|
|
45
48
|
canMergeCells: false,
|
|
46
49
|
canUnmergeCell: false,
|
|
50
|
+
inPoetry: false,
|
|
51
|
+
poetryLayout: 'single',
|
|
52
|
+
poetryAlign: 'justify',
|
|
47
53
|
canUndo: false,
|
|
48
54
|
canRedo: false,
|
|
49
55
|
};
|
|
@@ -155,6 +161,26 @@ function TableMenuItems({ strings, direction, rows, columns, canMergeCells, canU
|
|
|
155
161
|
};
|
|
156
162
|
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')] }));
|
|
157
163
|
}
|
|
164
|
+
/** The poetry couplet menu's contents — layout (single/two-column) and
|
|
165
|
+
* literal alignment, both per-instance overrides (requirements doc §4.11:
|
|
166
|
+
* "set per CoupletNode, not inherited from a document-wide setting"). */
|
|
167
|
+
function PoetryMenuItems({ strings, alignIcons, layout, align, onAction, }) {
|
|
168
|
+
const t = strings.poetryMenu;
|
|
169
|
+
const a = strings.toolbar.alignOptions;
|
|
170
|
+
const item = (icon, label, action, active) => {
|
|
171
|
+
const Icon = icon;
|
|
172
|
+
return (_jsx(Menu.Item, { disabled: active, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onAction(action), children: label }));
|
|
173
|
+
};
|
|
174
|
+
const setLayout = (next) => () => {
|
|
175
|
+
const node = $getPoetryBlockFromSelection();
|
|
176
|
+
if (node)
|
|
177
|
+
$setPoetryLayout(node, next);
|
|
178
|
+
};
|
|
179
|
+
const setAlign = (next) => () => {
|
|
180
|
+
$getPoetryBlockFromSelection()?.setAlign(next);
|
|
181
|
+
};
|
|
182
|
+
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), _jsx(Menu.Divider, {}), item(alignIcons.justify ?? IconAlignJustified, a.justify, setAlign('justify'), align === 'justify'), item(alignIcons.start ?? IconAlignLeft, a.start, setAlign('start'), align === 'start'), item(IconAlignLeft, a.left, setAlign('left'), align === 'left'), item(IconAlignRight, a.right, setAlign('right'), align === 'right')] }));
|
|
183
|
+
}
|
|
158
184
|
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
159
185
|
const [editor] = useLexicalComposerContext();
|
|
160
186
|
const [state, setState] = useState(INITIAL_STATE);
|
|
@@ -179,7 +205,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
179
205
|
if ($isTableSelection(selection)) {
|
|
180
206
|
const { rows, columns } = $getTableSelectionSize();
|
|
181
207
|
const canMergeCells = $canMergeSelectedCells();
|
|
182
|
-
setState((s) => ({ ...s, inTable: true, tableRows: rows, tableColumns: columns, canMergeCells, canUnmergeCell: false }));
|
|
208
|
+
setState((s) => ({ ...s, inTable: true, tableRows: rows, tableColumns: columns, canMergeCells, canUnmergeCell: false, inPoetry: false }));
|
|
183
209
|
return;
|
|
184
210
|
}
|
|
185
211
|
if (!$isRangeSelection(selection))
|
|
@@ -188,6 +214,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
188
214
|
const inTable = $getTableCellNodeFromLexicalNode(anchorNode) !== null;
|
|
189
215
|
const tableSize = inTable ? $getTableSelectionSize() : { rows: 1, columns: 1 };
|
|
190
216
|
const canUnmergeCell = inTable && $canUnmergeSelectedCell();
|
|
217
|
+
const poetryBlock = $getPoetryBlockFromSelection();
|
|
191
218
|
const element = anchorNode.getKey() === 'root' ? anchorNode : (anchorNode.getTopLevelElement() ?? anchorNode);
|
|
192
219
|
const listParent = $findMatchingParent(anchorNode, $isListNode);
|
|
193
220
|
const listType = listParent && $isListNode(listParent) ? listParent.getListType() : null;
|
|
@@ -228,6 +255,9 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
228
255
|
tableColumns: tableSize.columns,
|
|
229
256
|
canMergeCells: false,
|
|
230
257
|
canUnmergeCell,
|
|
258
|
+
inPoetry: poetryBlock !== null,
|
|
259
|
+
poetryLayout: poetryBlock?.getLayout() ?? s.poetryLayout,
|
|
260
|
+
poetryAlign: poetryBlock?.getAlign() ?? s.poetryAlign,
|
|
231
261
|
}));
|
|
232
262
|
});
|
|
233
263
|
}, [editor]);
|
|
@@ -456,9 +486,9 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
456
486
|
root?.addEventListener('contextmenu', onContextMenu);
|
|
457
487
|
});
|
|
458
488
|
}, [editor, config.links]);
|
|
459
|
-
// Table actions. The menu takes focus, so the
|
|
460
|
-
// opens and restored before an action runs.
|
|
461
|
-
const
|
|
489
|
+
// Table/poetry contextual-menu actions. The menu takes focus, so the
|
|
490
|
+
// selection is snapshotted when it opens and restored before an action runs.
|
|
491
|
+
const runMenuAction = (action) => () => {
|
|
462
492
|
restoreSelection();
|
|
463
493
|
editor.update(action);
|
|
464
494
|
editor.focus();
|
|
@@ -566,6 +596,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
566
596
|
config.blocks.horizontalRule ||
|
|
567
597
|
config.blocks.pageBreak ||
|
|
568
598
|
config.columns ||
|
|
599
|
+
config.footnotes ||
|
|
569
600
|
config.poetry.enabled;
|
|
570
601
|
const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
|
|
571
602
|
// The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
|
|
@@ -738,8 +769,9 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
738
769
|
restoreSelection();
|
|
739
770
|
openLinkDialog();
|
|
740
771
|
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
|
|
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(
|
|
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:
|
|
772
|
+
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.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout, align: 'justify' }) }))] }, "stubInsert")),
|
|
773
|
+
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: runMenuAction }) })] }) }, "tableActions")),
|
|
774
|
+
config.poetry.enabled && state.inPoetry && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, 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.poetryOptions, title: strings.toolbar.poetryOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconFeather, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, alignIcons: ALIGN_ICONS, layout: state.poetryLayout, align: state.poetryAlign, onAction: runMenuAction }) })] }) }, "poetryActions")),
|
|
743
775
|
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")),
|
|
744
776
|
];
|
|
745
777
|
return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
|
|
@@ -755,7 +787,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
755
787
|
}, onRemove: removeLink, strings: strings }) })] })), config.tables && (_jsxs(Menu, { opened: tableContext !== null, onChange: (opened) => {
|
|
756
788
|
if (!opened)
|
|
757
789
|
setTableContext(null);
|
|
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:
|
|
790
|
+
}, 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: runMenuAction }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
|
|
759
791
|
position: 'absolute',
|
|
760
792
|
visibility: 'hidden',
|
|
761
793
|
pointerEvents: 'none',
|
package/dist/i18n/strings.d.ts
CHANGED
|
@@ -42,13 +42,16 @@ export interface Strings {
|
|
|
42
42
|
linkOptions: string;
|
|
43
43
|
/** Toolbar button (shown while the caret is in a table) that opens the table actions menu. */
|
|
44
44
|
tableOptions: string;
|
|
45
|
+
/** Toolbar button (shown while the caret is in a couplet) that opens the poetry layout/alignment menu. */
|
|
46
|
+
poetryOptions: string;
|
|
45
47
|
insertLink: string;
|
|
46
48
|
insertImage: string;
|
|
47
49
|
insertPageBreak: string;
|
|
48
50
|
insertTable: string;
|
|
49
51
|
insertColumns: string;
|
|
52
|
+
insertFootnote: string;
|
|
53
|
+
insertPoetryCouplet: string;
|
|
50
54
|
insertHorizontalRule: string;
|
|
51
|
-
poetryBlocks: string;
|
|
52
55
|
comingSoon: (label: string) => string;
|
|
53
56
|
autocorrect: string;
|
|
54
57
|
textCleanup: string;
|
|
@@ -135,6 +138,11 @@ export interface Strings {
|
|
|
135
138
|
mergeCells: string;
|
|
136
139
|
unmergeCell: string;
|
|
137
140
|
};
|
|
141
|
+
poetryMenu: {
|
|
142
|
+
menuLabel: string;
|
|
143
|
+
singleColumn: string;
|
|
144
|
+
twoColumn: string;
|
|
145
|
+
};
|
|
138
146
|
link: {
|
|
139
147
|
menuLabel: string;
|
|
140
148
|
noUrl: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;+DAC+D;AAC/D,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAElD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE;QACN,mGAAmG;QACnG,WAAW,EAAE,MAAM,CAAC;QACpB,gDAAgD;QAChD,YAAY,EAAE,MAAM,CAAC;QACrB,sEAAsE;QACtE,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE;QACP,gEAAgE;QAChE,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,0CAA0C;QAC1C,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,8FAA8F;QAC9F,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,
|
|
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
|
@@ -33,13 +33,15 @@ const en = {
|
|
|
33
33
|
alignment: 'Alignment',
|
|
34
34
|
linkOptions: 'Link options',
|
|
35
35
|
tableOptions: 'Table options',
|
|
36
|
+
poetryOptions: 'Poetry options',
|
|
36
37
|
insertLink: 'Insert link (Ctrl+K)',
|
|
37
38
|
insertImage: 'Insert image',
|
|
38
39
|
insertPageBreak: 'Insert page break',
|
|
39
40
|
insertTable: 'Insert table',
|
|
40
41
|
insertColumns: 'Insert columns',
|
|
42
|
+
insertFootnote: 'Insert footnote',
|
|
43
|
+
insertPoetryCouplet: 'Insert poetry couplet',
|
|
41
44
|
insertHorizontalRule: 'Insert horizontal rule',
|
|
42
|
-
poetryBlocks: 'Poetry blocks',
|
|
43
45
|
comingSoon: (label) => `${label} (coming soon)`,
|
|
44
46
|
autocorrect: 'Auto-correct',
|
|
45
47
|
textCleanup: 'Text cleanup',
|
|
@@ -119,6 +121,11 @@ const en = {
|
|
|
119
121
|
mergeCells: 'Merge cells',
|
|
120
122
|
unmergeCell: 'Unmerge cell',
|
|
121
123
|
},
|
|
124
|
+
poetryMenu: {
|
|
125
|
+
menuLabel: 'Poetry',
|
|
126
|
+
singleColumn: 'Single column',
|
|
127
|
+
twoColumn: 'Two column',
|
|
128
|
+
},
|
|
122
129
|
link: {
|
|
123
130
|
menuLabel: 'Link',
|
|
124
131
|
noUrl: '(no URL)',
|
|
@@ -233,13 +240,15 @@ const ur = {
|
|
|
233
240
|
alignment: 'سیدھ',
|
|
234
241
|
linkOptions: 'لنک کے اختیارات',
|
|
235
242
|
tableOptions: 'جدول کے اختیارات',
|
|
243
|
+
poetryOptions: 'شاعری کے اختیارات',
|
|
236
244
|
insertLink: 'لنک شامل کریں (Ctrl+K)',
|
|
237
245
|
insertImage: 'تصویر شامل کریں',
|
|
238
246
|
insertPageBreak: 'صفحے کی تقسیم شامل کریں',
|
|
239
247
|
insertTable: 'جدول شامل کریں',
|
|
240
248
|
insertColumns: 'کالم شامل کریں',
|
|
249
|
+
insertFootnote: 'فٹ نوٹ شامل کریں',
|
|
250
|
+
insertPoetryCouplet: 'شعر شامل کریں',
|
|
241
251
|
insertHorizontalRule: 'افقی لکیر شامل کریں',
|
|
242
|
-
poetryBlocks: 'شاعری کے بلاکس',
|
|
243
252
|
comingSoon: (label) => `${label} (جلد آ رہا ہے)`,
|
|
244
253
|
autocorrect: 'خودکار تصحیح',
|
|
245
254
|
textCleanup: 'متن کی صفائی',
|
|
@@ -319,6 +328,11 @@ const ur = {
|
|
|
319
328
|
mergeCells: 'خانے ضم کریں',
|
|
320
329
|
unmergeCell: 'خانہ الگ کریں',
|
|
321
330
|
},
|
|
331
|
+
poetryMenu: {
|
|
332
|
+
menuLabel: 'شاعری',
|
|
333
|
+
singleColumn: 'ایک کالم',
|
|
334
|
+
twoColumn: 'دو کالم',
|
|
335
|
+
},
|
|
322
336
|
link: {
|
|
323
337
|
menuLabel: 'لنک',
|
|
324
338
|
noUrl: '(کوئی یو آر ایل نہیں)',
|
|
@@ -433,13 +447,15 @@ const paShahmukhi = {
|
|
|
433
447
|
alignment: 'سیدھ',
|
|
434
448
|
linkOptions: 'لنک دے اختیار',
|
|
435
449
|
tableOptions: 'ٹیبل دے اختیار',
|
|
450
|
+
poetryOptions: 'شاعری دے اختیار',
|
|
436
451
|
insertLink: 'لنک پاؤ (Ctrl+K)',
|
|
437
452
|
insertImage: 'تصویر پاؤ',
|
|
438
453
|
insertPageBreak: 'صفحے دی ونڈ پاؤ',
|
|
439
454
|
insertTable: 'ٹیبل پاؤ',
|
|
440
455
|
insertColumns: 'کالم پاؤ',
|
|
456
|
+
insertFootnote: 'فٹ نوٹ پاؤ',
|
|
457
|
+
insertPoetryCouplet: 'شعر پاؤ',
|
|
441
458
|
insertHorizontalRule: 'لیٹی لکیر پاؤ',
|
|
442
|
-
poetryBlocks: 'شاعری بلاک',
|
|
443
459
|
comingSoon: (label) => `${label} (چھیتی آ رہا اے)`,
|
|
444
460
|
autocorrect: 'خودکار درستی',
|
|
445
461
|
textCleanup: 'متن دی صفائی',
|
|
@@ -519,6 +535,11 @@ const paShahmukhi = {
|
|
|
519
535
|
mergeCells: 'خانے رلاؤ',
|
|
520
536
|
unmergeCell: 'خانہ الگ کرو',
|
|
521
537
|
},
|
|
538
|
+
poetryMenu: {
|
|
539
|
+
menuLabel: 'شاعری',
|
|
540
|
+
singleColumn: 'اک کالم',
|
|
541
|
+
twoColumn: 'دو کالم',
|
|
542
|
+
},
|
|
522
543
|
link: {
|
|
523
544
|
menuLabel: 'لنک',
|
|
524
545
|
noUrl: '(کوئی یو آر ایل نئیں)',
|
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;AAalD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAkB5C,CAAC"}
|
package/dist/nodes.js
CHANGED
|
@@ -6,6 +6,9 @@ import { TableCellNode, TableNode, TableRowNode } from '@lexical/table';
|
|
|
6
6
|
import { ImageNode } from './image/ImageNode.js';
|
|
7
7
|
import { PageBreakNode } from './blocks/PageBreakNode.js';
|
|
8
8
|
import { LayoutContainerNode, LayoutItemNode } from './blocks/LayoutNode.js';
|
|
9
|
+
import { FootnoteReferenceNode } from './blocks/FootnoteNode.js';
|
|
10
|
+
import { FootnoteItemNode, FootnoteListNode } from './blocks/FootnoteListNode.js';
|
|
11
|
+
import { PoetryBlockNode } from './blocks/PoetryNode.js';
|
|
9
12
|
/**
|
|
10
13
|
* Node types registered on the editor at construction time. Per
|
|
11
14
|
* editor-architecture-design.md §2, node registration cannot be conditional
|
|
@@ -29,4 +32,8 @@ export const EDITOR_NODES = [
|
|
|
29
32
|
PageBreakNode,
|
|
30
33
|
LayoutContainerNode,
|
|
31
34
|
LayoutItemNode,
|
|
35
|
+
FootnoteReferenceNode,
|
|
36
|
+
FootnoteListNode,
|
|
37
|
+
FootnoteItemNode,
|
|
38
|
+
PoetryBlockNode,
|
|
32
39
|
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers INSERT_FOOTNOTE_COMMAND, a mutation listener that removes a
|
|
3
|
+
* FootnoteItemNode when its matching FootnoteReferenceNode is deleted from
|
|
4
|
+
* the text (so the list never accumulates orphaned notes), and two node
|
|
5
|
+
* transforms that keep the list/item nesting valid against malformed trees
|
|
6
|
+
* from paste, undo or JSON import — mirrors LayoutPlugin's shape.
|
|
7
|
+
*/
|
|
8
|
+
export declare function FootnotePlugin(): null;
|
|
9
|
+
//# sourceMappingURL=FootnotePlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FootnotePlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/FootnotePlugin.tsx"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,cAAc,SAyC7B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { $getFootnoteIdByKey, FootnoteReferenceNode, INSERT_FOOTNOTE_COMMAND } from '../blocks/FootnoteNode.js';
|
|
6
|
+
import { $isFootnoteItemNode, $isFootnoteListNode, FootnoteItemNode, FootnoteListNode } from '../blocks/FootnoteListNode.js';
|
|
7
|
+
import { $insertFootnote, $removeFootnoteItem } from '../blocks/footnoteActions.js';
|
|
8
|
+
/**
|
|
9
|
+
* Registers INSERT_FOOTNOTE_COMMAND, a mutation listener that removes a
|
|
10
|
+
* FootnoteItemNode when its matching FootnoteReferenceNode is deleted from
|
|
11
|
+
* the text (so the list never accumulates orphaned notes), and two node
|
|
12
|
+
* transforms that keep the list/item nesting valid against malformed trees
|
|
13
|
+
* from paste, undo or JSON import — mirrors LayoutPlugin's shape.
|
|
14
|
+
*/
|
|
15
|
+
export function FootnotePlugin() {
|
|
16
|
+
const [editor] = useLexicalComposerContext();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!editor.hasNodes([FootnoteReferenceNode, FootnoteListNode, FootnoteItemNode])) {
|
|
19
|
+
throw new Error('FootnotePlugin: footnote node types not registered on the editor');
|
|
20
|
+
}
|
|
21
|
+
return mergeRegister(editor.registerCommand(INSERT_FOOTNOTE_COMMAND, () => $insertFootnote(), COMMAND_PRIORITY_EDITOR), editor.registerMutationListener(FootnoteReferenceNode, (mutations, { prevEditorState }) => {
|
|
22
|
+
const destroyedIds = [];
|
|
23
|
+
for (const [key, mutation] of mutations) {
|
|
24
|
+
if (mutation !== 'destroyed')
|
|
25
|
+
continue;
|
|
26
|
+
const id = $getFootnoteIdByKey(prevEditorState, key);
|
|
27
|
+
if (id)
|
|
28
|
+
destroyedIds.push(id);
|
|
29
|
+
}
|
|
30
|
+
if (destroyedIds.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
editor.update(() => {
|
|
33
|
+
for (const id of destroyedIds)
|
|
34
|
+
$removeFootnoteItem(id);
|
|
35
|
+
});
|
|
36
|
+
}, { skipInitialization: true }), editor.registerNodeTransform(FootnoteItemNode, (node) => {
|
|
37
|
+
const parent = node.getParent();
|
|
38
|
+
if ($isFootnoteListNode(parent))
|
|
39
|
+
return;
|
|
40
|
+
for (const child of node.getChildren())
|
|
41
|
+
node.insertBefore(child);
|
|
42
|
+
node.remove();
|
|
43
|
+
}), editor.registerNodeTransform(FootnoteListNode, (node) => {
|
|
44
|
+
for (const child of node.getChildren()) {
|
|
45
|
+
if (!$isFootnoteItemNode(child))
|
|
46
|
+
child.remove();
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
}, [editor]);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type LexicalCommand } from 'lexical';
|
|
2
|
+
import { type PoetryAlign, type PoetryLayout } from '../blocks/PoetryNode.js';
|
|
3
|
+
export declare const INSERT_POETRY_COUPLET_COMMAND: LexicalCommand<{
|
|
4
|
+
layout: PoetryLayout;
|
|
5
|
+
align: PoetryAlign;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Registers INSERT_POETRY_COUPLET_COMMAND (mirrors LayoutPlugin/FootnotePlugin's
|
|
9
|
+
* shape) and a node transform that removes a couplet left with no misra
|
|
10
|
+
* content — e.g. after undo/paste leaves it structurally empty — rather
|
|
11
|
+
* than letting an unselectable husk linger in the document.
|
|
12
|
+
*/
|
|
13
|
+
export declare function PoetryPlugin(): null;
|
|
14
|
+
//# sourceMappingURL=PoetryPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PoetryPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PoetryPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAGtF,OAAO,EAAmB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG5F,eAAO,MAAM,6BAA6B,EAAE,cAAc,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAEtG,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,SAqB3B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
|
|
4
|
+
import { mergeRegister } from '@lexical/utils';
|
|
5
|
+
import { LayoutContainerNode, LayoutItemNode } from '../blocks/LayoutNode.js';
|
|
6
|
+
import { PoetryBlockNode } from '../blocks/PoetryNode.js';
|
|
7
|
+
import { $insertPoetryCouplet } from '../blocks/poetryActions.js';
|
|
8
|
+
export const INSERT_POETRY_COUPLET_COMMAND = createCommand('INSERT_POETRY_COUPLET_COMMAND');
|
|
9
|
+
/**
|
|
10
|
+
* Registers INSERT_POETRY_COUPLET_COMMAND (mirrors LayoutPlugin/FootnotePlugin's
|
|
11
|
+
* shape) and a node transform that removes a couplet left with no misra
|
|
12
|
+
* content — e.g. after undo/paste leaves it structurally empty — rather
|
|
13
|
+
* than letting an unselectable husk linger in the document.
|
|
14
|
+
*/
|
|
15
|
+
export function PoetryPlugin() {
|
|
16
|
+
const [editor] = useLexicalComposerContext();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!editor.hasNodes([PoetryBlockNode, LayoutContainerNode, LayoutItemNode])) {
|
|
19
|
+
throw new Error('PoetryPlugin: PoetryBlockNode (and the columns nodes it composes) not registered on the editor');
|
|
20
|
+
}
|
|
21
|
+
return mergeRegister(editor.registerCommand(INSERT_POETRY_COUPLET_COMMAND, ({ layout, align }) => $insertPoetryCouplet(layout, align), COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(PoetryBlockNode, (node) => {
|
|
22
|
+
if (node.getChildrenSize() === 0)
|
|
23
|
+
node.remove();
|
|
24
|
+
}));
|
|
25
|
+
}, [editor]);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
package/dist/theme/editor.css
CHANGED
|
@@ -178,6 +178,10 @@
|
|
|
178
178
|
font-family: 'IBM Plex Serif', Georgia, serif;
|
|
179
179
|
font-size: 15px;
|
|
180
180
|
line-height: 1.75;
|
|
181
|
+
/* Scopes the footnote-reference counter to the whole document (editor.css
|
|
182
|
+
footnote rules below), so numbering is consistent regardless of which
|
|
183
|
+
blocks the references happen to sit in. */
|
|
184
|
+
counter-reset: likhari-footnote-ref;
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
.likhari-content-editable[dir='rtl'] {
|
|
@@ -335,6 +339,69 @@
|
|
|
335
339
|
margin-bottom: 0;
|
|
336
340
|
}
|
|
337
341
|
|
|
342
|
+
/* Footnotes (lexical-editor-spec.md §4.10). The bracketed number on both
|
|
343
|
+
the inline reference and its list entry is a CSS counter, not stored
|
|
344
|
+
state — Lexical insert/delete/reorder of the underlying nodes renumbers
|
|
345
|
+
both sequences automatically as long as list items stay in the same
|
|
346
|
+
order their references were inserted (FootnotePlugin's insert command
|
|
347
|
+
always appends in that order). */
|
|
348
|
+
.likhari-footnote-ref {
|
|
349
|
+
counter-increment: likhari-footnote-ref;
|
|
350
|
+
cursor: default;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.likhari-footnote-ref::after {
|
|
354
|
+
content: '[' counter(likhari-footnote-ref) ']';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.likhari-footnote-list {
|
|
358
|
+
counter-reset: likhari-footnote-item;
|
|
359
|
+
list-style: none;
|
|
360
|
+
margin: 2em 0 0 0;
|
|
361
|
+
padding-inline-start: 1.5em;
|
|
362
|
+
border-top: 1px solid var(--editor-line);
|
|
363
|
+
padding-top: 0.75em;
|
|
364
|
+
font-size: 0.9em;
|
|
365
|
+
color: var(--editor-ink-soft);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.likhari-footnote-item {
|
|
369
|
+
counter-increment: likhari-footnote-item;
|
|
370
|
+
margin: 0.3em 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.likhari-footnote-item::marker {
|
|
374
|
+
content: '[' counter(likhari-footnote-item) '] ';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Poetry couplets (lexical-editor-spec.md §4.11, editor-ui-design-spec.md §4).
|
|
378
|
+
Own type scale and spacing, distinct from body text — the .likhari-ltr/
|
|
379
|
+
.likhari-rtl per-block classes (above) already switch font-family for this
|
|
380
|
+
element same as any other block; these rules layer poetry's own size/
|
|
381
|
+
line-height/gap on top, combined with those same classes. */
|
|
382
|
+
.likhari-poetry {
|
|
383
|
+
margin: 0 0 1.4em 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.likhari-poetry.likhari-ltr {
|
|
387
|
+
font-size: 15px;
|
|
388
|
+
line-height: 1.6;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.likhari-poetry.likhari-rtl {
|
|
392
|
+
font-size: 20px;
|
|
393
|
+
line-height: 2.1;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.likhari-poetry--two-column .likhari-layout-container {
|
|
397
|
+
margin: 0;
|
|
398
|
+
gap: 16px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.likhari-poetry--two-column .likhari-layout-item {
|
|
402
|
+
text-align: inherit;
|
|
403
|
+
}
|
|
404
|
+
|
|
338
405
|
.likhari-table {
|
|
339
406
|
border-collapse: collapse;
|
|
340
407
|
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,kBAgDzB,CAAC"}
|
|
@@ -42,6 +42,10 @@ export const editorTheme = {
|
|
|
42
42
|
pageBreak: 'likhari-page-break',
|
|
43
43
|
layoutContainer: 'likhari-layout-container',
|
|
44
44
|
layoutItem: 'likhari-layout-item',
|
|
45
|
+
footnoteReference: 'likhari-footnote-ref',
|
|
46
|
+
footnoteList: 'likhari-footnote-list',
|
|
47
|
+
footnoteItem: 'likhari-footnote-item',
|
|
48
|
+
poetry: 'likhari-poetry',
|
|
45
49
|
table: 'likhari-table',
|
|
46
50
|
tableRow: 'likhari-table-row',
|
|
47
51
|
tableCell: 'likhari-table-cell',
|