@lexical/markdown 0.13.0 → 0.14.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/LexicalMarkdown.dev.esm.js +1009 -0
- package/LexicalMarkdown.dev.js +19 -6
- package/LexicalMarkdown.esm.js +32 -0
- package/LexicalMarkdown.js +1 -1
- package/LexicalMarkdown.prod.esm.js +7 -0
- package/LexicalMarkdown.prod.js +23 -23
- package/README.md +2 -0
- package/package.json +11 -9
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { $getRoot, $isElementNode, $isDecoratorNode, $isLineBreakNode, $isTextNode, $getSelection, $isParagraphNode, $createTextNode, $createParagraphNode, $createLineBreakNode, $isRangeSelection, $isRootOrShadowRoot, $createRangeSelection, $setSelection } from 'lexical';
|
|
8
|
+
import { $createCodeNode, $isCodeNode, CodeNode } from '@lexical/code';
|
|
9
|
+
import { $isListNode, $isListItemNode, ListNode, ListItemNode, $createListItemNode, $createListNode } from '@lexical/list';
|
|
10
|
+
import { $isQuoteNode, HeadingNode, $isHeadingNode, QuoteNode, $createQuoteNode, $createHeadingNode } from '@lexical/rich-text';
|
|
11
|
+
import { $findMatchingParent } from '@lexical/utils';
|
|
12
|
+
import { LinkNode, $isLinkNode, $createLinkNode } from '@lexical/link';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
16
|
+
*
|
|
17
|
+
* This source code is licensed under the MIT license found in the
|
|
18
|
+
* LICENSE file in the root directory of this source tree.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
function indexBy(list, callback) {
|
|
22
|
+
const index = {};
|
|
23
|
+
for (const item of list) {
|
|
24
|
+
const key = callback(item);
|
|
25
|
+
if (index[key]) {
|
|
26
|
+
index[key].push(item);
|
|
27
|
+
} else {
|
|
28
|
+
index[key] = [item];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return index;
|
|
32
|
+
}
|
|
33
|
+
function transformersByType(transformers) {
|
|
34
|
+
const byType = indexBy(transformers, t => t.type);
|
|
35
|
+
return {
|
|
36
|
+
element: byType.element || [],
|
|
37
|
+
textFormat: byType['text-format'] || [],
|
|
38
|
+
textMatch: byType['text-match'] || []
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const PUNCTUATION_OR_SPACE = /[!-/:-@[-`{-~\s]/;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
45
|
+
*
|
|
46
|
+
* This source code is licensed under the MIT license found in the
|
|
47
|
+
* LICENSE file in the root directory of this source tree.
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
function createMarkdownExport(transformers) {
|
|
51
|
+
const byType = transformersByType(transformers);
|
|
52
|
+
|
|
53
|
+
// Export only uses text formats that are responsible for single format
|
|
54
|
+
// e.g. it will filter out *** (bold, italic) and instead use separate ** and *
|
|
55
|
+
const textFormatTransformers = byType.textFormat.filter(transformer => transformer.format.length === 1);
|
|
56
|
+
return node => {
|
|
57
|
+
const output = [];
|
|
58
|
+
const children = (node || $getRoot()).getChildren();
|
|
59
|
+
for (const child of children) {
|
|
60
|
+
const result = exportTopLevelElements(child, byType.element, textFormatTransformers, byType.textMatch);
|
|
61
|
+
if (result != null) {
|
|
62
|
+
output.push(result);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return output.join('\n\n');
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function exportTopLevelElements(node, elementTransformers, textTransformersIndex, textMatchTransformers) {
|
|
69
|
+
for (const transformer of elementTransformers) {
|
|
70
|
+
const result = transformer.export(node, _node => exportChildren(_node, textTransformersIndex, textMatchTransformers));
|
|
71
|
+
if (result != null) {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if ($isElementNode(node)) {
|
|
76
|
+
return exportChildren(node, textTransformersIndex, textMatchTransformers);
|
|
77
|
+
} else if ($isDecoratorNode(node)) {
|
|
78
|
+
return node.getTextContent();
|
|
79
|
+
} else {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function exportChildren(node, textTransformersIndex, textMatchTransformers) {
|
|
84
|
+
const output = [];
|
|
85
|
+
const children = node.getChildren();
|
|
86
|
+
mainLoop: for (const child of children) {
|
|
87
|
+
for (const transformer of textMatchTransformers) {
|
|
88
|
+
const result = transformer.export(child, parentNode => exportChildren(parentNode, textTransformersIndex, textMatchTransformers), (textNode, textContent) => exportTextFormat(textNode, textContent, textTransformersIndex));
|
|
89
|
+
if (result != null) {
|
|
90
|
+
output.push(result);
|
|
91
|
+
continue mainLoop;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if ($isLineBreakNode(child)) {
|
|
95
|
+
output.push('\n');
|
|
96
|
+
} else if ($isTextNode(child)) {
|
|
97
|
+
output.push(exportTextFormat(child, child.getTextContent(), textTransformersIndex));
|
|
98
|
+
} else if ($isElementNode(child)) {
|
|
99
|
+
output.push(exportChildren(child, textTransformersIndex, textMatchTransformers));
|
|
100
|
+
} else if ($isDecoratorNode(child)) {
|
|
101
|
+
output.push(child.getTextContent());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return output.join('');
|
|
105
|
+
}
|
|
106
|
+
function exportTextFormat(node, textContent, textTransformers) {
|
|
107
|
+
// This function handles the case of a string looking like this: " foo "
|
|
108
|
+
// Where it would be invalid markdown to generate: "** foo **"
|
|
109
|
+
// We instead want to trim the whitespace out, apply formatting, and then
|
|
110
|
+
// bring the whitespace back. So our returned string looks like this: " **foo** "
|
|
111
|
+
const frozenString = textContent.trim();
|
|
112
|
+
let output = frozenString;
|
|
113
|
+
const applied = new Set();
|
|
114
|
+
for (const transformer of textTransformers) {
|
|
115
|
+
const format = transformer.format[0];
|
|
116
|
+
const tag = transformer.tag;
|
|
117
|
+
if (hasFormat(node, format) && !applied.has(format)) {
|
|
118
|
+
// Multiple tags might be used for the same format (*, _)
|
|
119
|
+
applied.add(format);
|
|
120
|
+
// Prevent adding opening tag is already opened by the previous sibling
|
|
121
|
+
const previousNode = getTextSibling(node, true);
|
|
122
|
+
if (!hasFormat(previousNode, format)) {
|
|
123
|
+
output = tag + output;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Prevent adding closing tag if next sibling will do it
|
|
127
|
+
const nextNode = getTextSibling(node, false);
|
|
128
|
+
if (!hasFormat(nextNode, format)) {
|
|
129
|
+
output += tag;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Replace trimmed version of textContent ensuring surrounding whitespace is not modified
|
|
135
|
+
return textContent.replace(frozenString, () => output);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get next or previous text sibling a text node, including cases
|
|
139
|
+
// when it's a child of inline element (e.g. link)
|
|
140
|
+
function getTextSibling(node, backward) {
|
|
141
|
+
let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
|
|
142
|
+
if (!sibling) {
|
|
143
|
+
const parent = node.getParentOrThrow();
|
|
144
|
+
if (parent.isInline()) {
|
|
145
|
+
sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
while (sibling) {
|
|
149
|
+
if ($isElementNode(sibling)) {
|
|
150
|
+
if (!sibling.isInline()) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const descendant = backward ? sibling.getLastDescendant() : sibling.getFirstDescendant();
|
|
154
|
+
if ($isTextNode(descendant)) {
|
|
155
|
+
return descendant;
|
|
156
|
+
} else {
|
|
157
|
+
sibling = backward ? sibling.getPreviousSibling() : sibling.getNextSibling();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if ($isTextNode(sibling)) {
|
|
161
|
+
return sibling;
|
|
162
|
+
}
|
|
163
|
+
if (!$isElementNode(sibling)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function hasFormat(node, format) {
|
|
170
|
+
return $isTextNode(node) && node.hasFormat(format);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
175
|
+
*
|
|
176
|
+
* This source code is licensed under the MIT license found in the
|
|
177
|
+
* LICENSE file in the root directory of this source tree.
|
|
178
|
+
*
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
185
|
+
*
|
|
186
|
+
* This source code is licensed under the MIT license found in the
|
|
187
|
+
* LICENSE file in the root directory of this source tree.
|
|
188
|
+
*
|
|
189
|
+
*/
|
|
190
|
+
const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
|
|
191
|
+
CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
192
|
+
CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
193
|
+
CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
|
|
194
|
+
const IS_SAFARI = CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
|
|
195
|
+
const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
196
|
+
const IS_ANDROID = CAN_USE_DOM && /Android/.test(navigator.userAgent);
|
|
197
|
+
|
|
198
|
+
// Keep these in case we need to use them in the future.
|
|
199
|
+
// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
|
|
200
|
+
const IS_CHROME = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);
|
|
201
|
+
// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
|
|
202
|
+
|
|
203
|
+
CAN_USE_DOM && IS_ANDROID && IS_CHROME;
|
|
204
|
+
const IS_APPLE_WEBKIT = CAN_USE_DOM && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && !IS_CHROME;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
208
|
+
*
|
|
209
|
+
* This source code is licensed under the MIT license found in the
|
|
210
|
+
* LICENSE file in the root directory of this source tree.
|
|
211
|
+
*
|
|
212
|
+
*/
|
|
213
|
+
const MARKDOWN_EMPTY_LINE_REG_EXP = /^\s{0,3}$/;
|
|
214
|
+
const CODE_BLOCK_REG_EXP = /^```(\w{1,10})?\s?$/;
|
|
215
|
+
function createMarkdownImport(transformers) {
|
|
216
|
+
const byType = transformersByType(transformers);
|
|
217
|
+
const textFormatTransformersIndex = createTextFormatTransformersIndex(byType.textFormat);
|
|
218
|
+
return (markdownString, node) => {
|
|
219
|
+
const lines = markdownString.split('\n');
|
|
220
|
+
const linesLength = lines.length;
|
|
221
|
+
const root = node || $getRoot();
|
|
222
|
+
root.clear();
|
|
223
|
+
for (let i = 0; i < linesLength; i++) {
|
|
224
|
+
const lineText = lines[i];
|
|
225
|
+
// Codeblocks are processed first as anything inside such block
|
|
226
|
+
// is ignored for further processing
|
|
227
|
+
// TODO:
|
|
228
|
+
// Abstract it to be dynamic as other transformers (add multiline match option)
|
|
229
|
+
const [codeBlockNode, shiftedIndex] = importCodeBlock(lines, i, root);
|
|
230
|
+
if (codeBlockNode != null) {
|
|
231
|
+
i = shiftedIndex;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
importBlocks(lineText, root, byType.element, textFormatTransformersIndex, byType.textMatch);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Removing empty paragraphs as md does not really
|
|
238
|
+
// allow empty lines and uses them as delimiter
|
|
239
|
+
const children = root.getChildren();
|
|
240
|
+
for (const child of children) {
|
|
241
|
+
if (isEmptyParagraph(child) && root.getChildrenSize() > 1) {
|
|
242
|
+
child.remove();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if ($getSelection() !== null) {
|
|
246
|
+
root.selectEnd();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function isEmptyParagraph(node) {
|
|
251
|
+
if (!$isParagraphNode(node)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const firstChild = node.getFirstChild();
|
|
255
|
+
return firstChild == null || node.getChildrenSize() === 1 && $isTextNode(firstChild) && MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent());
|
|
256
|
+
}
|
|
257
|
+
function importBlocks(lineText, rootNode, elementTransformers, textFormatTransformersIndex, textMatchTransformers) {
|
|
258
|
+
const lineTextTrimmed = lineText.trim();
|
|
259
|
+
const textNode = $createTextNode(lineTextTrimmed);
|
|
260
|
+
const elementNode = $createParagraphNode();
|
|
261
|
+
elementNode.append(textNode);
|
|
262
|
+
rootNode.append(elementNode);
|
|
263
|
+
for (const {
|
|
264
|
+
regExp,
|
|
265
|
+
replace
|
|
266
|
+
} of elementTransformers) {
|
|
267
|
+
const match = lineText.match(regExp);
|
|
268
|
+
if (match) {
|
|
269
|
+
textNode.setTextContent(lineText.slice(match[0].length));
|
|
270
|
+
replace(elementNode, [textNode], match, true);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers);
|
|
275
|
+
|
|
276
|
+
// If no transformer found and we left with original paragraph node
|
|
277
|
+
// can check if its content can be appended to the previous node
|
|
278
|
+
// if it's a paragraph, quote or list
|
|
279
|
+
if (elementNode.isAttached() && lineTextTrimmed.length > 0) {
|
|
280
|
+
const previousNode = elementNode.getPreviousSibling();
|
|
281
|
+
if ($isParagraphNode(previousNode) || $isQuoteNode(previousNode) || $isListNode(previousNode)) {
|
|
282
|
+
let targetNode = previousNode;
|
|
283
|
+
if ($isListNode(previousNode)) {
|
|
284
|
+
const lastDescendant = previousNode.getLastDescendant();
|
|
285
|
+
if (lastDescendant == null) {
|
|
286
|
+
targetNode = null;
|
|
287
|
+
} else {
|
|
288
|
+
targetNode = $findMatchingParent(lastDescendant, $isListItemNode);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (targetNode != null && targetNode.getTextContentSize() > 0) {
|
|
292
|
+
targetNode.splice(targetNode.getChildrenSize(), 0, [$createLineBreakNode(), ...elementNode.getChildren()]);
|
|
293
|
+
elementNode.remove();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function importCodeBlock(lines, startLineIndex, rootNode) {
|
|
299
|
+
const openMatch = lines[startLineIndex].match(CODE_BLOCK_REG_EXP);
|
|
300
|
+
if (openMatch) {
|
|
301
|
+
let endLineIndex = startLineIndex;
|
|
302
|
+
const linesLength = lines.length;
|
|
303
|
+
while (++endLineIndex < linesLength) {
|
|
304
|
+
const closeMatch = lines[endLineIndex].match(CODE_BLOCK_REG_EXP);
|
|
305
|
+
if (closeMatch) {
|
|
306
|
+
const codeBlockNode = $createCodeNode(openMatch[1]);
|
|
307
|
+
const textNode = $createTextNode(lines.slice(startLineIndex + 1, endLineIndex).join('\n'));
|
|
308
|
+
codeBlockNode.append(textNode);
|
|
309
|
+
rootNode.append(codeBlockNode);
|
|
310
|
+
return [codeBlockNode, endLineIndex];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return [null, startLineIndex];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Processing text content and replaces text format tags.
|
|
318
|
+
// It takes outermost tag match and its content, creates text node with
|
|
319
|
+
// format based on tag and then recursively executed over node's content
|
|
320
|
+
//
|
|
321
|
+
// E.g. for "*Hello **world**!*" string it will create text node with
|
|
322
|
+
// "Hello **world**!" content and italic format and run recursively over
|
|
323
|
+
// its content to transform "**world**" part
|
|
324
|
+
function importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers) {
|
|
325
|
+
const textContent = textNode.getTextContent();
|
|
326
|
+
const match = findOutermostMatch(textContent, textFormatTransformersIndex);
|
|
327
|
+
if (!match) {
|
|
328
|
+
// Once text format processing is done run text match transformers, as it
|
|
329
|
+
// only can span within single text node (unline formats that can cover multiple nodes)
|
|
330
|
+
importTextMatchTransformers(textNode, textMatchTransformers);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
let currentNode, remainderNode, leadingNode;
|
|
334
|
+
|
|
335
|
+
// If matching full content there's no need to run splitText and can reuse existing textNode
|
|
336
|
+
// to update its content and apply format. E.g. for **_Hello_** string after applying bold
|
|
337
|
+
// format (**) it will reuse the same text node to apply italic (_)
|
|
338
|
+
if (match[0] === textContent) {
|
|
339
|
+
currentNode = textNode;
|
|
340
|
+
} else {
|
|
341
|
+
const startIndex = match.index || 0;
|
|
342
|
+
const endIndex = startIndex + match[0].length;
|
|
343
|
+
if (startIndex === 0) {
|
|
344
|
+
[currentNode, remainderNode] = textNode.splitText(endIndex);
|
|
345
|
+
} else {
|
|
346
|
+
[leadingNode, currentNode, remainderNode] = textNode.splitText(startIndex, endIndex);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
currentNode.setTextContent(match[2]);
|
|
350
|
+
const transformer = textFormatTransformersIndex.transformersByTag[match[1]];
|
|
351
|
+
if (transformer) {
|
|
352
|
+
for (const format of transformer.format) {
|
|
353
|
+
if (!currentNode.hasFormat(format)) {
|
|
354
|
+
currentNode.toggleFormat(format);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Recursively run over inner text if it's not inline code
|
|
360
|
+
if (!currentNode.hasFormat('code')) {
|
|
361
|
+
importTextFormatTransformers(currentNode, textFormatTransformersIndex, textMatchTransformers);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Run over leading/remaining text if any
|
|
365
|
+
if (leadingNode) {
|
|
366
|
+
importTextFormatTransformers(leadingNode, textFormatTransformersIndex, textMatchTransformers);
|
|
367
|
+
}
|
|
368
|
+
if (remainderNode) {
|
|
369
|
+
importTextFormatTransformers(remainderNode, textFormatTransformersIndex, textMatchTransformers);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function importTextMatchTransformers(textNode_, textMatchTransformers) {
|
|
373
|
+
let textNode = textNode_;
|
|
374
|
+
mainLoop: while (textNode) {
|
|
375
|
+
for (const transformer of textMatchTransformers) {
|
|
376
|
+
const match = textNode.getTextContent().match(transformer.importRegExp);
|
|
377
|
+
if (!match) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const startIndex = match.index || 0;
|
|
381
|
+
const endIndex = startIndex + match[0].length;
|
|
382
|
+
let replaceNode, newTextNode;
|
|
383
|
+
if (startIndex === 0) {
|
|
384
|
+
[replaceNode, textNode] = textNode.splitText(endIndex);
|
|
385
|
+
} else {
|
|
386
|
+
[, replaceNode, newTextNode] = textNode.splitText(startIndex, endIndex);
|
|
387
|
+
}
|
|
388
|
+
if (newTextNode) {
|
|
389
|
+
importTextMatchTransformers(newTextNode, textMatchTransformers);
|
|
390
|
+
}
|
|
391
|
+
transformer.replace(replaceNode, match);
|
|
392
|
+
continue mainLoop;
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Finds first "<tag>content<tag>" match that is not nested into another tag
|
|
399
|
+
function findOutermostMatch(textContent, textTransformersIndex) {
|
|
400
|
+
const openTagsMatch = textContent.match(textTransformersIndex.openTagsRegExp);
|
|
401
|
+
if (openTagsMatch == null) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
for (const match of openTagsMatch) {
|
|
405
|
+
// Open tags reg exp might capture leading space so removing it
|
|
406
|
+
// before using match to find transformer
|
|
407
|
+
const tag = match.replace(/^\s/, '');
|
|
408
|
+
const fullMatchRegExp = textTransformersIndex.fullMatchRegExpByTag[tag];
|
|
409
|
+
if (fullMatchRegExp == null) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const fullMatch = textContent.match(fullMatchRegExp);
|
|
413
|
+
const transformer = textTransformersIndex.transformersByTag[tag];
|
|
414
|
+
if (fullMatch != null && transformer != null) {
|
|
415
|
+
if (transformer.intraword !== false) {
|
|
416
|
+
return fullMatch;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// For non-intraword transformers checking if it's within a word
|
|
420
|
+
// or surrounded with space/punctuation/newline
|
|
421
|
+
const {
|
|
422
|
+
index = 0
|
|
423
|
+
} = fullMatch;
|
|
424
|
+
const beforeChar = textContent[index - 1];
|
|
425
|
+
const afterChar = textContent[index + fullMatch[0].length];
|
|
426
|
+
if ((!beforeChar || PUNCTUATION_OR_SPACE.test(beforeChar)) && (!afterChar || PUNCTUATION_OR_SPACE.test(afterChar))) {
|
|
427
|
+
return fullMatch;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function createTextFormatTransformersIndex(textTransformers) {
|
|
434
|
+
const transformersByTag = {};
|
|
435
|
+
const fullMatchRegExpByTag = {};
|
|
436
|
+
const openTagsRegExp = [];
|
|
437
|
+
const escapeRegExp = `(?<![\\\\])`;
|
|
438
|
+
for (const transformer of textTransformers) {
|
|
439
|
+
const {
|
|
440
|
+
tag
|
|
441
|
+
} = transformer;
|
|
442
|
+
transformersByTag[tag] = transformer;
|
|
443
|
+
const tagRegExp = tag.replace(/(\*|\^|\+)/g, '\\$1');
|
|
444
|
+
openTagsRegExp.push(tagRegExp);
|
|
445
|
+
if (IS_SAFARI || IS_IOS || IS_APPLE_WEBKIT) {
|
|
446
|
+
fullMatchRegExpByTag[tag] = new RegExp(`(${tagRegExp})(?![${tagRegExp}\\s])(.*?[^${tagRegExp}\\s])${tagRegExp}(?!${tagRegExp})`);
|
|
447
|
+
} else {
|
|
448
|
+
fullMatchRegExpByTag[tag] = new RegExp(`(?<![\\\\${tagRegExp}])(${tagRegExp})((\\\\${tagRegExp})?.*?[^${tagRegExp}\\s](\\\\${tagRegExp})?)((?<!\\\\)|(?<=\\\\\\\\))(${tagRegExp})(?![\\\\${tagRegExp}])`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
// Reg exp to find open tag + content + close tag
|
|
453
|
+
fullMatchRegExpByTag,
|
|
454
|
+
// Reg exp to find opening tags
|
|
455
|
+
openTagsRegExp: new RegExp((IS_SAFARI || IS_IOS || IS_APPLE_WEBKIT ? '' : `${escapeRegExp}`) + '(' + openTagsRegExp.join('|') + ')', 'g'),
|
|
456
|
+
transformersByTag
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
462
|
+
*
|
|
463
|
+
* This source code is licensed under the MIT license found in the
|
|
464
|
+
* LICENSE file in the root directory of this source tree.
|
|
465
|
+
*
|
|
466
|
+
*/
|
|
467
|
+
function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers) {
|
|
468
|
+
const grandParentNode = parentNode.getParent();
|
|
469
|
+
if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const textContent = anchorNode.getTextContent();
|
|
473
|
+
|
|
474
|
+
// Checking for anchorOffset position to prevent any checks for cases when caret is too far
|
|
475
|
+
// from a line start to be a part of block-level markdown trigger.
|
|
476
|
+
//
|
|
477
|
+
// TODO:
|
|
478
|
+
// Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20)
|
|
479
|
+
// since otherwise it won't be a markdown shortcut, but tables are exception
|
|
480
|
+
if (textContent[anchorOffset - 1] !== ' ') {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
for (const {
|
|
484
|
+
regExp,
|
|
485
|
+
replace
|
|
486
|
+
} of elementTransformers) {
|
|
487
|
+
const match = textContent.match(regExp);
|
|
488
|
+
if (match && match[0].length === anchorOffset) {
|
|
489
|
+
const nextSiblings = anchorNode.getNextSiblings();
|
|
490
|
+
const [leadingNode, remainderNode] = anchorNode.splitText(anchorOffset);
|
|
491
|
+
leadingNode.remove();
|
|
492
|
+
const siblings = remainderNode ? [remainderNode, ...nextSiblings] : nextSiblings;
|
|
493
|
+
replace(parentNode, siblings, match, false);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
function runTextMatchTransformers(anchorNode, anchorOffset, transformersByTrigger) {
|
|
500
|
+
let textContent = anchorNode.getTextContent();
|
|
501
|
+
const lastChar = textContent[anchorOffset - 1];
|
|
502
|
+
const transformers = transformersByTrigger[lastChar];
|
|
503
|
+
if (transformers == null) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// If typing in the middle of content, remove the tail to do
|
|
508
|
+
// reg exp match up to a string end (caret position)
|
|
509
|
+
if (anchorOffset < textContent.length) {
|
|
510
|
+
textContent = textContent.slice(0, anchorOffset);
|
|
511
|
+
}
|
|
512
|
+
for (const transformer of transformers) {
|
|
513
|
+
const match = textContent.match(transformer.regExp);
|
|
514
|
+
if (match === null) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const startIndex = match.index || 0;
|
|
518
|
+
const endIndex = startIndex + match[0].length;
|
|
519
|
+
let replaceNode;
|
|
520
|
+
if (startIndex === 0) {
|
|
521
|
+
[replaceNode] = anchorNode.splitText(endIndex);
|
|
522
|
+
} else {
|
|
523
|
+
[, replaceNode] = anchorNode.splitText(startIndex, endIndex);
|
|
524
|
+
}
|
|
525
|
+
replaceNode.selectNext(0, 0);
|
|
526
|
+
transformer.replace(replaceNode, match);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
function runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformers) {
|
|
532
|
+
const textContent = anchorNode.getTextContent();
|
|
533
|
+
const closeTagEndIndex = anchorOffset - 1;
|
|
534
|
+
const closeChar = textContent[closeTagEndIndex];
|
|
535
|
+
// Quick check if we're possibly at the end of inline markdown style
|
|
536
|
+
const matchers = textFormatTransformers[closeChar];
|
|
537
|
+
if (!matchers) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
for (const matcher of matchers) {
|
|
541
|
+
const {
|
|
542
|
+
tag
|
|
543
|
+
} = matcher;
|
|
544
|
+
const tagLength = tag.length;
|
|
545
|
+
const closeTagStartIndex = closeTagEndIndex - tagLength + 1;
|
|
546
|
+
|
|
547
|
+
// If tag is not single char check if rest of it matches with text content
|
|
548
|
+
if (tagLength > 1) {
|
|
549
|
+
if (!isEqualSubString(textContent, closeTagStartIndex, tag, 0, tagLength)) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Space before closing tag cancels inline markdown
|
|
555
|
+
if (textContent[closeTagStartIndex - 1] === ' ') {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Some tags can not be used within words, hence should have newline/space/punctuation after it
|
|
560
|
+
const afterCloseTagChar = textContent[closeTagEndIndex + 1];
|
|
561
|
+
if (matcher.intraword === false && afterCloseTagChar && !PUNCTUATION_OR_SPACE.test(afterCloseTagChar)) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const closeNode = anchorNode;
|
|
565
|
+
let openNode = closeNode;
|
|
566
|
+
let openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag);
|
|
567
|
+
|
|
568
|
+
// Go through text node siblings and search for opening tag
|
|
569
|
+
// if haven't found it within the same text node as closing tag
|
|
570
|
+
let sibling = openNode;
|
|
571
|
+
while (openTagStartIndex < 0 && (sibling = sibling.getPreviousSibling())) {
|
|
572
|
+
if ($isLineBreakNode(sibling)) {
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
if ($isTextNode(sibling)) {
|
|
576
|
+
const siblingTextContent = sibling.getTextContent();
|
|
577
|
+
openNode = sibling;
|
|
578
|
+
openTagStartIndex = getOpenTagStartIndex(siblingTextContent, siblingTextContent.length, tag);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Opening tag is not found
|
|
583
|
+
if (openTagStartIndex < 0) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// No content between opening and closing tag
|
|
588
|
+
if (openNode === closeNode && openTagStartIndex + tagLength === closeTagStartIndex) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Checking longer tags for repeating chars (e.g. *** vs **)
|
|
593
|
+
const prevOpenNodeText = openNode.getTextContent();
|
|
594
|
+
if (openTagStartIndex > 0 && prevOpenNodeText[openTagStartIndex - 1] === closeChar) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Some tags can not be used within words, hence should have newline/space/punctuation before it
|
|
599
|
+
const beforeOpenTagChar = prevOpenNodeText[openTagStartIndex - 1];
|
|
600
|
+
if (matcher.intraword === false && beforeOpenTagChar && !PUNCTUATION_OR_SPACE.test(beforeOpenTagChar)) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Clean text from opening and closing tags (starting from closing tag
|
|
605
|
+
// to prevent any offset shifts if we start from opening one)
|
|
606
|
+
const prevCloseNodeText = closeNode.getTextContent();
|
|
607
|
+
const closeNodeText = prevCloseNodeText.slice(0, closeTagStartIndex) + prevCloseNodeText.slice(closeTagEndIndex + 1);
|
|
608
|
+
closeNode.setTextContent(closeNodeText);
|
|
609
|
+
const openNodeText = openNode === closeNode ? closeNodeText : prevOpenNodeText;
|
|
610
|
+
openNode.setTextContent(openNodeText.slice(0, openTagStartIndex) + openNodeText.slice(openTagStartIndex + tagLength));
|
|
611
|
+
const selection = $getSelection();
|
|
612
|
+
const nextSelection = $createRangeSelection();
|
|
613
|
+
$setSelection(nextSelection);
|
|
614
|
+
// Adjust offset based on deleted chars
|
|
615
|
+
const newOffset = closeTagEndIndex - tagLength * (openNode === closeNode ? 2 : 1) + 1;
|
|
616
|
+
nextSelection.anchor.set(openNode.__key, openTagStartIndex, 'text');
|
|
617
|
+
nextSelection.focus.set(closeNode.__key, newOffset, 'text');
|
|
618
|
+
|
|
619
|
+
// Apply formatting to selected text
|
|
620
|
+
for (const format of matcher.format) {
|
|
621
|
+
if (!nextSelection.hasFormat(format)) {
|
|
622
|
+
nextSelection.formatText(format);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Collapse selection up to the focus point
|
|
627
|
+
nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type);
|
|
628
|
+
|
|
629
|
+
// Remove formatting from collapsed selection
|
|
630
|
+
for (const format of matcher.format) {
|
|
631
|
+
if (nextSelection.hasFormat(format)) {
|
|
632
|
+
nextSelection.toggleFormat(format);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if ($isRangeSelection(selection)) {
|
|
636
|
+
nextSelection.format = selection.format;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
function getOpenTagStartIndex(string, maxIndex, tag) {
|
|
643
|
+
const tagLength = tag.length;
|
|
644
|
+
for (let i = maxIndex; i >= tagLength; i--) {
|
|
645
|
+
const startIndex = i - tagLength;
|
|
646
|
+
if (isEqualSubString(string, startIndex, tag, 0, tagLength) &&
|
|
647
|
+
// Space after opening tag cancels transformation
|
|
648
|
+
string[startIndex + tagLength] !== ' ') {
|
|
649
|
+
return startIndex;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return -1;
|
|
653
|
+
}
|
|
654
|
+
function isEqualSubString(stringA, aStart, stringB, bStart, length) {
|
|
655
|
+
for (let i = 0; i < length; i++) {
|
|
656
|
+
if (stringA[aStart + i] !== stringB[bStart + i]) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
663
|
+
const byType = transformersByType(transformers);
|
|
664
|
+
const textFormatTransformersIndex = indexBy(byType.textFormat, ({
|
|
665
|
+
tag
|
|
666
|
+
}) => tag[tag.length - 1]);
|
|
667
|
+
const textMatchTransformersIndex = indexBy(byType.textMatch, ({
|
|
668
|
+
trigger
|
|
669
|
+
}) => trigger);
|
|
670
|
+
for (const transformer of transformers) {
|
|
671
|
+
const type = transformer.type;
|
|
672
|
+
if (type === 'element' || type === 'text-match') {
|
|
673
|
+
const dependencies = transformer.dependencies;
|
|
674
|
+
for (const node of dependencies) {
|
|
675
|
+
if (!editor.hasNode(node)) {
|
|
676
|
+
{
|
|
677
|
+
throw Error(`MarkdownShortcuts: missing dependency ${node.getType()} for transformer. Ensure node dependency is included in editor initial config.`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const transform = (parentNode, anchorNode, anchorOffset) => {
|
|
684
|
+
if (runElementTransformers(parentNode, anchorNode, anchorOffset, byType.element)) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (runTextMatchTransformers(anchorNode, anchorOffset, textMatchTransformersIndex)) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformersIndex);
|
|
691
|
+
};
|
|
692
|
+
return editor.registerUpdateListener(({
|
|
693
|
+
tags,
|
|
694
|
+
dirtyLeaves,
|
|
695
|
+
editorState,
|
|
696
|
+
prevEditorState
|
|
697
|
+
}) => {
|
|
698
|
+
// Ignore updates from collaboration and undo/redo (as changes already calculated)
|
|
699
|
+
if (tags.has('collaboration') || tags.has('historic')) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// If editor is still composing (i.e. backticks) we must wait before the user confirms the key
|
|
704
|
+
if (editor.isComposing()) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const selection = editorState.read($getSelection);
|
|
708
|
+
const prevSelection = prevEditorState.read($getSelection);
|
|
709
|
+
if (!$isRangeSelection(prevSelection) || !$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const anchorKey = selection.anchor.key;
|
|
713
|
+
const anchorOffset = selection.anchor.offset;
|
|
714
|
+
const anchorNode = editorState._nodeMap.get(anchorKey);
|
|
715
|
+
if (!$isTextNode(anchorNode) || !dirtyLeaves.has(anchorKey) || anchorOffset !== 1 && anchorOffset > prevSelection.anchor.offset + 1) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
editor.update(() => {
|
|
719
|
+
// Markdown is not available inside code
|
|
720
|
+
if (anchorNode.hasFormat('code')) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const parentNode = anchorNode.getParent();
|
|
724
|
+
if (parentNode === null || $isCodeNode(parentNode)) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
transform(parentNode, anchorNode, selection.anchor.offset);
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
734
|
+
*
|
|
735
|
+
* This source code is licensed under the MIT license found in the
|
|
736
|
+
* LICENSE file in the root directory of this source tree.
|
|
737
|
+
*
|
|
738
|
+
*/
|
|
739
|
+
const createBlockNode = createNode => {
|
|
740
|
+
return (parentNode, children, match) => {
|
|
741
|
+
const node = createNode(match);
|
|
742
|
+
node.append(...children);
|
|
743
|
+
parentNode.replace(node);
|
|
744
|
+
node.select(0, 0);
|
|
745
|
+
};
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
// Amount of spaces that define indentation level
|
|
749
|
+
// TODO: should be an option
|
|
750
|
+
const LIST_INDENT_SIZE = 4;
|
|
751
|
+
function getIndent(whitespaces) {
|
|
752
|
+
const tabs = whitespaces.match(/\t/g);
|
|
753
|
+
const spaces = whitespaces.match(/ /g);
|
|
754
|
+
let indent = 0;
|
|
755
|
+
if (tabs) {
|
|
756
|
+
indent += tabs.length;
|
|
757
|
+
}
|
|
758
|
+
if (spaces) {
|
|
759
|
+
indent += Math.floor(spaces.length / LIST_INDENT_SIZE);
|
|
760
|
+
}
|
|
761
|
+
return indent;
|
|
762
|
+
}
|
|
763
|
+
const listReplace = listType => {
|
|
764
|
+
return (parentNode, children, match) => {
|
|
765
|
+
const previousNode = parentNode.getPreviousSibling();
|
|
766
|
+
const nextNode = parentNode.getNextSibling();
|
|
767
|
+
const listItem = $createListItemNode(listType === 'check' ? match[3] === 'x' : undefined);
|
|
768
|
+
if ($isListNode(nextNode) && nextNode.getListType() === listType) {
|
|
769
|
+
const firstChild = nextNode.getFirstChild();
|
|
770
|
+
if (firstChild !== null) {
|
|
771
|
+
firstChild.insertBefore(listItem);
|
|
772
|
+
} else {
|
|
773
|
+
// should never happen, but let's handle gracefully, just in case.
|
|
774
|
+
nextNode.append(listItem);
|
|
775
|
+
}
|
|
776
|
+
parentNode.remove();
|
|
777
|
+
} else if ($isListNode(previousNode) && previousNode.getListType() === listType) {
|
|
778
|
+
previousNode.append(listItem);
|
|
779
|
+
parentNode.remove();
|
|
780
|
+
} else {
|
|
781
|
+
const list = $createListNode(listType, listType === 'number' ? Number(match[2]) : undefined);
|
|
782
|
+
list.append(listItem);
|
|
783
|
+
parentNode.replace(list);
|
|
784
|
+
}
|
|
785
|
+
listItem.append(...children);
|
|
786
|
+
listItem.select(0, 0);
|
|
787
|
+
const indent = getIndent(match[1]);
|
|
788
|
+
if (indent) {
|
|
789
|
+
listItem.setIndent(indent);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
};
|
|
793
|
+
const listExport = (listNode, exportChildren, depth) => {
|
|
794
|
+
const output = [];
|
|
795
|
+
const children = listNode.getChildren();
|
|
796
|
+
let index = 0;
|
|
797
|
+
for (const listItemNode of children) {
|
|
798
|
+
if ($isListItemNode(listItemNode)) {
|
|
799
|
+
if (listItemNode.getChildrenSize() === 1) {
|
|
800
|
+
const firstChild = listItemNode.getFirstChild();
|
|
801
|
+
if ($isListNode(firstChild)) {
|
|
802
|
+
output.push(listExport(firstChild, exportChildren, depth + 1));
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const indent = ' '.repeat(depth * LIST_INDENT_SIZE);
|
|
807
|
+
const listType = listNode.getListType();
|
|
808
|
+
const prefix = listType === 'number' ? `${listNode.getStart() + index}. ` : listType === 'check' ? `- [${listItemNode.getChecked() ? 'x' : ' '}] ` : '- ';
|
|
809
|
+
output.push(indent + prefix + exportChildren(listItemNode));
|
|
810
|
+
index++;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return output.join('\n');
|
|
814
|
+
};
|
|
815
|
+
const HEADING = {
|
|
816
|
+
dependencies: [HeadingNode],
|
|
817
|
+
export: (node, exportChildren) => {
|
|
818
|
+
if (!$isHeadingNode(node)) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
const level = Number(node.getTag().slice(1));
|
|
822
|
+
return '#'.repeat(level) + ' ' + exportChildren(node);
|
|
823
|
+
},
|
|
824
|
+
regExp: /^(#{1,6})\s/,
|
|
825
|
+
replace: createBlockNode(match => {
|
|
826
|
+
const tag = 'h' + match[1].length;
|
|
827
|
+
return $createHeadingNode(tag);
|
|
828
|
+
}),
|
|
829
|
+
type: 'element'
|
|
830
|
+
};
|
|
831
|
+
const QUOTE = {
|
|
832
|
+
dependencies: [QuoteNode],
|
|
833
|
+
export: (node, exportChildren) => {
|
|
834
|
+
if (!$isQuoteNode(node)) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
const lines = exportChildren(node).split('\n');
|
|
838
|
+
const output = [];
|
|
839
|
+
for (const line of lines) {
|
|
840
|
+
output.push('> ' + line);
|
|
841
|
+
}
|
|
842
|
+
return output.join('\n');
|
|
843
|
+
},
|
|
844
|
+
regExp: /^>\s/,
|
|
845
|
+
replace: (parentNode, children, _match, isImport) => {
|
|
846
|
+
if (isImport) {
|
|
847
|
+
const previousNode = parentNode.getPreviousSibling();
|
|
848
|
+
if ($isQuoteNode(previousNode)) {
|
|
849
|
+
previousNode.splice(previousNode.getChildrenSize(), 0, [$createLineBreakNode(), ...children]);
|
|
850
|
+
previousNode.select(0, 0);
|
|
851
|
+
parentNode.remove();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
const node = $createQuoteNode();
|
|
856
|
+
node.append(...children);
|
|
857
|
+
parentNode.replace(node);
|
|
858
|
+
node.select(0, 0);
|
|
859
|
+
},
|
|
860
|
+
type: 'element'
|
|
861
|
+
};
|
|
862
|
+
const CODE = {
|
|
863
|
+
dependencies: [CodeNode],
|
|
864
|
+
export: node => {
|
|
865
|
+
if (!$isCodeNode(node)) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
const textContent = node.getTextContent();
|
|
869
|
+
return '```' + (node.getLanguage() || '') + (textContent ? '\n' + textContent : '') + '\n' + '```';
|
|
870
|
+
},
|
|
871
|
+
regExp: /^```(\w{1,10})?\s/,
|
|
872
|
+
replace: createBlockNode(match => {
|
|
873
|
+
return $createCodeNode(match ? match[1] : undefined);
|
|
874
|
+
}),
|
|
875
|
+
type: 'element'
|
|
876
|
+
};
|
|
877
|
+
const UNORDERED_LIST = {
|
|
878
|
+
dependencies: [ListNode, ListItemNode],
|
|
879
|
+
export: (node, exportChildren) => {
|
|
880
|
+
return $isListNode(node) ? listExport(node, exportChildren, 0) : null;
|
|
881
|
+
},
|
|
882
|
+
regExp: /^(\s*)[-*+]\s/,
|
|
883
|
+
replace: listReplace('bullet'),
|
|
884
|
+
type: 'element'
|
|
885
|
+
};
|
|
886
|
+
const CHECK_LIST = {
|
|
887
|
+
dependencies: [ListNode, ListItemNode],
|
|
888
|
+
export: (node, exportChildren) => {
|
|
889
|
+
return $isListNode(node) ? listExport(node, exportChildren, 0) : null;
|
|
890
|
+
},
|
|
891
|
+
regExp: /^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i,
|
|
892
|
+
replace: listReplace('check'),
|
|
893
|
+
type: 'element'
|
|
894
|
+
};
|
|
895
|
+
const ORDERED_LIST = {
|
|
896
|
+
dependencies: [ListNode, ListItemNode],
|
|
897
|
+
export: (node, exportChildren) => {
|
|
898
|
+
return $isListNode(node) ? listExport(node, exportChildren, 0) : null;
|
|
899
|
+
},
|
|
900
|
+
regExp: /^(\s*)(\d{1,})\.\s/,
|
|
901
|
+
replace: listReplace('number'),
|
|
902
|
+
type: 'element'
|
|
903
|
+
};
|
|
904
|
+
const INLINE_CODE = {
|
|
905
|
+
format: ['code'],
|
|
906
|
+
tag: '`',
|
|
907
|
+
type: 'text-format'
|
|
908
|
+
};
|
|
909
|
+
const HIGHLIGHT = {
|
|
910
|
+
format: ['highlight'],
|
|
911
|
+
tag: '==',
|
|
912
|
+
type: 'text-format'
|
|
913
|
+
};
|
|
914
|
+
const BOLD_ITALIC_STAR = {
|
|
915
|
+
format: ['bold', 'italic'],
|
|
916
|
+
tag: '***',
|
|
917
|
+
type: 'text-format'
|
|
918
|
+
};
|
|
919
|
+
const BOLD_ITALIC_UNDERSCORE = {
|
|
920
|
+
format: ['bold', 'italic'],
|
|
921
|
+
intraword: false,
|
|
922
|
+
tag: '___',
|
|
923
|
+
type: 'text-format'
|
|
924
|
+
};
|
|
925
|
+
const BOLD_STAR = {
|
|
926
|
+
format: ['bold'],
|
|
927
|
+
tag: '**',
|
|
928
|
+
type: 'text-format'
|
|
929
|
+
};
|
|
930
|
+
const BOLD_UNDERSCORE = {
|
|
931
|
+
format: ['bold'],
|
|
932
|
+
intraword: false,
|
|
933
|
+
tag: '__',
|
|
934
|
+
type: 'text-format'
|
|
935
|
+
};
|
|
936
|
+
const STRIKETHROUGH = {
|
|
937
|
+
format: ['strikethrough'],
|
|
938
|
+
tag: '~~',
|
|
939
|
+
type: 'text-format'
|
|
940
|
+
};
|
|
941
|
+
const ITALIC_STAR = {
|
|
942
|
+
format: ['italic'],
|
|
943
|
+
tag: '*',
|
|
944
|
+
type: 'text-format'
|
|
945
|
+
};
|
|
946
|
+
const ITALIC_UNDERSCORE = {
|
|
947
|
+
format: ['italic'],
|
|
948
|
+
intraword: false,
|
|
949
|
+
tag: '_',
|
|
950
|
+
type: 'text-format'
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
// Order of text transformers matters:
|
|
954
|
+
//
|
|
955
|
+
// - code should go first as it prevents any transformations inside
|
|
956
|
+
// - then longer tags match (e.g. ** or __ should go before * or _)
|
|
957
|
+
const LINK = {
|
|
958
|
+
dependencies: [LinkNode],
|
|
959
|
+
export: (node, exportChildren, exportFormat) => {
|
|
960
|
+
if (!$isLinkNode(node)) {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
const title = node.getTitle();
|
|
964
|
+
const linkContent = title ? `[${node.getTextContent()}](${node.getURL()} "${title}")` : `[${node.getTextContent()}](${node.getURL()})`;
|
|
965
|
+
const firstChild = node.getFirstChild();
|
|
966
|
+
// Add text styles only if link has single text node inside. If it's more
|
|
967
|
+
// then one we ignore it as markdown does not support nested styles for links
|
|
968
|
+
if (node.getChildrenSize() === 1 && $isTextNode(firstChild)) {
|
|
969
|
+
return exportFormat(firstChild, linkContent);
|
|
970
|
+
} else {
|
|
971
|
+
return linkContent;
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
importRegExp: /(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))/,
|
|
975
|
+
regExp: /(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))$/,
|
|
976
|
+
replace: (textNode, match) => {
|
|
977
|
+
const [, linkText, linkUrl, linkTitle] = match;
|
|
978
|
+
const linkNode = $createLinkNode(linkUrl, {
|
|
979
|
+
title: linkTitle
|
|
980
|
+
});
|
|
981
|
+
const linkTextNode = $createTextNode(linkText);
|
|
982
|
+
linkTextNode.setFormat(textNode.getFormat());
|
|
983
|
+
linkNode.append(linkTextNode);
|
|
984
|
+
textNode.replace(linkNode);
|
|
985
|
+
},
|
|
986
|
+
trigger: ')',
|
|
987
|
+
type: 'text-match'
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
/** @module @lexical/markdown */
|
|
991
|
+
const ELEMENT_TRANSFORMERS = [HEADING, QUOTE, CODE, UNORDERED_LIST, ORDERED_LIST];
|
|
992
|
+
|
|
993
|
+
// Order of text format transformers matters:
|
|
994
|
+
//
|
|
995
|
+
// - code should go first as it prevents any transformations inside
|
|
996
|
+
// - then longer tags match (e.g. ** or __ should go before * or _)
|
|
997
|
+
const TEXT_FORMAT_TRANSFORMERS = [INLINE_CODE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, HIGHLIGHT, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH];
|
|
998
|
+
const TEXT_MATCH_TRANSFORMERS = [LINK];
|
|
999
|
+
const TRANSFORMERS = [...ELEMENT_TRANSFORMERS, ...TEXT_FORMAT_TRANSFORMERS, ...TEXT_MATCH_TRANSFORMERS];
|
|
1000
|
+
function $convertFromMarkdownString(markdown, transformers = TRANSFORMERS, node) {
|
|
1001
|
+
const importMarkdown = createMarkdownImport(transformers);
|
|
1002
|
+
return importMarkdown(markdown, node);
|
|
1003
|
+
}
|
|
1004
|
+
function $convertToMarkdownString(transformers = TRANSFORMERS, node) {
|
|
1005
|
+
const exportMarkdown = createMarkdownExport(transformers);
|
|
1006
|
+
return exportMarkdown(node);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
export { $convertFromMarkdownString, $convertToMarkdownString, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, CHECK_LIST, CODE, ELEMENT_TRANSFORMERS, HEADING, HIGHLIGHT, INLINE_CODE, ITALIC_STAR, ITALIC_UNDERSCORE, LINK, ORDERED_LIST, QUOTE, STRIKETHROUGH, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, TRANSFORMERS, UNORDERED_LIST, registerMarkdownShortcuts };
|
package/LexicalMarkdown.dev.js
CHANGED
|
@@ -195,13 +195,14 @@ CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
|
195
195
|
CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
|
|
196
196
|
const IS_SAFARI = CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
|
|
197
197
|
const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
198
|
-
CAN_USE_DOM && /Android/.test(navigator.userAgent);
|
|
198
|
+
const IS_ANDROID = CAN_USE_DOM && /Android/.test(navigator.userAgent);
|
|
199
199
|
|
|
200
200
|
// Keep these in case we need to use them in the future.
|
|
201
201
|
// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
|
|
202
202
|
const IS_CHROME = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);
|
|
203
203
|
// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
|
|
204
204
|
|
|
205
|
+
CAN_USE_DOM && IS_ANDROID && IS_CHROME;
|
|
205
206
|
const IS_APPLE_WEBKIT = CAN_USE_DOM && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && !IS_CHROME;
|
|
206
207
|
|
|
207
208
|
/**
|
|
@@ -236,10 +237,10 @@ function createMarkdownImport(transformers) {
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
// Removing empty paragraphs as md does not really
|
|
239
|
-
// allow empty lines and uses them as
|
|
240
|
+
// allow empty lines and uses them as delimiter
|
|
240
241
|
const children = root.getChildren();
|
|
241
242
|
for (const child of children) {
|
|
242
|
-
if (isEmptyParagraph(child)) {
|
|
243
|
+
if (isEmptyParagraph(child) && root.getChildrenSize() > 1) {
|
|
243
244
|
child.remove();
|
|
244
245
|
}
|
|
245
246
|
}
|
|
@@ -696,8 +697,8 @@ function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
|
696
697
|
editorState,
|
|
697
698
|
prevEditorState
|
|
698
699
|
}) => {
|
|
699
|
-
// Ignore updates from undo/redo (as changes already calculated)
|
|
700
|
-
if (tags.has('historic')) {
|
|
700
|
+
// Ignore updates from collaboration and undo/redo (as changes already calculated)
|
|
701
|
+
if (tags.has('collaboration') || tags.has('historic')) {
|
|
701
702
|
return;
|
|
702
703
|
}
|
|
703
704
|
|
|
@@ -749,6 +750,18 @@ const createBlockNode = createNode => {
|
|
|
749
750
|
// Amount of spaces that define indentation level
|
|
750
751
|
// TODO: should be an option
|
|
751
752
|
const LIST_INDENT_SIZE = 4;
|
|
753
|
+
function getIndent(whitespaces) {
|
|
754
|
+
const tabs = whitespaces.match(/\t/g);
|
|
755
|
+
const spaces = whitespaces.match(/ /g);
|
|
756
|
+
let indent = 0;
|
|
757
|
+
if (tabs) {
|
|
758
|
+
indent += tabs.length;
|
|
759
|
+
}
|
|
760
|
+
if (spaces) {
|
|
761
|
+
indent += Math.floor(spaces.length / LIST_INDENT_SIZE);
|
|
762
|
+
}
|
|
763
|
+
return indent;
|
|
764
|
+
}
|
|
752
765
|
const listReplace = listType => {
|
|
753
766
|
return (parentNode, children, match) => {
|
|
754
767
|
const previousNode = parentNode.getPreviousSibling();
|
|
@@ -773,7 +786,7 @@ const listReplace = listType => {
|
|
|
773
786
|
}
|
|
774
787
|
listItem.append(...children);
|
|
775
788
|
listItem.select(0, 0);
|
|
776
|
-
const indent =
|
|
789
|
+
const indent = getIndent(match[1]);
|
|
777
790
|
if (indent) {
|
|
778
791
|
listItem.setIndent(indent);
|
|
779
792
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import * as modDev from './LexicalMarkdown.dev.esm.js';
|
|
8
|
+
import * as modProd from './LexicalMarkdown.prod.esm.js';
|
|
9
|
+
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
|
10
|
+
export const $convertFromMarkdownString = mod.$convertFromMarkdownString;
|
|
11
|
+
export const $convertToMarkdownString = mod.$convertToMarkdownString;
|
|
12
|
+
export const BOLD_ITALIC_STAR = mod.BOLD_ITALIC_STAR;
|
|
13
|
+
export const BOLD_ITALIC_UNDERSCORE = mod.BOLD_ITALIC_UNDERSCORE;
|
|
14
|
+
export const BOLD_STAR = mod.BOLD_STAR;
|
|
15
|
+
export const BOLD_UNDERSCORE = mod.BOLD_UNDERSCORE;
|
|
16
|
+
export const CHECK_LIST = mod.CHECK_LIST;
|
|
17
|
+
export const CODE = mod.CODE;
|
|
18
|
+
export const ELEMENT_TRANSFORMERS = mod.ELEMENT_TRANSFORMERS;
|
|
19
|
+
export const HEADING = mod.HEADING;
|
|
20
|
+
export const HIGHLIGHT = mod.HIGHLIGHT;
|
|
21
|
+
export const INLINE_CODE = mod.INLINE_CODE;
|
|
22
|
+
export const ITALIC_STAR = mod.ITALIC_STAR;
|
|
23
|
+
export const ITALIC_UNDERSCORE = mod.ITALIC_UNDERSCORE;
|
|
24
|
+
export const LINK = mod.LINK;
|
|
25
|
+
export const ORDERED_LIST = mod.ORDERED_LIST;
|
|
26
|
+
export const QUOTE = mod.QUOTE;
|
|
27
|
+
export const STRIKETHROUGH = mod.STRIKETHROUGH;
|
|
28
|
+
export const TEXT_FORMAT_TRANSFORMERS = mod.TEXT_FORMAT_TRANSFORMERS;
|
|
29
|
+
export const TEXT_MATCH_TRANSFORMERS = mod.TEXT_MATCH_TRANSFORMERS;
|
|
30
|
+
export const TRANSFORMERS = mod.TRANSFORMERS;
|
|
31
|
+
export const UNORDERED_LIST = mod.UNORDERED_LIST;
|
|
32
|
+
export const registerMarkdownShortcuts = mod.registerMarkdownShortcuts;
|
package/LexicalMarkdown.js
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
'use strict'
|
|
8
|
-
const LexicalMarkdown = process.env.NODE_ENV === 'development' ? require('./LexicalMarkdown.dev.js') : require('./LexicalMarkdown.prod.js')
|
|
8
|
+
const LexicalMarkdown = process.env.NODE_ENV === 'development' ? require('./LexicalMarkdown.dev.js') : require('./LexicalMarkdown.prod.js');
|
|
9
9
|
module.exports = LexicalMarkdown;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import{$getRoot as t,$isElementNode as e,$isDecoratorNode as n,$isLineBreakNode as o,$isTextNode as r,$getSelection as i,$isParagraphNode as s,$createTextNode as c,$createParagraphNode as l,$createLineBreakNode as a,$isRangeSelection as f,$isRootOrShadowRoot as u,$createRangeSelection as g,$setSelection as p}from"lexical";import{$createCodeNode as d,$isCodeNode as m,CodeNode as h}from"@lexical/code";import{$isListNode as x,$isListItemNode as T,ListNode as C,ListItemNode as y,$createListItemNode as v,$createListNode as w}from"@lexical/list";import{$isQuoteNode as S,HeadingNode as b,$isHeadingNode as E,QuoteNode as $,$createQuoteNode as F,$createHeadingNode as P}from"@lexical/rich-text";import{$findMatchingParent as k}from"@lexical/utils";import{LinkNode as M,$isLinkNode as R,$createLinkNode as _}from"@lexical/link";function L(t,e){const n={};for(const o of t){const t=e(o);n[t]?n[t].push(o):n[t]=[o]}return n}function A(t){const e=L(t,(t=>t.type));return{element:e.element||[],textFormat:e["text-format"]||[],textMatch:e["text-match"]||[]}}const N=/[!-/:-@[-`{-~\s]/;function z(t,o,r,i){for(const e of o){const n=e.export(t,(t=>j(t,r,i)));if(null!=n)return n}return e(t)?j(t,r,i):n(t)?t.getTextContent():null}function j(t,i,s){const c=[],l=t.getChildren();t:for(const t of l){for(const e of s){const n=e.export(t,(t=>j(t,i,s)),((t,e)=>B(t,e,i)));if(null!=n){c.push(n);continue t}}o(t)?c.push("\n"):r(t)?c.push(B(t,t.getTextContent(),i)):e(t)?c.push(j(t,i,s)):n(t)&&c.push(t.getTextContent())}return c.join("")}function B(t,e,n){const o=e.trim();let r=o;const i=new Set;for(const e of n){const n=e.format[0],o=e.tag;if(D(t,n)&&!i.has(n)){i.add(n);D(I(t,!0),n)||(r=o+r);D(I(t,!1),n)||(r+=o)}}return e.replace(o,(()=>r))}function I(t,n){let o=n?t.getPreviousSibling():t.getNextSibling();if(!o){const e=t.getParentOrThrow();e.isInline()&&(o=n?e.getPreviousSibling():e.getNextSibling())}for(;o;){if(e(o)){if(!o.isInline())break;const t=n?o.getLastDescendant():o.getFirstDescendant();if(r(t))return t;o=n?o.getPreviousSibling():o.getNextSibling()}if(r(o))return o;if(!e(o))return null}return null}function D(t,e){return r(t)&&t.hasFormat(e)}const U="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,K=U&&"documentMode"in document?document.documentMode:null;U&&/Mac|iPod|iPhone|iPad/.test(navigator.platform),U&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent),U&&"InputEvent"in window&&!K&&new window.InputEvent("input");const O=U&&/Version\/[\d.]+.*Safari/.test(navigator.userAgent),V=U&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,W=(U&&/Android/.test(navigator.userAgent),U&&/^(?=.*Chrome).*/i.test(navigator.userAgent)),q=U&&/AppleWebKit\/[\d.]+/.test(navigator.userAgent)&&!W,G=/^\s{0,3}$/,H=/^```(\w{1,10})?\s?$/;function J(e){const n=A(e),o=function(t){const e={},n={},o=[],r="(?<![\\\\])";for(const r of t){const{tag:t}=r;e[t]=r;const i=t.replace(/(\*|\^|\+)/g,"\\$1");o.push(i),n[t]=O||V||q?new RegExp(`(${i})(?![${i}\\s])(.*?[^${i}\\s])${i}(?!${i})`):new RegExp(`(?<![\\\\${i}])(${i})((\\\\${i})?.*?[^${i}\\s](\\\\${i})?)((?<!\\\\)|(?<=\\\\\\\\))(${i})(?![\\\\${i}])`)}return{fullMatchRegExpByTag:n,openTagsRegExp:new RegExp((O||V||q?"":`${r}`)+"("+o.join("|")+")","g"),transformersByTag:e}}(n.textFormat);return(e,r)=>{const s=e.split("\n"),c=s.length,l=r||t();l.clear();for(let t=0;t<c;t++){const e=s[t],[r,i]=Y(s,t,l);null==r?X(e,l,n.element,o,n.textMatch):t=i}const a=l.getChildren();for(const t of a)Q(t)&&l.getChildrenSize()>1&&t.remove();null!==i()&&l.selectEnd()}}function Q(t){if(!s(t))return!1;const e=t.getFirstChild();return null==e||1===t.getChildrenSize()&&r(e)&&G.test(e.getTextContent())}function X(t,e,n,o,r){const i=t.trim(),f=c(i),u=l();u.append(f),e.append(u);for(const{regExp:e,replace:o}of n){const n=t.match(e);if(n){f.setTextContent(t.slice(n[0].length)),o(u,[f],n,!0);break}}if(Z(f,o,r),u.isAttached()&&i.length>0){const t=u.getPreviousSibling();if(s(t)||S(t)||x(t)){let e=t;if(x(t)){const n=t.getLastDescendant();e=null==n?null:k(n,T)}null!=e&&e.getTextContentSize()>0&&(e.splice(e.getChildrenSize(),0,[a(),...u.getChildren()]),u.remove())}}}function Y(t,e,n){const o=t[e].match(H);if(o){let r=e;const i=t.length;for(;++r<i;){if(t[r].match(H)){const i=d(o[1]),s=c(t.slice(e+1,r).join("\n"));return i.append(s),n.append(i),[i,r]}}}return[null,e]}function Z(t,e,n){const o=t.getTextContent(),r=function(t,e){const n=t.match(e.openTagsRegExp);if(null==n)return null;for(const o of n){const n=o.replace(/^\s/,""),r=e.fullMatchRegExpByTag[n];if(null==r)continue;const i=t.match(r),s=e.transformersByTag[n];if(null!=i&&null!=s){if(!1!==s.intraword)return i;const{index:e=0}=i,n=t[e-1],o=t[e+i[0].length];if((!n||N.test(n))&&(!o||N.test(o)))return i}}return null}(o,e);if(!r)return void tt(t,n);let i,s,c;if(r[0]===o)i=t;else{const e=r.index||0,n=e+r[0].length;0===e?[i,s]=t.splitText(n):[c,i,s]=t.splitText(e,n)}i.setTextContent(r[2]);const l=e.transformersByTag[r[1]];if(l)for(const t of l.format)i.hasFormat(t)||i.toggleFormat(t);i.hasFormat("code")||Z(i,e,n),c&&Z(c,e,n),s&&Z(s,e,n)}function tt(t,e){let n=t;t:for(;n;){for(const t of e){const o=n.getTextContent().match(t.importRegExp);if(!o)continue;const r=o.index||0,i=r+o[0].length;let s,c;0===r?[s,n]=n.splitText(i):[,s,c]=n.splitText(r,i),c&&tt(c,e),t.replace(s,o);continue t}break}}function et(t,e,n){const o=n.length;for(let r=e;r>=o;r--){const e=r-o;if(nt(t,e,n,0,o)&&" "!==t[e+o])return e}return-1}function nt(t,e,n,o,r){for(let i=0;i<r;i++)if(t[e+i]!==n[o+i])return!1;return!0}function ot(t,e=$t){const n=A(e),s=L(n.textFormat,(({tag:t})=>t[t.length-1])),c=L(n.textMatch,(({trigger:t})=>t));for(const n of e){const e=n.type;if("element"===e||"text-match"===e){const e=n.dependencies;for(const n of e)if(!t.hasNode(n))throw Error(`MarkdownShortcuts: missing dependency ${n.getType()} for transformer. Ensure node dependency is included in editor initial config.`)}}const l=(t,e,l)=>{(function(t,e,n,o){const r=t.getParent();if(!u(r)||t.getFirstChild()!==e)return!1;const i=e.getTextContent();if(" "!==i[n-1])return!1;for(const{regExp:r,replace:s}of o){const o=i.match(r);if(o&&o[0].length===n){const r=e.getNextSiblings(),[i,c]=e.splitText(n);return i.remove(),s(t,c?[c,...r]:r,o,!1),!0}}return!1})(t,e,l,n.element)||function(t,e,n){let o=t.getTextContent();const r=n[o[e-1]];if(null==r)return!1;e<o.length&&(o=o.slice(0,e));for(const e of r){const n=o.match(e.regExp);if(null===n)continue;const r=n.index||0,i=r+n[0].length;let s;return 0===r?[s]=t.splitText(i):[,s]=t.splitText(r,i),s.selectNext(0,0),e.replace(s,n),!0}return!1}(e,l,c)||function(t,e,n){const s=t.getTextContent(),c=e-1,l=s[c],a=n[l];if(!a)return!1;for(const e of a){const{tag:n}=e,a=n.length,u=c-a+1;if(a>1&&!nt(s,u,n,0,a))continue;if(" "===s[u-1])continue;const d=s[c+1];if(!1===e.intraword&&d&&!N.test(d))continue;const m=t;let h=m,x=et(s,u,n),T=h;for(;x<0&&(T=T.getPreviousSibling())&&!o(T);)if(r(T)){const t=T.getTextContent();h=T,x=et(t,t.length,n)}if(x<0)continue;if(h===m&&x+a===u)continue;const C=h.getTextContent();if(x>0&&C[x-1]===l)continue;const y=C[x-1];if(!1===e.intraword&&y&&!N.test(y))continue;const v=m.getTextContent(),w=v.slice(0,u)+v.slice(c+1);m.setTextContent(w);const S=h===m?w:C;h.setTextContent(S.slice(0,x)+S.slice(x+a));const b=i(),E=g();p(E);const $=c-a*(h===m?2:1)+1;E.anchor.set(h.__key,x,"text"),E.focus.set(m.__key,$,"text");for(const t of e.format)E.hasFormat(t)||E.formatText(t);E.anchor.set(E.focus.key,E.focus.offset,E.focus.type);for(const t of e.format)E.hasFormat(t)&&E.toggleFormat(t);return f(b)&&(E.format=b.format),!0}}(e,l,s)};return t.registerUpdateListener((({tags:e,dirtyLeaves:n,editorState:o,prevEditorState:s})=>{if(e.has("collaboration")||e.has("historic"))return;if(t.isComposing())return;const c=o.read(i),a=s.read(i);if(!f(a)||!f(c)||!c.isCollapsed())return;const u=c.anchor.key,g=c.anchor.offset,p=o._nodeMap.get(u);!r(p)||!n.has(u)||1!==g&&g>a.anchor.offset+1||t.update((()=>{if(p.hasFormat("code"))return;const t=p.getParent();null===t||m(t)||l(t,p,c.anchor.offset)}))}))}const rt=t=>(e,n,o)=>{const r=t(o);r.append(...n),e.replace(r),r.select(0,0)};const it=t=>(e,n,o)=>{const r=e.getPreviousSibling(),i=e.getNextSibling(),s=v("check"===t?"x"===o[3]:void 0);if(x(i)&&i.getListType()===t){const t=i.getFirstChild();null!==t?t.insertBefore(s):i.append(s),e.remove()}else if(x(r)&&r.getListType()===t)r.append(s),e.remove();else{const n=w(t,"number"===t?Number(o[2]):void 0);n.append(s),e.replace(n)}s.append(...n),s.select(0,0);const c=function(t){const e=t.match(/\t/g),n=t.match(/ /g);let o=0;return e&&(o+=e.length),n&&(o+=Math.floor(n.length/4)),o}(o[1]);c&&s.setIndent(c)},st=(t,e,n)=>{const o=[],r=t.getChildren();let i=0;for(const s of r)if(T(s)){if(1===s.getChildrenSize()){const t=s.getFirstChild();if(x(t)){o.push(st(t,e,n+1));continue}}const r=" ".repeat(4*n),c=t.getListType(),l="number"===c?`${t.getStart()+i}. `:"check"===c?`- [${s.getChecked()?"x":" "}] `:"- ";o.push(r+l+e(s)),i++}return o.join("\n")},ct={dependencies:[b],export:(t,e)=>{if(!E(t))return null;const n=Number(t.getTag().slice(1));return"#".repeat(n)+" "+e(t)},regExp:/^(#{1,6})\s/,replace:rt((t=>{const e="h"+t[1].length;return P(e)})),type:"element"},lt={dependencies:[$],export:(t,e)=>{if(!S(t))return null;const n=e(t).split("\n"),o=[];for(const t of n)o.push("> "+t);return o.join("\n")},regExp:/^>\s/,replace:(t,e,n,o)=>{if(o){const n=t.getPreviousSibling();if(S(n))return n.splice(n.getChildrenSize(),0,[a(),...e]),n.select(0,0),void t.remove()}const r=F();r.append(...e),t.replace(r),r.select(0,0)},type:"element"},at={dependencies:[h],export:t=>{if(!m(t))return null;const e=t.getTextContent();return"```"+(t.getLanguage()||"")+(e?"\n"+e:"")+"\n```"},regExp:/^```(\w{1,10})?\s/,replace:rt((t=>d(t?t[1]:void 0))),type:"element"},ft={dependencies:[C,y],export:(t,e)=>x(t)?st(t,e,0):null,regExp:/^(\s*)[-*+]\s/,replace:it("bullet"),type:"element"},ut={dependencies:[C,y],export:(t,e)=>x(t)?st(t,e,0):null,regExp:/^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i,replace:it("check"),type:"element"},gt={dependencies:[C,y],export:(t,e)=>x(t)?st(t,e,0):null,regExp:/^(\s*)(\d{1,})\.\s/,replace:it("number"),type:"element"},pt={format:["code"],tag:"`",type:"text-format"},dt={format:["highlight"],tag:"==",type:"text-format"},mt={format:["bold","italic"],tag:"***",type:"text-format"},ht={format:["bold","italic"],intraword:!1,tag:"___",type:"text-format"},xt={format:["bold"],tag:"**",type:"text-format"},Tt={format:["bold"],intraword:!1,tag:"__",type:"text-format"},Ct={format:["strikethrough"],tag:"~~",type:"text-format"},yt={format:["italic"],tag:"*",type:"text-format"},vt={format:["italic"],intraword:!1,tag:"_",type:"text-format"},wt={dependencies:[M],export:(t,e,n)=>{if(!R(t))return null;const o=t.getTitle(),i=o?`[${t.getTextContent()}](${t.getURL()} "${o}")`:`[${t.getTextContent()}](${t.getURL()})`,s=t.getFirstChild();return 1===t.getChildrenSize()&&r(s)?n(s,i):i},importRegExp:/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))/,regExp:/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))$/,replace:(t,e)=>{const[,n,o,r]=e,i=_(o,{title:r}),s=c(n);s.setFormat(t.getFormat()),i.append(s),t.replace(i)},trigger:")",type:"text-match"},St=[ct,lt,at,ft,gt],bt=[pt,mt,ht,xt,Tt,dt,yt,vt,Ct],Et=[wt],$t=[...St,...bt,...Et];function Ft(t,e=$t,n){return J(e)(t,n)}function Pt(e=$t,n){const o=function(e){const n=A(e),o=n.textFormat.filter((t=>1===t.format.length));return e=>{const r=[],i=(e||t()).getChildren();for(const t of i){const e=z(t,n.element,o,n.textMatch);null!=e&&r.push(e)}return r.join("\n\n")}}(e);return o(n)}export{Ft as $convertFromMarkdownString,Pt as $convertToMarkdownString,mt as BOLD_ITALIC_STAR,ht as BOLD_ITALIC_UNDERSCORE,xt as BOLD_STAR,Tt as BOLD_UNDERSCORE,ut as CHECK_LIST,at as CODE,St as ELEMENT_TRANSFORMERS,ct as HEADING,dt as HIGHLIGHT,pt as INLINE_CODE,yt as ITALIC_STAR,vt as ITALIC_UNDERSCORE,wt as LINK,gt as ORDERED_LIST,lt as QUOTE,Ct as STRIKETHROUGH,bt as TEXT_FORMAT_TRANSFORMERS,Et as TEXT_MATCH_TRANSFORMERS,$t as TRANSFORMERS,ft as UNORDERED_LIST,ot as registerMarkdownShortcuts};
|
package/LexicalMarkdown.prod.js
CHANGED
|
@@ -9,27 +9,27 @@ function ba(a){let b=I(a),c=b.textFormat.filter(d=>1===d.format.length);return d
|
|
|
9
9
|
function K(a,b,c){let d=[];a=a.getChildren();a:for(let e of a){for(let f of c)if(a=f.export(e,g=>K(g,b,c),(g,l)=>L(g,l,b)),null!=a){d.push(a);continue a}k.$isLineBreakNode(e)?d.push("\n"):k.$isTextNode(e)?d.push(L(e,e.getTextContent(),b)):k.$isElementNode(e)?d.push(K(e,b,c)):k.$isDecoratorNode(e)&&d.push(e.getTextContent())}return d.join("")}
|
|
10
10
|
function L(a,b,c){let d=b.trim(),e=d,f=new Set;for(let l of c){c=l.format[0];let r=l.tag;if(M(a,c)&&!f.has(c)){f.add(c);var g=N(a,!0);M(g,c)||(e=r+e);g=N(a,!1);M(g,c)||(e+=r)}}return b.replace(d,()=>e)}
|
|
11
11
|
function N(a,b){let c=b?a.getPreviousSibling():a.getNextSibling();c||(a=a.getParentOrThrow(),a.isInline()&&(c=b?a.getPreviousSibling():a.getNextSibling()));for(;c;){if(k.$isElementNode(c)){if(!c.isInline())break;a=b?c.getLastDescendant():c.getFirstDescendant();if(k.$isTextNode(a))return a;c=b?c.getPreviousSibling():c.getNextSibling()}if(k.$isTextNode(c))return c;if(!k.$isElementNode(c))break}return null}function M(a,b){return k.$isTextNode(a)&&a.hasFormat(b)}
|
|
12
|
-
let O="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement,da=O&&"documentMode"in document?document.documentMode:null;O&&/Mac|iPod|iPhone|iPad/.test(navigator.platform);O&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);O&&"InputEvent"in window&&!da?"getTargetRanges"in new window.InputEvent("input"):!1;
|
|
13
|
-
O&&/
|
|
14
|
-
function ha(a){let b=I(a),c=ia(b.textFormat);return(d,e)=>{d=d.split("\n");var f=d.length;e=e||k.$getRoot();e.clear();for(let h=0;h<f;h++){var g=d[h];a:{var l=d,r=h;var q=e;var y=l[r].match(
|
|
15
|
-
g.append(y);m.append(g);for(let {regExp:x,replace:u}of w)if(m=q.match(x)){y.setTextContent(q.slice(m[0].length));u(g,[y],m,!0);break}
|
|
16
|
-
for(let h of d)d=h,k.$isParagraphNode(d)?(f=d.getFirstChild(),d=null==f||1===d.getChildrenSize()&&k.$isTextNode(f)&&fa.test(f.getTextContent())):d=!1,d&&h.remove();null!==k.$getSelection()&&e.selectEnd()}}
|
|
17
|
-
function
|
|
18
|
-
function
|
|
12
|
+
let O="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement,da=O&&"documentMode"in document?document.documentMode:null;O&&/Mac|iPod|iPhone|iPad/.test(navigator.platform);O&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);O&&"InputEvent"in window&&!da?"getTargetRanges"in new window.InputEvent("input"):!1;
|
|
13
|
+
let P=O&&/Version\/[\d.]+.*Safari/.test(navigator.userAgent),Q=O&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,ea=O&&/Android/.test(navigator.userAgent),R=O&&/^(?=.*Chrome).*/i.test(navigator.userAgent);O&&ea&&R;let S=O&&/AppleWebKit\/[\d.]+/.test(navigator.userAgent)&&!R,fa=/^\s{0,3}$/,T=/^```(\w{1,10})?\s?$/;
|
|
14
|
+
function ha(a){let b=I(a),c=ia(b.textFormat);return(d,e)=>{d=d.split("\n");var f=d.length;e=e||k.$getRoot();e.clear();for(let h=0;h<f;h++){var g=d[h];a:{var l=d,r=h;var q=e;var y=l[r].match(T);if(y)for(var p=r,m=l.length;++p<m;)if(l[p].match(T)){y=t.$createCodeNode(y[1]);l=k.$createTextNode(l.slice(r+1,p).join("\n"));y.append(l);q.append(y);q=[y,p];break a}q=[null,r]}let [n,v]=q;if(null!=n)h=v;else{q=g;m=e;var w=b.element;p=c;l=b.textMatch;r=q.trim();y=k.$createTextNode(r);g=k.$createParagraphNode();
|
|
15
|
+
g.append(y);m.append(g);for(let {regExp:x,replace:u}of w)if(m=q.match(x)){y.setTextContent(q.slice(m[0].length));u(g,[y],m,!0);break}U(y,p,l);g.isAttached()&&0<r.length&&(q=g.getPreviousSibling(),k.$isParagraphNode(q)||B.$isQuoteNode(q)||z.$isListNode(q))&&(p=q,z.$isListNode(q)&&(q=q.getLastDescendant(),p=null==q?null:aa.$findMatchingParent(q,z.$isListItemNode)),null!=p&&0<p.getTextContentSize()&&(p.splice(p.getChildrenSize(),0,[k.$createLineBreakNode(),...g.getChildren()]),g.remove()))}}d=e.getChildren();
|
|
16
|
+
for(let h of d)d=h,k.$isParagraphNode(d)?(f=d.getFirstChild(),d=null==f||1===d.getChildrenSize()&&k.$isTextNode(f)&&fa.test(f.getTextContent())):d=!1,d&&1<e.getChildrenSize()&&h.remove();null!==k.$getSelection()&&e.selectEnd()}}
|
|
17
|
+
function U(a,b,c){var d=a.getTextContent();let e=ja(d,b);if(e){var f,g;if(e[0]===d)var l=a;else{d=e.index||0;let r=d+e[0].length;0===d?[l,f]=a.splitText(r):[g,l,f]=a.splitText(d,r)}l.setTextContent(e[2]);if(a=b.transformersByTag[e[1]])for(let r of a.format)l.hasFormat(r)||l.toggleFormat(r);l.hasFormat("code")||U(l,b,c);g&&U(g,b,c);f&&U(f,b,c)}else V(a,c)}
|
|
18
|
+
function V(a,b){a:for(;a;){for(let c of b){let d=a.getTextContent().match(c.importRegExp);if(!d)continue;let e=d.index||0,f=e+d[0].length,g,l;0===e?[g,a]=a.splitText(f):[,g,l]=a.splitText(e,f);l&&V(l,b);c.replace(g,d);continue a}break}}
|
|
19
19
|
function ja(a,b){var c=a.match(b.openTagsRegExp);if(null==c)return null;for(let f of c){var d=f.replace(/^\s/,"");c=b.fullMatchRegExpByTag[d];if(null!=c&&(c=a.match(c),d=b.transformersByTag[d],null!=c&&null!=d)){if(!1!==d.intraword)return c;var {index:e=0}=c;d=a[e-1];e=a[e+c[0].length];if(!(d&&!J.test(d)||e&&!J.test(e)))return c}}return null}
|
|
20
|
-
function ia(a){let b={},c={},d=[];for(let e of a){({tag:a}=e);b[a]=e;let f=a.replace(/(\*|\^|\+)/g,"\\$1");d.push(f);c[a]=P||Q||
|
|
21
|
-
function
|
|
22
|
-
let
|
|
23
|
-
4))&&g.setIndent(b)},Y=(a,b,c)=>{const d=[];var e=a.getChildren();let f=0;for(const l of e)if(z.$isListItemNode(l)){if(1===l.getChildrenSize()&&(e=l.getFirstChild(),z.$isListNode(e))){d.push(Y(e,b,c+1));continue}e=" ".repeat(4*c);var g=a.getListType();g="number"===g?`${a.getStart()+f}. `:"check"===g?`- [${l.getChecked()?"x":" "}] `:"- ";d.push(e+g+b(l));f++}return d.join("\n")},
|
|
24
|
-
return"#".repeat(c)+" "+b(a)},regExp:/^(#{1,6})\s/,replace:
|
|
25
|
-
c.select(0,0)},type:"element"},
|
|
26
|
-
Y(a,b,0):null,regExp:/^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i,replace:X("check"),type:"element"},
|
|
27
|
-
type:"text-format"},
|
|
28
|
-
c(d,b):b},importRegExp:/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))/,regExp:/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))$/,replace:(a,b)=>{const [,c,d,e]=b;b=G.$createLinkNode(d,{title:e});const f=k.$createTextNode(c);f.setFormat(a.getFormat());b.append(f);a.replace(b)},trigger:")",type:"text-match"},
|
|
29
|
-
exports.$convertFromMarkdownString=function(a,b=Z,c){return ha(b)(a,c)};exports.$convertToMarkdownString=function(a=Z,b){return ba(a)(b)};exports.BOLD_ITALIC_STAR=
|
|
30
|
-
exports.STRIKETHROUGH=
|
|
31
|
-
exports.registerMarkdownShortcuts=function(a,b=Z){let c=I(b),d=H(c.textFormat,({tag:f})=>f[f.length-1]),e=H(c.textMatch,({trigger:f})=>f);for(let f of b)if(b=f.type,"element"===b||"text-match"===b){b=f.dependencies;for(let g of b)if(!a.hasNode(g))throw Error(`MarkdownShortcuts: missing dependency ${g.getType()} for transformer. Ensure node dependency is included in editor initial config.`);}return a.registerUpdateListener(({tags:f,dirtyLeaves:g,editorState:l,prevEditorState:r})=>{if(!f.has("
|
|
32
|
-
!a.isComposing()){var q=l.read(k.$getSelection);f=r.read(k.$getSelection);if(k.$isRangeSelection(f)&&k.$isRangeSelection(q)&&q.isCollapsed()){r=q.anchor.key;var y=q.anchor.offset,p=l._nodeMap.get(r);!k.$isTextNode(p)||!g.has(r)||1!==y&&y>f.anchor.offset+1||a.update(()=>{if(!p.hasFormat("code")){var m=p.getParent();if(null!==m&&!t.$isCodeNode(m)){var w=q.anchor.offset;b:{var h=c.element,n=m.getParent();if(k.$isRootOrShadowRoot(n)&&m.getFirstChild()===p&&(n=p.getTextContent()," "===
|
|
33
|
-
replace:E}of h)if((h=n.match(D))&&h[0].length===w){n=p.getNextSiblings();let [F,
|
|
34
|
-
D;x=C.length;let E=w-x+1;if(!(1<x&&!
|
|
35
|
-
u=k.$createRangeSelection();k.$setSelection(u);w=w-x*(m===v?2:1)+1;u.anchor.set(m.__key,h,"text");u.focus.set(v.__key,w,"text");for(let F of D.format)u.hasFormat(F)||u.formatText(F);u.anchor.set(u.focus.key,u.focus.offset,u.focus.type);for(let F of D.format)u.hasFormat(F)&&u.toggleFormat(F);k.$isRangeSelection(n)&&(u.format=n.format);break b}}}}}}}})}}})}
|
|
20
|
+
function ia(a){let b={},c={},d=[];for(let e of a){({tag:a}=e);b[a]=e;let f=a.replace(/(\*|\^|\+)/g,"\\$1");d.push(f);c[a]=P||Q||S?new RegExp(`(${f})(?![${f}\\s])(.*?[^${f}\\s])${f}(?!${f})`):new RegExp(`(?<![\\\\${f}])(${f})((\\\\${f})?.*?[^${f}\\s](\\\\${f})?)((?<!\\\\)|(?<=\\\\\\\\))(${f})(?![\\\\${f}])`)}return{fullMatchRegExpByTag:c,openTagsRegExp:new RegExp((P||Q||S?"":"(?<![\\\\])")+"("+d.join("|")+")","g"),transformersByTag:b}}
|
|
21
|
+
function W(a,b,c){let d=c.length;for(;b>=d;b--){let e=b-d;if(ka(a,e,c,0,d)&&" "!==a[e+d])return e}return-1}function ka(a,b,c,d,e){for(let f=0;f<e;f++)if(a[b+f]!==c[d+f])return!1;return!0}
|
|
22
|
+
let la=a=>(b,c,d)=>{d=a(d);d.append(...c);b.replace(d);d.select(0,0)},X=a=>(b,c,d)=>{var e=b.getPreviousSibling(),f=b.getNextSibling();const g=z.$createListItemNode("check"===a?"x"===d[3]:void 0);z.$isListNode(f)&&f.getListType()===a?(e=f.getFirstChild(),null!==e?e.insertBefore(g):f.append(g),b.remove()):z.$isListNode(e)&&e.getListType()===a?(e.append(g),b.remove()):(f=z.$createListNode(a,"number"===a?Number(d[2]):void 0),f.append(g),b.replace(f));g.append(...c);g.select(0,0);c=d[1];b=c.match(/\t/g);
|
|
23
|
+
c=c.match(/ /g);d=0;b&&(d+=b.length);c&&(d+=Math.floor(c.length/4));(b=d)&&g.setIndent(b)},Y=(a,b,c)=>{const d=[];var e=a.getChildren();let f=0;for(const l of e)if(z.$isListItemNode(l)){if(1===l.getChildrenSize()&&(e=l.getFirstChild(),z.$isListNode(e))){d.push(Y(e,b,c+1));continue}e=" ".repeat(4*c);var g=a.getListType();g="number"===g?`${a.getStart()+f}. `:"check"===g?`- [${l.getChecked()?"x":" "}] `:"- ";d.push(e+g+b(l));f++}return d.join("\n")},ma={dependencies:[B.HeadingNode],export:(a,b)=>{if(!B.$isHeadingNode(a))return null;
|
|
24
|
+
const c=Number(a.getTag().slice(1));return"#".repeat(c)+" "+b(a)},regExp:/^(#{1,6})\s/,replace:la(a=>B.$createHeadingNode("h"+a[1].length)),type:"element"},na={dependencies:[B.QuoteNode],export:(a,b)=>{if(!B.$isQuoteNode(a))return null;a=b(a).split("\n");b=[];for(const c of a)b.push("> "+c);return b.join("\n")},regExp:/^>\s/,replace:(a,b,c,d)=>{if(d&&(c=a.getPreviousSibling(),B.$isQuoteNode(c))){c.splice(c.getChildrenSize(),0,[k.$createLineBreakNode(),...b]);c.select(0,0);a.remove();return}c=B.$createQuoteNode();
|
|
25
|
+
c.append(...b);a.replace(c);c.select(0,0)},type:"element"},oa={dependencies:[t.CodeNode],export:a=>{if(!t.$isCodeNode(a))return null;const b=a.getTextContent();return"```"+(a.getLanguage()||"")+(b?"\n"+b:"")+"\n```"},regExp:/^```(\w{1,10})?\s/,replace:la(a=>t.$createCodeNode(a?a[1]:void 0)),type:"element"},pa={dependencies:[z.ListNode,z.ListItemNode],export:(a,b)=>z.$isListNode(a)?Y(a,b,0):null,regExp:/^(\s*)[-*+]\s/,replace:X("bullet"),type:"element"},qa={dependencies:[z.ListNode,z.ListItemNode],
|
|
26
|
+
export:(a,b)=>z.$isListNode(a)?Y(a,b,0):null,regExp:/^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i,replace:X("check"),type:"element"},ra={dependencies:[z.ListNode,z.ListItemNode],export:(a,b)=>z.$isListNode(a)?Y(a,b,0):null,regExp:/^(\s*)(\d{1,})\.\s/,replace:X("number"),type:"element"},sa={format:["code"],tag:"`",type:"text-format"},ta={format:["highlight"],tag:"==",type:"text-format"},ua={format:["bold","italic"],tag:"***",type:"text-format"},wa={format:["bold","italic"],intraword:!1,tag:"___",type:"text-format"},
|
|
27
|
+
xa={format:["bold"],tag:"**",type:"text-format"},ya={format:["bold"],intraword:!1,tag:"__",type:"text-format"},za={format:["strikethrough"],tag:"~~",type:"text-format"},Aa={format:["italic"],tag:"*",type:"text-format"},Ba={format:["italic"],intraword:!1,tag:"_",type:"text-format"},Ca={dependencies:[G.LinkNode],export:(a,b,c)=>{if(!G.$isLinkNode(a))return null;b=(b=a.getTitle())?`[${a.getTextContent()}](${a.getURL()} "${b}")`:`[${a.getTextContent()}](${a.getURL()})`;const d=a.getFirstChild();return 1===
|
|
28
|
+
a.getChildrenSize()&&k.$isTextNode(d)?c(d,b):b},importRegExp:/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))/,regExp:/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))$/,replace:(a,b)=>{const [,c,d,e]=b;b=G.$createLinkNode(d,{title:e});const f=k.$createTextNode(c);f.setFormat(a.getFormat());b.append(f);a.replace(b)},trigger:")",type:"text-match"},Da=[ma,na,oa,pa,ra],Ea=[sa,ua,wa,xa,ya,ta,Aa,Ba,za],Fa=[Ca],Z=[...Da,...Ea,...Fa];
|
|
29
|
+
exports.$convertFromMarkdownString=function(a,b=Z,c){return ha(b)(a,c)};exports.$convertToMarkdownString=function(a=Z,b){return ba(a)(b)};exports.BOLD_ITALIC_STAR=ua;exports.BOLD_ITALIC_UNDERSCORE=wa;exports.BOLD_STAR=xa;exports.BOLD_UNDERSCORE=ya;exports.CHECK_LIST=qa;exports.CODE=oa;exports.ELEMENT_TRANSFORMERS=Da;exports.HEADING=ma;exports.HIGHLIGHT=ta;exports.INLINE_CODE=sa;exports.ITALIC_STAR=Aa;exports.ITALIC_UNDERSCORE=Ba;exports.LINK=Ca;exports.ORDERED_LIST=ra;exports.QUOTE=na;
|
|
30
|
+
exports.STRIKETHROUGH=za;exports.TEXT_FORMAT_TRANSFORMERS=Ea;exports.TEXT_MATCH_TRANSFORMERS=Fa;exports.TRANSFORMERS=Z;exports.UNORDERED_LIST=pa;
|
|
31
|
+
exports.registerMarkdownShortcuts=function(a,b=Z){let c=I(b),d=H(c.textFormat,({tag:f})=>f[f.length-1]),e=H(c.textMatch,({trigger:f})=>f);for(let f of b)if(b=f.type,"element"===b||"text-match"===b){b=f.dependencies;for(let g of b)if(!a.hasNode(g))throw Error(`MarkdownShortcuts: missing dependency ${g.getType()} for transformer. Ensure node dependency is included in editor initial config.`);}return a.registerUpdateListener(({tags:f,dirtyLeaves:g,editorState:l,prevEditorState:r})=>{if(!f.has("collaboration")&&
|
|
32
|
+
!f.has("historic")&&!a.isComposing()){var q=l.read(k.$getSelection);f=r.read(k.$getSelection);if(k.$isRangeSelection(f)&&k.$isRangeSelection(q)&&q.isCollapsed()){r=q.anchor.key;var y=q.anchor.offset,p=l._nodeMap.get(r);!k.$isTextNode(p)||!g.has(r)||1!==y&&y>f.anchor.offset+1||a.update(()=>{if(!p.hasFormat("code")){var m=p.getParent();if(null!==m&&!t.$isCodeNode(m)){var w=q.anchor.offset;b:{var h=c.element,n=m.getParent();if(k.$isRootOrShadowRoot(n)&&m.getFirstChild()===p&&(n=p.getTextContent()," "===
|
|
33
|
+
n[w-1]))for(let {regExp:D,replace:E}of h)if((h=n.match(D))&&h[0].length===w){n=p.getNextSiblings();let [F,va]=p.splitText(w);F.remove();n=va?[va,...n]:n;E(m,n,h,!1);m=!0;break b}m=!1}if(!m){b:{h=p.getTextContent();m=e[h[w-1]];if(null!=m){w<h.length&&(h=h.slice(0,w));for(x of m)if(m=h.match(x.regExp),null!==m){h=m.index||0;n=h+m[0].length;var v=void 0;0===h?[v]=p.splitText(n):[,v]=p.splitText(h,n);v.selectNext(0,0);x.replace(v,m);var x=!0;break b}}x=!1}if(!x)b:{n=p.getTextContent();--w;var u=n[w];
|
|
34
|
+
if(x=d[u])for(let D of x){var {tag:C}=D;x=C.length;let E=w-x+1;if(!(1<x&&!ka(n,E,C,0,x)||" "===n[E-1])&&(v=n[w+1],!1!==D.intraword||!v||J.test(v))){m=v=p;h=W(n,E,C);for(var A=m;0>h&&(A=A.getPreviousSibling())&&!k.$isLineBreakNode(A);)k.$isTextNode(A)&&(h=A.getTextContent(),m=A,h=W(h,h.length,C));if(!(0>h||m===v&&h+x===E||(C=m.getTextContent(),0<h&&C[h-1]===u||(A=C[h-1],!1===D.intraword&&A&&!J.test(A))))){n=v.getTextContent();n=n.slice(0,E)+n.slice(w+1);v.setTextContent(n);n=m===v?n:C;m.setTextContent(n.slice(0,
|
|
35
|
+
h)+n.slice(h+x));n=k.$getSelection();u=k.$createRangeSelection();k.$setSelection(u);w=w-x*(m===v?2:1)+1;u.anchor.set(m.__key,h,"text");u.focus.set(v.__key,w,"text");for(let F of D.format)u.hasFormat(F)||u.formatText(F);u.anchor.set(u.focus.key,u.focus.offset,u.focus.type);for(let F of D.format)u.hasFormat(F)&&u.toggleFormat(F);k.$isRangeSelection(n)&&(u.format=n.format);break b}}}}}}}})}}})}
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# `@lexical/markdown`
|
|
2
2
|
|
|
3
|
+
[](https://lexical.dev/docs/api/modules/lexical_markdown)
|
|
4
|
+
|
|
3
5
|
This package contains markdown helpers for Lexical: import, export and shortcuts.
|
|
4
6
|
|
|
5
7
|
## Import and export
|
package/package.json
CHANGED
|
@@ -8,22 +8,24 @@
|
|
|
8
8
|
"markdown"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.14.1",
|
|
12
12
|
"main": "LexicalMarkdown.js",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"lexical": "0.
|
|
14
|
+
"lexical": "0.14.1"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@lexical/utils": "0.
|
|
18
|
-
"@lexical/code": "0.
|
|
19
|
-
"@lexical/text": "0.
|
|
20
|
-
"@lexical/rich-text": "0.
|
|
21
|
-
"@lexical/list": "0.
|
|
22
|
-
"@lexical/link": "0.
|
|
17
|
+
"@lexical/utils": "0.14.1",
|
|
18
|
+
"@lexical/code": "0.14.1",
|
|
19
|
+
"@lexical/text": "0.14.1",
|
|
20
|
+
"@lexical/rich-text": "0.14.1",
|
|
21
|
+
"@lexical/list": "0.14.1",
|
|
22
|
+
"@lexical/link": "0.14.1"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
26
26
|
"url": "https://github.com/facebook/lexical",
|
|
27
27
|
"directory": "packages/lexical-markdown"
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"module": "LexicalMarkdown.esm.js",
|
|
30
|
+
"sideEffects": false
|
|
29
31
|
}
|