@liminis/editor 0.1.0-rc.1
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/LICENSE +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- package/package.json +175 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { registerMarkdownShortcuts, HEADING, QUOTE, CODE,
|
|
4
|
+
// Text format transformers for inline markdown syntax
|
|
5
|
+
BOLD_STAR, BOLD_UNDERSCORE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH, INLINE_CODE,
|
|
6
|
+
// Text match transformers for links
|
|
7
|
+
LINK, } from '@lexical/markdown';
|
|
8
|
+
import { $createTextNode } from 'lexical';
|
|
9
|
+
import { LinkNode } from '@lexical/link';
|
|
10
|
+
import { $createCustomLinkNode, $createCustomListNode, $isCustomListNode, CustomListNode, $createCustomListItemNode, CustomListItemNode } from './nodes/index.js';
|
|
11
|
+
import { $createImageNode, ImageNode } from './nodes/ImageNode.js';
|
|
12
|
+
import { $createEquationNode, EquationNode } from './nodes/index.js';
|
|
13
|
+
import { getFileType } from '../../utils/file-types.js';
|
|
14
|
+
// Amount of spaces that define one indentation level, matching @lexical/markdown's own default.
|
|
15
|
+
const LIST_INDENT_SIZE = 4;
|
|
16
|
+
function getListIndent(whitespaces) {
|
|
17
|
+
const tabs = whitespaces.match(/\t/g);
|
|
18
|
+
const spaces = whitespaces.match(/ /g);
|
|
19
|
+
let indent = 0;
|
|
20
|
+
if (tabs) {
|
|
21
|
+
indent += tabs.length;
|
|
22
|
+
}
|
|
23
|
+
if (spaces) {
|
|
24
|
+
indent += Math.floor(spaces.length / LIST_INDENT_SIZE);
|
|
25
|
+
}
|
|
26
|
+
return indent;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Builds an `ElementTransformer.replace` for list markdown shortcuts ("- ", "1. ", "[] ")
|
|
30
|
+
* that creates `CustomListNode`s instead of going through `@lexical/markdown`'s built-in
|
|
31
|
+
* UNORDERED_LIST/ORDERED_LIST/CHECK_LIST transformers.
|
|
32
|
+
*
|
|
33
|
+
* The stock transformers call `@lexical/list`'s raw `$createListNode`, which constructs a
|
|
34
|
+
* plain `ListNode`. Since this editor replaces the registered 'list' node type with
|
|
35
|
+
* `CustomListNode` (to carry markdown loose/tight "spread" state through the round trip —
|
|
36
|
+
* see #898), constructing a plain `ListNode` throws
|
|
37
|
+
* ("Create node: Type list in node ListNode does not match registered node CustomListNode
|
|
38
|
+
* with the same type") the instant a user types a list shortcut. Mirrors the stock
|
|
39
|
+
* `listReplace` logic (list creation + merging with an adjacent list of the same type)
|
|
40
|
+
* with `$createCustomListNode` substituted for `$createListNode`.
|
|
41
|
+
*/
|
|
42
|
+
function createCustomListReplace(listType) {
|
|
43
|
+
return (parentNode, children, match, isImport) => {
|
|
44
|
+
const previousNode = parentNode.getPreviousSibling();
|
|
45
|
+
const nextNode = parentNode.getNextSibling();
|
|
46
|
+
const listItem = $createCustomListItemNode(listType === 'check' ? match[3]?.toLowerCase() === 'x' : undefined);
|
|
47
|
+
if ($isCustomListNode(nextNode) && nextNode.getListType() === listType) {
|
|
48
|
+
const firstChild = nextNode.getFirstChild();
|
|
49
|
+
if (firstChild !== null) {
|
|
50
|
+
firstChild.insertBefore(listItem);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
nextNode.append(listItem);
|
|
54
|
+
}
|
|
55
|
+
parentNode.remove();
|
|
56
|
+
}
|
|
57
|
+
else if ($isCustomListNode(previousNode) && previousNode.getListType() === listType) {
|
|
58
|
+
previousNode.append(listItem);
|
|
59
|
+
parentNode.remove();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const listNode = $createCustomListNode(listType, listType === 'number' ? Number(match[2]) : undefined);
|
|
63
|
+
listNode.append(listItem);
|
|
64
|
+
parentNode.replace(listNode);
|
|
65
|
+
}
|
|
66
|
+
listItem.append(...children);
|
|
67
|
+
if (!isImport) {
|
|
68
|
+
listItem.select(0, 0);
|
|
69
|
+
}
|
|
70
|
+
const indent = getListIndent(match[1]);
|
|
71
|
+
if (indent) {
|
|
72
|
+
listItem.setIndent(indent);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
77
|
+
export const UNORDERED_LIST = {
|
|
78
|
+
dependencies: [CustomListNode, CustomListItemNode],
|
|
79
|
+
export: () => null,
|
|
80
|
+
regExp: /^(\s*)[-*+]\s/,
|
|
81
|
+
replace: createCustomListReplace('bullet'),
|
|
82
|
+
type: 'element',
|
|
83
|
+
};
|
|
84
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
85
|
+
export const ORDERED_LIST = {
|
|
86
|
+
dependencies: [CustomListNode, CustomListItemNode],
|
|
87
|
+
export: () => null,
|
|
88
|
+
regExp: /^(\s*)(\d{1,})\.\s/,
|
|
89
|
+
replace: createCustomListReplace('number'),
|
|
90
|
+
type: 'element',
|
|
91
|
+
};
|
|
92
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
93
|
+
export const CHECK_LIST = {
|
|
94
|
+
dependencies: [CustomListNode, CustomListItemNode],
|
|
95
|
+
export: () => null,
|
|
96
|
+
regExp: /^(\s*)(?:[-*+]\s)?\s?(\[(\s|x)?\])\s/i,
|
|
97
|
+
replace: createCustomListReplace('check'),
|
|
98
|
+
type: 'element',
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Parses a wiki link alias and extracts format markers.
|
|
102
|
+
* Supports nested formats like ***bold italic***.
|
|
103
|
+
*
|
|
104
|
+
* @param alias - The raw alias text potentially containing format markers
|
|
105
|
+
* @returns Object with the plain text and array of formats to apply
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* parseFormattedAlias('**bold**') // { text: 'bold', formats: ['bold'] }
|
|
109
|
+
* parseFormattedAlias('*italic*') // { text: 'italic', formats: ['italic'] }
|
|
110
|
+
* parseFormattedAlias('***both***') // { text: 'both', formats: ['bold', 'italic'] }
|
|
111
|
+
* parseFormattedAlias('~~strike~~') // { text: 'strike', formats: ['strikethrough'] }
|
|
112
|
+
*/
|
|
113
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
114
|
+
export function parseFormattedAlias(alias) {
|
|
115
|
+
const formats = [];
|
|
116
|
+
let text = alias;
|
|
117
|
+
// Process from outermost to innermost format markers
|
|
118
|
+
// We loop to handle nested formats like ***bold italic*** or ~~**bold strike**~~
|
|
119
|
+
let changed = true;
|
|
120
|
+
while (changed && text.length >= 2) {
|
|
121
|
+
changed = false;
|
|
122
|
+
// Check for strikethrough: ~~text~~
|
|
123
|
+
if (text.startsWith('~~') && text.endsWith('~~') && text.length >= 4) {
|
|
124
|
+
const inner = text.slice(2, -2);
|
|
125
|
+
if (inner.length > 0) {
|
|
126
|
+
formats.push('strikethrough');
|
|
127
|
+
text = inner;
|
|
128
|
+
changed = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Check for bold: **text** (must check before single *)
|
|
133
|
+
if (text.startsWith('**') && text.endsWith('**') && text.length >= 4) {
|
|
134
|
+
const inner = text.slice(2, -2);
|
|
135
|
+
if (inner.length > 0) {
|
|
136
|
+
formats.push('bold');
|
|
137
|
+
text = inner;
|
|
138
|
+
changed = true;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Check for bold: __text__ (must check before single _)
|
|
143
|
+
if (text.startsWith('__') && text.endsWith('__') && text.length >= 4) {
|
|
144
|
+
const inner = text.slice(2, -2);
|
|
145
|
+
if (inner.length > 0) {
|
|
146
|
+
formats.push('bold');
|
|
147
|
+
text = inner;
|
|
148
|
+
changed = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Check for italic: *text* (but not if inner starts/ends with * which would be **)
|
|
153
|
+
if (text.startsWith('*') && text.endsWith('*') && text.length >= 2 && !text.startsWith('**') && !text.endsWith('**')) {
|
|
154
|
+
const inner = text.slice(1, -1);
|
|
155
|
+
if (inner.length > 0) {
|
|
156
|
+
formats.push('italic');
|
|
157
|
+
text = inner;
|
|
158
|
+
changed = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Check for italic: _text_ (but not if inner starts/ends with _ which would be __)
|
|
163
|
+
if (text.startsWith('_') && text.endsWith('_') && text.length >= 2 && !text.startsWith('__') && !text.endsWith('__')) {
|
|
164
|
+
const inner = text.slice(1, -1);
|
|
165
|
+
if (inner.length > 0) {
|
|
166
|
+
formats.push('italic');
|
|
167
|
+
text = inner;
|
|
168
|
+
changed = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { text, formats };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Custom IMAGE transformer for  markdown syntax.
|
|
177
|
+
* Creates an ImageNode block when the pattern is completed.
|
|
178
|
+
*/
|
|
179
|
+
const IMAGE = {
|
|
180
|
+
dependencies: [ImageNode],
|
|
181
|
+
// Export: convert ImageNode back to markdown
|
|
182
|
+
export: (node) => {
|
|
183
|
+
if (node instanceof ImageNode) {
|
|
184
|
+
const alt = node.getAlt();
|
|
185
|
+
const src = node.getSrc();
|
|
186
|
+
const title = node.getTitle();
|
|
187
|
+
if (title) {
|
|
188
|
+
return ``;
|
|
189
|
+
}
|
|
190
|
+
return ``;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
},
|
|
194
|
+
// Import regex for parsing markdown files (more permissive)
|
|
195
|
+
importRegExp: /!\[([^\]]*)\]\(([^()\s]+)(?:\s"([^"]*)")?\)/,
|
|
196
|
+
// Trigger regex for live typing (must end at cursor position)
|
|
197
|
+
regExp: /!\[([^\]]*)\]\(([^()\s]+)(?:\s"([^"]*)")?\)$/,
|
|
198
|
+
// Replace the matched text with an ImageNode
|
|
199
|
+
replace: (textNode, match) => {
|
|
200
|
+
const [, alt, src, title] = match;
|
|
201
|
+
const imageNode = $createImageNode(src, alt || '', { title });
|
|
202
|
+
textNode.replace(imageNode);
|
|
203
|
+
},
|
|
204
|
+
// Trigger character - transform happens when ) is typed
|
|
205
|
+
trigger: ')',
|
|
206
|
+
type: 'text-match',
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Custom BLOCK_EQUATION transformer for $$...$$ markdown syntax.
|
|
210
|
+
* Creates a block (display) EquationNode when the pattern is completed.
|
|
211
|
+
*/
|
|
212
|
+
const BLOCK_EQUATION = {
|
|
213
|
+
dependencies: [EquationNode],
|
|
214
|
+
export: (node) => {
|
|
215
|
+
if (node instanceof EquationNode && !node.isInline()) {
|
|
216
|
+
return `$$${node.getEquation()}$$`;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
},
|
|
220
|
+
// Import regex - matches $$...$$ anywhere
|
|
221
|
+
importRegExp: /\$\$([^$]+)\$\$/,
|
|
222
|
+
// Trigger regex - must end at cursor
|
|
223
|
+
regExp: /\$\$([^$]+)\$\$$/,
|
|
224
|
+
replace: (textNode, match) => {
|
|
225
|
+
const [, equation] = match;
|
|
226
|
+
const equationNode = $createEquationNode(equation.trim(), false);
|
|
227
|
+
textNode.replace(equationNode);
|
|
228
|
+
},
|
|
229
|
+
trigger: '$',
|
|
230
|
+
type: 'text-match',
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Custom INLINE_EQUATION transformer for $...$ markdown syntax.
|
|
234
|
+
* Creates an inline EquationNode when the pattern is completed.
|
|
235
|
+
* Note: Must come after BLOCK_EQUATION to avoid premature matching.
|
|
236
|
+
*/
|
|
237
|
+
const INLINE_EQUATION = {
|
|
238
|
+
dependencies: [EquationNode],
|
|
239
|
+
export: (node) => {
|
|
240
|
+
if (node instanceof EquationNode && node.isInline()) {
|
|
241
|
+
return `$${node.getEquation()}$`;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
},
|
|
245
|
+
// Import regex - matches $...$ but not $$...$$ (negative lookahead/lookbehind)
|
|
246
|
+
importRegExp: /(?<!\$)\$([^$]+)\$(?!\$)/,
|
|
247
|
+
// Trigger regex - matches single $ but not $$
|
|
248
|
+
regExp: /(?<!\$)\$([^$]+)\$$/,
|
|
249
|
+
replace: (textNode, match) => {
|
|
250
|
+
const [, equation] = match;
|
|
251
|
+
const equationNode = $createEquationNode(equation.trim(), true);
|
|
252
|
+
textNode.replace(equationNode);
|
|
253
|
+
},
|
|
254
|
+
trigger: '$',
|
|
255
|
+
type: 'text-match',
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Custom WIKI_LINK transformer for [[target|alias]] and [[target]] markdown syntax.
|
|
259
|
+
* Creates a LinkNode that will be exported back as a wiki-link.
|
|
260
|
+
* The URL is set to target.md so the exporter knows it's a wiki-link.
|
|
261
|
+
*
|
|
262
|
+
* Supports:
|
|
263
|
+
* - [[page]] → page.md
|
|
264
|
+
* - [[page|alias]] → page.md with display text "alias"
|
|
265
|
+
* - [[#anchor]] → #anchor (anchor-only, no .md)
|
|
266
|
+
* - [[page#anchor]] → page.md#anchor
|
|
267
|
+
* - [[page#anchor|alias]] → page.md#anchor with display text "alias"
|
|
268
|
+
*/
|
|
269
|
+
const WIKI_LINK = {
|
|
270
|
+
dependencies: [LinkNode],
|
|
271
|
+
export: (_node) => {
|
|
272
|
+
void _node;
|
|
273
|
+
// Export is handled by lexicalToMdast.ts convertLinkNode
|
|
274
|
+
// which detects .md URLs and exports as wiki-links
|
|
275
|
+
return null;
|
|
276
|
+
},
|
|
277
|
+
// Import regex - matches [[target|alias]], [[target|]] (empty alias), or [[target]]
|
|
278
|
+
importRegExp: /\[\[([^\]|]+)(?:\|([^\]]*))?\]\]/,
|
|
279
|
+
// Trigger regex - must end at cursor position
|
|
280
|
+
regExp: /\[\[([^\]|]+)(?:\|([^\]]*))?\]\]$/,
|
|
281
|
+
replace: (textNode, match) => {
|
|
282
|
+
const [, target, alias] = match;
|
|
283
|
+
// Handle anchors properly
|
|
284
|
+
let url;
|
|
285
|
+
if (target.startsWith('#')) {
|
|
286
|
+
// Anchor-only link: [[#anchor]] → #anchor
|
|
287
|
+
url = target;
|
|
288
|
+
}
|
|
289
|
+
else if (target.includes('#')) {
|
|
290
|
+
// Path with anchor: [[page#anchor]] → page.md#anchor
|
|
291
|
+
const [path, anchor] = target.split('#', 2);
|
|
292
|
+
const pathWithExt = getFileType(path) !== 'unknown' ? path : `${path}.md`;
|
|
293
|
+
url = `${pathWithExt}#${anchor}`;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
// Simple path: [[page]] → page.md (unless it already has a recognized extension)
|
|
297
|
+
url = getFileType(target) !== 'unknown' ? target : `${target}.md`;
|
|
298
|
+
}
|
|
299
|
+
// Parse alias for format markers (e.g., **bold**, *italic*, ~~strikethrough~~)
|
|
300
|
+
const rawDisplayText = alias || target;
|
|
301
|
+
const { text: displayText, formats } = parseFormattedAlias(rawDisplayText);
|
|
302
|
+
const linkNode = $createCustomLinkNode(url);
|
|
303
|
+
const textNodeChild = $createTextNode(displayText);
|
|
304
|
+
// Apply detected formats to the text node
|
|
305
|
+
for (const format of formats) {
|
|
306
|
+
textNodeChild.toggleFormat(format);
|
|
307
|
+
}
|
|
308
|
+
linkNode.append(textNodeChild);
|
|
309
|
+
textNode.replace(linkNode);
|
|
310
|
+
},
|
|
311
|
+
trigger: ']',
|
|
312
|
+
type: 'text-match',
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Plugin that enables markdown shortcuts for block types and inline formatting.
|
|
316
|
+
*
|
|
317
|
+
* Block shortcuts (triggered at start of line):
|
|
318
|
+
* - "- " or "* " → Bulleted list
|
|
319
|
+
* - "1. " → Numbered list
|
|
320
|
+
* - "[] " or "[ ] " → Todo/checkbox list
|
|
321
|
+
* - "# ", "## ", "### " → Headings (h1-h6)
|
|
322
|
+
* - "> " → Block quote
|
|
323
|
+
* - "```" → Code block
|
|
324
|
+
*
|
|
325
|
+
* Inline formatting (Slack-style - transforms when closing delimiter is typed):
|
|
326
|
+
* - *text* → Italic
|
|
327
|
+
* - **text** → Bold
|
|
328
|
+
* - ***text*** → Bold + Italic
|
|
329
|
+
* - _text_ → Italic
|
|
330
|
+
* - __text__ → Bold
|
|
331
|
+
* - ___text___ → Bold + Italic
|
|
332
|
+
* - `text` → Inline code
|
|
333
|
+
* - ~~text~~ → Strikethrough
|
|
334
|
+
* - [text](url) → Link
|
|
335
|
+
* - [[target|alias]] → Wiki-link (Obsidian/Foam style)
|
|
336
|
+
* -  → Image
|
|
337
|
+
* - $equation$ → Inline math (KaTeX)
|
|
338
|
+
* - $$equation$$ → Block math (KaTeX)
|
|
339
|
+
*/
|
|
340
|
+
export function MarkdownShortcutsPlugin() {
|
|
341
|
+
const [editor] = useLexicalComposerContext();
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
// Register both element (block) and text format (inline) transformers
|
|
344
|
+
// This enables "live preview" markdown editing where typing *text* becomes italic, **text** becomes bold
|
|
345
|
+
return registerMarkdownShortcuts(editor, [
|
|
346
|
+
// Block-level transformers
|
|
347
|
+
UNORDERED_LIST,
|
|
348
|
+
ORDERED_LIST,
|
|
349
|
+
CHECK_LIST,
|
|
350
|
+
HEADING,
|
|
351
|
+
QUOTE,
|
|
352
|
+
CODE,
|
|
353
|
+
// Inline text format transformers (Slack-style resolution)
|
|
354
|
+
// Order matters: more specific patterns (longer) should come first
|
|
355
|
+
BOLD_ITALIC_STAR, // ***text*** → bold + italic
|
|
356
|
+
BOLD_ITALIC_UNDERSCORE, // ___text___ → bold + italic
|
|
357
|
+
BOLD_STAR, // **text** → bold
|
|
358
|
+
BOLD_UNDERSCORE, // __text__ → bold
|
|
359
|
+
ITALIC_STAR, // *text* → italic
|
|
360
|
+
ITALIC_UNDERSCORE, // _text_ → italic
|
|
361
|
+
STRIKETHROUGH, // ~~text~~ → strikethrough
|
|
362
|
+
INLINE_CODE, // `text` → code
|
|
363
|
+
// Link and image transformers
|
|
364
|
+
IMAGE, //  → image (must come before LINK)
|
|
365
|
+
WIKI_LINK, // [[target|alias]] → wiki-link (must come before LINK)
|
|
366
|
+
LINK, // [text](url) → link
|
|
367
|
+
// Equation transformers (block must come before inline)
|
|
368
|
+
BLOCK_EQUATION, // $$equation$$ → block math
|
|
369
|
+
INLINE_EQUATION, // $equation$ → inline math
|
|
370
|
+
]);
|
|
371
|
+
}, [editor]);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OrderedTaskListPlugin - Click-to-toggle for ordered task-item checkboxes.
|
|
3
|
+
*
|
|
4
|
+
* @lexical/react's CheckListPlugin only wires up click/keyboard handling for
|
|
5
|
+
* a list whose DOM `__lexicalListType` is 'check' (see @lexical/list's
|
|
6
|
+
* registerCheckList/handleCheckItemEvent) — it never fires for our
|
|
7
|
+
* 'number'-typed ordered lists, so this plugin reimplements the click half
|
|
8
|
+
* of that behavior for CustomListItemNode's own ordered-task classes
|
|
9
|
+
* (CustomListItemNode.tsx's createDOM/updateDOM override). The hit-box
|
|
10
|
+
* geometry (clientX vs. the rendered ::before pseudo-element's width) is
|
|
11
|
+
* copied from @lexical/list's handleCheckItemEvent so clicking anywhere
|
|
12
|
+
* across the visible checkbox — not just its exact CSS box — toggles it,
|
|
13
|
+
* matching the feel of the unordered checklist.
|
|
14
|
+
*
|
|
15
|
+
* Toggling calls setTaskChecked() exclusively, never setChecked()/
|
|
16
|
+
* toggleChecked() — see CustomListItemNode's docstring and
|
|
17
|
+
* ListNode's updateChildrenListItemValue transform, which resets any
|
|
18
|
+
* non-null raw __checked back to undefined for a non-'check' list and would
|
|
19
|
+
* cascade into wiping __taskChecked too.
|
|
20
|
+
*/
|
|
21
|
+
export declare function OrderedTaskListPlugin(): null;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { calculateZoomLevel } from '@lexical/utils';
|
|
4
|
+
import { $getNearestNodeFromDOMNode } from 'lexical';
|
|
5
|
+
import { $isCustomListItemNode, ORDERED_TASK_CHECKED_CLASS, ORDERED_TASK_UNCHECKED_CLASS } from './nodes/CustomListItemNode.js';
|
|
6
|
+
/**
|
|
7
|
+
* OrderedTaskListPlugin - Click-to-toggle for ordered task-item checkboxes.
|
|
8
|
+
*
|
|
9
|
+
* @lexical/react's CheckListPlugin only wires up click/keyboard handling for
|
|
10
|
+
* a list whose DOM `__lexicalListType` is 'check' (see @lexical/list's
|
|
11
|
+
* registerCheckList/handleCheckItemEvent) — it never fires for our
|
|
12
|
+
* 'number'-typed ordered lists, so this plugin reimplements the click half
|
|
13
|
+
* of that behavior for CustomListItemNode's own ordered-task classes
|
|
14
|
+
* (CustomListItemNode.tsx's createDOM/updateDOM override). The hit-box
|
|
15
|
+
* geometry (clientX vs. the rendered ::before pseudo-element's width) is
|
|
16
|
+
* copied from @lexical/list's handleCheckItemEvent so clicking anywhere
|
|
17
|
+
* across the visible checkbox — not just its exact CSS box — toggles it,
|
|
18
|
+
* matching the feel of the unordered checklist.
|
|
19
|
+
*
|
|
20
|
+
* Toggling calls setTaskChecked() exclusively, never setChecked()/
|
|
21
|
+
* toggleChecked() — see CustomListItemNode's docstring and
|
|
22
|
+
* ListNode's updateChildrenListItemValue transform, which resets any
|
|
23
|
+
* non-null raw __checked back to undefined for a non-'check' list and would
|
|
24
|
+
* cascade into wiping __taskChecked too.
|
|
25
|
+
*/
|
|
26
|
+
export function OrderedTaskListPlugin() {
|
|
27
|
+
const [editor] = useLexicalComposerContext();
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const rootElement = editor.getRootElement();
|
|
30
|
+
if (!rootElement)
|
|
31
|
+
return;
|
|
32
|
+
const isOrderedTaskTarget = (target) => target instanceof HTMLElement &&
|
|
33
|
+
(target.classList.contains(ORDERED_TASK_CHECKED_CLASS) || target.classList.contains(ORDERED_TASK_UNCHECKED_CLASS));
|
|
34
|
+
const isCheckboxHit = (target, clientX) => {
|
|
35
|
+
const rect = target.getBoundingClientRect();
|
|
36
|
+
const zoom = calculateZoomLevel(target);
|
|
37
|
+
const clientXInPixels = clientX / zoom;
|
|
38
|
+
const beforeStyles = window.getComputedStyle(target, '::before');
|
|
39
|
+
const beforeWidthInPixels = parseFloat(beforeStyles.width);
|
|
40
|
+
return target.dir === 'rtl'
|
|
41
|
+
? clientXInPixels < rect.right && clientXInPixels > rect.right - beforeWidthInPixels
|
|
42
|
+
: clientXInPixels > rect.left && clientXInPixels < rect.left + beforeWidthInPixels;
|
|
43
|
+
};
|
|
44
|
+
const handleMouseDown = (event) => {
|
|
45
|
+
const target = event.target;
|
|
46
|
+
if (!isOrderedTaskTarget(target))
|
|
47
|
+
return;
|
|
48
|
+
if (!isCheckboxHit(target, event.clientX))
|
|
49
|
+
return;
|
|
50
|
+
// Prevents the caret moving when clicking on the checkbox, matching
|
|
51
|
+
// CheckListPlugin's handleSelectDefaults for the unordered case.
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
};
|
|
54
|
+
const handleClick = (event) => {
|
|
55
|
+
const target = event.target;
|
|
56
|
+
if (!isOrderedTaskTarget(target))
|
|
57
|
+
return;
|
|
58
|
+
if (!isCheckboxHit(target, event.clientX))
|
|
59
|
+
return;
|
|
60
|
+
if (!editor.isEditable())
|
|
61
|
+
return;
|
|
62
|
+
const domNode = target;
|
|
63
|
+
editor.update(() => {
|
|
64
|
+
const node = $getNearestNodeFromDOMNode(domNode);
|
|
65
|
+
if ($isCustomListItemNode(node)) {
|
|
66
|
+
node.setTaskChecked(!node.getTaskChecked());
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
rootElement.addEventListener('mousedown', handleMouseDown, { capture: true });
|
|
71
|
+
rootElement.addEventListener('click', handleClick);
|
|
72
|
+
return () => {
|
|
73
|
+
rootElement.removeEventListener('mousedown', handleMouseDown, { capture: true });
|
|
74
|
+
rootElement.removeEventListener('click', handleClick);
|
|
75
|
+
};
|
|
76
|
+
}, [editor]);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SearchPlugin(): import("react").JSX.Element | null;
|