@lexical/markdown 0.2.4 → 0.2.7
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.d.ts +84 -5
- package/LexicalMarkdown.dev.js +697 -1035
- package/LexicalMarkdown.js.flow +83 -5
- package/LexicalMarkdown.prod.js +20 -34
- package/README.md +88 -6
- package/package.json +8 -8
package/LexicalMarkdown.dev.js
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
var code = require('@lexical/code');
|
|
10
|
-
var list = require('@lexical/list');
|
|
11
9
|
var lexical = require('lexical');
|
|
10
|
+
var code = require('@lexical/code');
|
|
12
11
|
var link = require('@lexical/link');
|
|
12
|
+
var list = require('@lexical/list');
|
|
13
13
|
var richText = require('@lexical/rich-text');
|
|
14
|
-
var text = require('@lexical/text');
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -21,834 +20,390 @@ var text = require('@lexical/text');
|
|
|
21
20
|
*
|
|
22
21
|
*
|
|
23
22
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES = '\u0004'; // Select an unused unicode character to separate text and non-text nodes.
|
|
39
|
-
|
|
40
|
-
const SEPARATOR_LENGTH = SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES.length;
|
|
41
|
-
const spaceTrigger = {
|
|
42
|
-
triggerKind: 'space_trigger',
|
|
43
|
-
triggerString: '\u0020'
|
|
44
|
-
}; // Future todo: add support for ``` + carriage return either inside or not inside code block. Should toggle between.
|
|
45
|
-
// const codeBlockTrigger : AutoFormatTrigger = {
|
|
46
|
-
// triggerKind: 'codeBlock_trigger',
|
|
47
|
-
// triggerString: '```', // + new paragraph element or new code block element.
|
|
48
|
-
// };
|
|
49
|
-
|
|
50
|
-
const triggers = [spaceTrigger
|
|
51
|
-
/*, codeBlockTrigger*/
|
|
52
|
-
]; // Future Todo: speed up performance by having non-capture group variations of the regex.
|
|
53
|
-
|
|
54
|
-
const autoFormatBase = {
|
|
55
|
-
markdownFormatKind: null,
|
|
56
|
-
regEx: /(?:)/,
|
|
57
|
-
regExForAutoFormatting: /(?:)/,
|
|
58
|
-
requiresParagraphStart: false
|
|
59
|
-
};
|
|
60
|
-
const paragraphStartBase = { ...autoFormatBase,
|
|
61
|
-
requiresParagraphStart: true
|
|
62
|
-
};
|
|
63
|
-
const markdownHeader1 = { ...paragraphStartBase,
|
|
64
|
-
markdownFormatKind: 'paragraphH1',
|
|
65
|
-
regEx: /^(?:# )/,
|
|
66
|
-
regExForAutoFormatting: /^(?:# )/
|
|
67
|
-
};
|
|
68
|
-
const markdownHeader2 = { ...paragraphStartBase,
|
|
69
|
-
markdownFormatKind: 'paragraphH2',
|
|
70
|
-
regEx: /^(?:## )/,
|
|
71
|
-
regExForAutoFormatting: /^(?:## )/
|
|
72
|
-
};
|
|
73
|
-
const markdownHeader3 = { ...paragraphStartBase,
|
|
74
|
-
markdownFormatKind: 'paragraphH3',
|
|
75
|
-
regEx: /^(?:### )/,
|
|
76
|
-
regExForAutoFormatting: /^(?:### )/
|
|
77
|
-
};
|
|
78
|
-
const markdownHeader4 = { ...paragraphStartBase,
|
|
79
|
-
markdownFormatKind: 'paragraphH4',
|
|
80
|
-
regEx: /^(?:#### )/,
|
|
81
|
-
regExForAutoFormatting: /^(?:#### )/
|
|
82
|
-
};
|
|
83
|
-
const markdownHeader5 = { ...paragraphStartBase,
|
|
84
|
-
markdownFormatKind: 'paragraphH5',
|
|
85
|
-
regEx: /^(?:##### )/,
|
|
86
|
-
regExForAutoFormatting: /^(?:##### )/
|
|
87
|
-
};
|
|
88
|
-
const markdownBlockQuote = { ...paragraphStartBase,
|
|
89
|
-
markdownFormatKind: 'paragraphBlockQuote',
|
|
90
|
-
regEx: /^(?:> )/,
|
|
91
|
-
regExForAutoFormatting: /^(?:> )/
|
|
92
|
-
};
|
|
93
|
-
const markdownUnorderedListDash = { ...paragraphStartBase,
|
|
94
|
-
markdownFormatKind: 'paragraphUnorderedList',
|
|
95
|
-
regEx: /^(\s{0,10})(?:- )/,
|
|
96
|
-
regExForAutoFormatting: /^(\s{0,10})(?:- )/
|
|
97
|
-
};
|
|
98
|
-
const markdownUnorderedListAsterisk = { ...paragraphStartBase,
|
|
99
|
-
markdownFormatKind: 'paragraphUnorderedList',
|
|
100
|
-
regEx: /^(\s{0,10})(?:\* )/,
|
|
101
|
-
regExForAutoFormatting: /^(\s{0,10})(?:\* )/
|
|
102
|
-
};
|
|
103
|
-
const markdownCodeBlock = { ...paragraphStartBase,
|
|
104
|
-
markdownFormatKind: 'paragraphCodeBlock',
|
|
105
|
-
regEx: /^(```)$/,
|
|
106
|
-
regExForAutoFormatting: /^(```)([a-z]*)( )/
|
|
107
|
-
};
|
|
108
|
-
const markdownOrderedList = { ...paragraphStartBase,
|
|
109
|
-
markdownFormatKind: 'paragraphOrderedList',
|
|
110
|
-
regEx: /^(\s{0,10})(\d+)\.\s/,
|
|
111
|
-
regExForAutoFormatting: /^(\s{0,10})(\d+)\.\s/
|
|
112
|
-
};
|
|
113
|
-
const markdownHorizontalRule = { ...paragraphStartBase,
|
|
114
|
-
markdownFormatKind: 'horizontalRule',
|
|
115
|
-
regEx: /^(?:\*\*\*)$/,
|
|
116
|
-
regExForAutoFormatting: /^(?:\*\*\* )/
|
|
117
|
-
};
|
|
118
|
-
const markdownHorizontalRuleUsingDashes = { ...paragraphStartBase,
|
|
119
|
-
markdownFormatKind: 'horizontalRule',
|
|
120
|
-
regEx: /^(?:---)$/,
|
|
121
|
-
regExForAutoFormatting: /^(?:--- )/
|
|
122
|
-
};
|
|
123
|
-
const markdownInlineCode = { ...autoFormatBase,
|
|
124
|
-
markdownFormatKind: 'code',
|
|
125
|
-
regEx: /(`)([^`]*)(`)/,
|
|
126
|
-
regExForAutoFormatting: /(`)(\s*\b)([^`]*)(\b\s*)(`)(\s)$/
|
|
127
|
-
};
|
|
128
|
-
const markdownBold = { ...autoFormatBase,
|
|
129
|
-
markdownFormatKind: 'bold',
|
|
130
|
-
regEx: /(\*\*)(\s*)([^\*\*]*)(\s*)(\*\*)()/,
|
|
131
|
-
regExForAutoFormatting: /(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*)(\s)$/
|
|
132
|
-
};
|
|
133
|
-
const markdownItalic = { ...autoFormatBase,
|
|
134
|
-
markdownFormatKind: 'italic',
|
|
135
|
-
regEx: /(\*)(\s*)([^\*]*)(\s*)(\*)()/,
|
|
136
|
-
regExForAutoFormatting: /(\*)(\s*\b)([^\*]*)(\b\s*)(\*)(\s)$/
|
|
137
|
-
};
|
|
138
|
-
const markdownBold2 = { ...autoFormatBase,
|
|
139
|
-
markdownFormatKind: 'bold',
|
|
140
|
-
regEx: /(__)(\s*)([^__]*)(\s*)(__)()/,
|
|
141
|
-
regExForAutoFormatting: /(__)(\s*)([^__]*)(\s*)(__)(\s)$/
|
|
142
|
-
};
|
|
143
|
-
const markdownItalic2 = { ...autoFormatBase,
|
|
144
|
-
markdownFormatKind: 'italic',
|
|
145
|
-
regEx: /(_)()([^_]*)()(_)()/,
|
|
146
|
-
regExForAutoFormatting: /(_)()([^_]*)()(_)(\s)$/ // Maintain 7 groups.
|
|
147
|
-
|
|
148
|
-
}; // Markdown does not support underline, but we can allow folks to use
|
|
149
|
-
// the HTML tags for underline.
|
|
150
|
-
|
|
151
|
-
const fakeMarkdownUnderline = { ...autoFormatBase,
|
|
152
|
-
markdownFormatKind: 'underline',
|
|
153
|
-
regEx: /(\<u\>)(\s*)([^\<]*)(\s*)(\<\/u\>)()/,
|
|
154
|
-
regExForAutoFormatting: /(\<u\>)(\s*\b)([^\<]*)(\b\s*)(\<\/u\>)(\s)$/
|
|
155
|
-
};
|
|
156
|
-
const markdownStrikethrough = { ...autoFormatBase,
|
|
157
|
-
markdownFormatKind: 'strikethrough',
|
|
158
|
-
regEx: /(~~)(\s*)([^~~]*)(\s*)(~~)()/,
|
|
159
|
-
regExForAutoFormatting: /(~~)(\s*\b)([^~~]*)(\b\s*)(~~)(\s)$/
|
|
160
|
-
};
|
|
161
|
-
const markdownStrikethroughItalicBold = { ...autoFormatBase,
|
|
162
|
-
markdownFormatKind: 'strikethrough_italic_bold',
|
|
163
|
-
regEx: /(~~_\*\*)(\s*\b)([^~~_\*\*][^\*\*_~~]*)(\b\s*)(\*\*_~~)()/,
|
|
164
|
-
regExForAutoFormatting: /(~~_\*\*)(\s*\b)([^~~_\*\*][^\*\*_~~]*)(\b\s*)(\*\*_~~)(\s)$/
|
|
165
|
-
};
|
|
166
|
-
const markdownItalicbold = { ...autoFormatBase,
|
|
167
|
-
markdownFormatKind: 'italic_bold',
|
|
168
|
-
regEx: /(_\*\*)(\s*\b)([^_\*\*][^\*\*_]*)(\b\s*)(\*\*_)/,
|
|
169
|
-
regExForAutoFormatting: /(_\*\*)(\s*\b)([^_\*\*][^\*\*_]*)(\b\s*)(\*\*_)(\s)$/
|
|
170
|
-
};
|
|
171
|
-
const markdownStrikethroughItalic = { ...autoFormatBase,
|
|
172
|
-
markdownFormatKind: 'strikethrough_italic',
|
|
173
|
-
regEx: /(~~_)(\s*)([^~~_][^_~~]*)(\s*)(_~~)/,
|
|
174
|
-
regExForAutoFormatting: /(~~_)(\s*)([^~~_][^_~~]*)(\s*)(_~~)(\s)$/
|
|
175
|
-
};
|
|
176
|
-
const markdownStrikethroughBold = { ...autoFormatBase,
|
|
177
|
-
markdownFormatKind: 'strikethrough_bold',
|
|
178
|
-
regEx: /(~~\*\*)(\s*\b)([^~~\*\*][^\*\*~~]*)(\b\s*)(\*\*~~)/,
|
|
179
|
-
regExForAutoFormatting: /(~~\*\*)(\s*\b)([^~~\*\*][^\*\*~~]*)(\b\s*)(\*\*~~)(\s)$/
|
|
180
|
-
};
|
|
181
|
-
const markdownLink = { ...autoFormatBase,
|
|
182
|
-
markdownFormatKind: 'link',
|
|
183
|
-
regEx: /(\[)([^\]]*)(\]\()([^)]*)(\)*)()/,
|
|
184
|
-
regExForAutoFormatting: /(\[)([^\]]*)(\]\()([^)]*)(\)*)(\s)$/
|
|
185
|
-
};
|
|
186
|
-
const allMarkdownCriteriaForTextNodes = [// Place the combination formats ahead of the individual formats.
|
|
187
|
-
// Combos
|
|
188
|
-
markdownStrikethroughItalicBold, markdownItalicbold, markdownStrikethroughItalic, markdownStrikethroughBold, // Individuals
|
|
189
|
-
markdownInlineCode, markdownBold, markdownItalic, // Must appear after markdownBold
|
|
190
|
-
markdownBold2, markdownItalic2, // Must appear after markdownBold2.
|
|
191
|
-
fakeMarkdownUnderline, markdownStrikethrough, markdownLink];
|
|
192
|
-
const allMarkdownCriteriaForParagraphs = [markdownHeader1, markdownHeader2, markdownHeader3, markdownHeader4, markdownHeader5, markdownBlockQuote, markdownUnorderedListDash, markdownUnorderedListAsterisk, markdownOrderedList, markdownCodeBlock, markdownHorizontalRule, markdownHorizontalRuleUsingDashes];
|
|
193
|
-
const allMarkdownCriteria = [...allMarkdownCriteriaForParagraphs, ...allMarkdownCriteriaForTextNodes];
|
|
194
|
-
function getAllTriggers() {
|
|
195
|
-
return triggers;
|
|
196
|
-
}
|
|
197
|
-
function getAllMarkdownCriteriaForParagraphs() {
|
|
198
|
-
return allMarkdownCriteriaForParagraphs;
|
|
199
|
-
}
|
|
200
|
-
function getAllMarkdownCriteriaForTextNodes() {
|
|
201
|
-
return allMarkdownCriteriaForTextNodes;
|
|
202
|
-
}
|
|
203
|
-
function getAllMarkdownCriteria() {
|
|
204
|
-
return allMarkdownCriteria;
|
|
23
|
+
function indexBy(list, callback) {
|
|
24
|
+
const index = {};
|
|
25
|
+
|
|
26
|
+
for (const item of list) {
|
|
27
|
+
const key = callback(item);
|
|
28
|
+
|
|
29
|
+
if (index[key]) {
|
|
30
|
+
index[key].push(item);
|
|
31
|
+
} else {
|
|
32
|
+
index[key] = [item];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return index;
|
|
205
37
|
}
|
|
206
|
-
function
|
|
38
|
+
function transformersByType(transformers) {
|
|
39
|
+
const byType = indexBy(transformers, t => t.type);
|
|
207
40
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
markdownFormatKind: 'noTransformation',
|
|
215
|
-
regEx: /(?:)/,
|
|
216
|
-
// Empty reg ex.
|
|
217
|
-
regExForAutoFormatting: /(?:)/,
|
|
218
|
-
// Empty reg ex.
|
|
219
|
-
requiresParagraphStart: null
|
|
220
|
-
},
|
|
221
|
-
patternMatchResults: {
|
|
222
|
-
regExCaptureGroups: []
|
|
223
|
-
},
|
|
224
|
-
textNodeWithOffset,
|
|
225
|
-
triggerState
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
function resetScanningContext(scanningContext) {
|
|
229
|
-
scanningContext.joinedText = null;
|
|
230
|
-
scanningContext.markdownCriteria = {
|
|
231
|
-
markdownFormatKind: 'noTransformation',
|
|
232
|
-
regEx: /(?:)/,
|
|
233
|
-
// Empty reg ex.
|
|
234
|
-
regExForAutoFormatting: /(?:)/,
|
|
235
|
-
// Empty reg ex.
|
|
236
|
-
requiresParagraphStart: null
|
|
41
|
+
// $FlowFixMe
|
|
42
|
+
element: byType.element,
|
|
43
|
+
// $FlowFixMe
|
|
44
|
+
textFormat: byType['text-format'],
|
|
45
|
+
// $FlowFixMe
|
|
46
|
+
textMatch: byType['text-match']
|
|
237
47
|
};
|
|
238
|
-
scanningContext.patternMatchResults = {
|
|
239
|
-
regExCaptureGroups: []
|
|
240
|
-
};
|
|
241
|
-
scanningContext.triggerState = null;
|
|
242
|
-
scanningContext.textNodeWithOffset = null;
|
|
243
|
-
return scanningContext;
|
|
244
|
-
}
|
|
245
|
-
function getCodeBlockCriteria() {
|
|
246
|
-
return markdownCodeBlock;
|
|
247
48
|
}
|
|
248
|
-
function getPatternMatchResultsForCriteria(markdownCriteria, scanningContext, parentElementNode) {
|
|
249
|
-
if (markdownCriteria.requiresParagraphStart === true) {
|
|
250
|
-
return getPatternMatchResultsForParagraphs(markdownCriteria, scanningContext);
|
|
251
|
-
}
|
|
252
49
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
52
|
+
*
|
|
53
|
+
* This source code is licensed under the MIT license found in the
|
|
54
|
+
* LICENSE file in the root directory of this source tree.
|
|
55
|
+
*
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
function createMarkdownExport(transformers) {
|
|
59
|
+
const byType = transformersByType(transformers); // Export only uses text formats that are responsible for single format
|
|
60
|
+
// e.g. it will filter out *** (bold, italic) and instead use separate ** and *
|
|
259
61
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (let captureGroupIndex = 0; captureGroupIndex < captureGroupsCount; captureGroupIndex++) {
|
|
271
|
-
const textContent = regExMatches[captureGroupIndex];
|
|
272
|
-
patternMatchResults.regExCaptureGroups.push({
|
|
273
|
-
offsetInParent: runningLength,
|
|
274
|
-
text: textContent
|
|
275
|
-
}); // The 0th capture group is special in that it's text contents is
|
|
276
|
-
// a join of all subsequent capture groups. So, skip this group
|
|
277
|
-
// when calculating the runningLength.
|
|
278
|
-
|
|
279
|
-
if (captureGroupIndex > 0) {
|
|
280
|
-
runningLength += textContent.length;
|
|
62
|
+
const textFormatTransformers = byType.textFormat.filter(transformer => transformer.format.length === 1);
|
|
63
|
+
return () => {
|
|
64
|
+
const output = [];
|
|
65
|
+
const children = lexical.$getRoot().getChildren();
|
|
66
|
+
|
|
67
|
+
for (const child of children) {
|
|
68
|
+
const result = exportTopLevelElements(child, byType.element, textFormatTransformers, byType.textMatch);
|
|
69
|
+
|
|
70
|
+
if (result != null) {
|
|
71
|
+
output.push(result);
|
|
281
72
|
}
|
|
282
73
|
}
|
|
283
74
|
|
|
284
|
-
return
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return null;
|
|
75
|
+
return output.join('\n');
|
|
76
|
+
};
|
|
288
77
|
}
|
|
289
78
|
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
function getTextNodeWithOffsetOrThrow(scanningContext) {
|
|
294
|
-
const textNodeWithOffset = scanningContext.textNodeWithOffset;
|
|
79
|
+
function exportTopLevelElements(node, elementTransformers, textTransformersIndex, textMatchTransformers) {
|
|
80
|
+
for (const transformer of elementTransformers) {
|
|
81
|
+
const result = transformer.export(node, _node => exportChildren(_node, textTransformersIndex, textMatchTransformers));
|
|
295
82
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
throw Error(`Expect to have a text node with offset.`);
|
|
83
|
+
if (result != null) {
|
|
84
|
+
return result;
|
|
299
85
|
}
|
|
300
86
|
}
|
|
301
87
|
|
|
302
|
-
return
|
|
88
|
+
return lexical.$isElementNode(node) ? exportChildren(node, textTransformersIndex, textMatchTransformers) : null;
|
|
303
89
|
}
|
|
304
90
|
|
|
305
|
-
function
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
if (textNodeWithOffset.node.getPreviousSibling() === null) {
|
|
309
|
-
const textToSearch = textNodeWithOffset.node.getTextContent();
|
|
310
|
-
return getPatternMatchResultsWithRegEx(textToSearch, true, false, scanningContext.isAutoFormatting ? markdownCriteria.regExForAutoFormatting : markdownCriteria.regEx);
|
|
311
|
-
}
|
|
91
|
+
function exportChildren(node, textTransformersIndex, textMatchTransformers) {
|
|
92
|
+
const output = [];
|
|
93
|
+
const children = node.getChildren();
|
|
312
94
|
|
|
313
|
-
|
|
314
|
-
|
|
95
|
+
mainLoop: for (const child of children) {
|
|
96
|
+
if (lexical.$isLineBreakNode(child)) {
|
|
97
|
+
output.push('\n');
|
|
98
|
+
} else if (lexical.$isTextNode(child)) {
|
|
99
|
+
output.push(exportTextFormat(child, child.getTextContent(), textTransformersIndex));
|
|
100
|
+
} else {
|
|
101
|
+
for (const transformer of textMatchTransformers) {
|
|
102
|
+
const result = transformer.export(child, parentNode => exportChildren(parentNode, textTransformersIndex, textMatchTransformers), (textNode, textContent) => exportTextFormat(textNode, textContent, textTransformersIndex));
|
|
315
103
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// Lazy calculate the text to search.
|
|
321
|
-
scanningContext.joinedText = text.$joinTextNodesInElementNode(parentElementNode, SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES, getTextNodeWithOffsetOrThrow(scanningContext));
|
|
104
|
+
if (result != null) {
|
|
105
|
+
output.push(result);
|
|
106
|
+
continue mainLoop;
|
|
107
|
+
}
|
|
322
108
|
}
|
|
323
|
-
|
|
324
|
-
{
|
|
325
|
-
|
|
109
|
+
|
|
110
|
+
if (lexical.$isElementNode(child)) {
|
|
111
|
+
output.push(exportChildren(child, textTransformersIndex, textMatchTransformers));
|
|
326
112
|
}
|
|
327
113
|
}
|
|
328
114
|
}
|
|
329
115
|
|
|
330
|
-
|
|
331
|
-
return getPatternMatchResultsWithRegEx(scanningContext.joinedText, false, matchMustAppearAtEndOfString, scanningContext.isAutoFormatting ? markdownCriteria.regExForAutoFormatting : markdownCriteria.regEx);
|
|
116
|
+
return output.join('');
|
|
332
117
|
}
|
|
333
118
|
|
|
334
|
-
function
|
|
335
|
-
let
|
|
336
|
-
const
|
|
337
|
-
const children = element.getChildren();
|
|
338
|
-
const markdownCriteria = scanningContext.markdownCriteria;
|
|
339
|
-
const patternMatchResults = scanningContext.patternMatchResults;
|
|
340
|
-
|
|
341
|
-
if (markdownCriteria.markdownFormatKind != null) {
|
|
342
|
-
switch (markdownCriteria.markdownFormatKind) {
|
|
343
|
-
case 'paragraphH1':
|
|
344
|
-
{
|
|
345
|
-
newNode = richText.$createHeadingNode('h1');
|
|
346
|
-
newNode.append(...children);
|
|
347
|
-
return {
|
|
348
|
-
newNode,
|
|
349
|
-
shouldDelete
|
|
350
|
-
};
|
|
351
|
-
}
|
|
119
|
+
function exportTextFormat(node, textContent, textTransformers) {
|
|
120
|
+
let output = textContent;
|
|
121
|
+
const applied = new Set();
|
|
352
122
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
newNode.append(...children);
|
|
357
|
-
return {
|
|
358
|
-
newNode,
|
|
359
|
-
shouldDelete
|
|
360
|
-
};
|
|
361
|
-
}
|
|
123
|
+
for (const transformer of textTransformers) {
|
|
124
|
+
const format = transformer.format[0];
|
|
125
|
+
const tag = transformer.tag;
|
|
362
126
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
newNode.append(...children);
|
|
367
|
-
return {
|
|
368
|
-
newNode,
|
|
369
|
-
shouldDelete
|
|
370
|
-
};
|
|
371
|
-
}
|
|
127
|
+
if (hasFormat(node, format) && !applied.has(format)) {
|
|
128
|
+
// Multiple tags might be used for the same format (*, _)
|
|
129
|
+
applied.add(format); // Prevent adding opening tag is already opened by the previous sibling
|
|
372
130
|
|
|
373
|
-
|
|
374
|
-
{
|
|
375
|
-
newNode = richText.$createHeadingNode('h4');
|
|
376
|
-
newNode.append(...children);
|
|
377
|
-
return {
|
|
378
|
-
newNode,
|
|
379
|
-
shouldDelete
|
|
380
|
-
};
|
|
381
|
-
}
|
|
131
|
+
const previousNode = getTextSibling(node, true);
|
|
382
132
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
newNode.append(...children);
|
|
387
|
-
return {
|
|
388
|
-
newNode,
|
|
389
|
-
shouldDelete
|
|
390
|
-
};
|
|
391
|
-
}
|
|
133
|
+
if (!hasFormat(previousNode, format)) {
|
|
134
|
+
output = tag + output;
|
|
135
|
+
} // Prevent adding closing tag if next sibling will do it
|
|
392
136
|
|
|
393
|
-
case 'paragraphBlockQuote':
|
|
394
|
-
{
|
|
395
|
-
newNode = richText.$createQuoteNode();
|
|
396
|
-
newNode.append(...children);
|
|
397
|
-
return {
|
|
398
|
-
newNode,
|
|
399
|
-
shouldDelete
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
137
|
|
|
403
|
-
|
|
404
|
-
{
|
|
405
|
-
createListOrMergeWithPrevious(element, children, patternMatchResults, 'ul');
|
|
406
|
-
return {
|
|
407
|
-
newNode: null,
|
|
408
|
-
shouldDelete: false
|
|
409
|
-
};
|
|
410
|
-
}
|
|
138
|
+
const nextNode = getTextSibling(node, false);
|
|
411
139
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const start = scanningContext.isAutoFormatting ? parseInt(startAsString, 10) : undefined;
|
|
419
|
-
createListOrMergeWithPrevious(element, children, patternMatchResults, 'ol', start);
|
|
420
|
-
return {
|
|
421
|
-
newNode: null,
|
|
422
|
-
shouldDelete: false
|
|
423
|
-
};
|
|
424
|
-
}
|
|
140
|
+
if (!hasFormat(nextNode, format)) {
|
|
141
|
+
output += tag;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
425
145
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (scanningContext.isAutoFormatting === false) {
|
|
430
|
-
const shouldToggle = hasPatternMatchResults(scanningContext);
|
|
431
|
-
|
|
432
|
-
if (shouldToggle) {
|
|
433
|
-
scanningContext.isWithinCodeBlock = scanningContext.isWithinCodeBlock !== true; // When toggling, always clear the code block element node.
|
|
434
|
-
|
|
435
|
-
scanningContext.currentElementNode = null;
|
|
436
|
-
return {
|
|
437
|
-
newNode: null,
|
|
438
|
-
shouldDelete: true
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (scanningContext.isWithinCodeBlock) {
|
|
443
|
-
// Create the code block and return it to the caller.
|
|
444
|
-
if (scanningContext.currentElementNode == null) {
|
|
445
|
-
const newCodeBlockNode = code.$createCodeNode();
|
|
446
|
-
newCodeBlockNode.append(...children);
|
|
447
|
-
scanningContext.currentElementNode = newCodeBlockNode;
|
|
448
|
-
return {
|
|
449
|
-
newNode: newCodeBlockNode,
|
|
450
|
-
shouldDelete: false
|
|
451
|
-
};
|
|
452
|
-
} // Build up the code block with a line break and the children.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (scanningContext.currentElementNode != null) {
|
|
456
|
-
const codeBlockNode = scanningContext.currentElementNode;
|
|
457
|
-
const lineBreakNode = lexical.$createLineBreakNode();
|
|
458
|
-
codeBlockNode.append(lineBreakNode);
|
|
459
|
-
|
|
460
|
-
if (children.length) {
|
|
461
|
-
codeBlockNode.append(lineBreakNode);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
codeBlockNode.append(...children);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return {
|
|
469
|
-
newNode: null,
|
|
470
|
-
shouldDelete: true
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (scanningContext.triggerState != null && scanningContext.triggerState.isCodeBlock) {
|
|
475
|
-
newNode = lexical.$createParagraphNode();
|
|
476
|
-
} else {
|
|
477
|
-
newNode = code.$createCodeNode();
|
|
478
|
-
const codingLanguage = patternMatchResults.regExCaptureGroups.length >= 3 ? patternMatchResults.regExCaptureGroups[2].text : null;
|
|
479
|
-
|
|
480
|
-
if (codingLanguage != null && codingLanguage.length > 0) {
|
|
481
|
-
newNode.setLanguage(codingLanguage);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
newNode.append(...children);
|
|
486
|
-
return {
|
|
487
|
-
newNode,
|
|
488
|
-
shouldDelete
|
|
489
|
-
};
|
|
490
|
-
}
|
|
146
|
+
return output;
|
|
147
|
+
} // Get next or previous text sibling a text node, including cases
|
|
148
|
+
// when it's a child of inline element (e.g. link)
|
|
491
149
|
|
|
492
|
-
case 'horizontalRule':
|
|
493
|
-
{
|
|
494
|
-
if (createHorizontalRuleNode != null) {
|
|
495
|
-
// return null for newNode. Insert the HR here.
|
|
496
|
-
const horizontalRuleNode = createHorizontalRuleNode();
|
|
497
|
-
element.insertBefore(horizontalRuleNode);
|
|
498
|
-
}
|
|
499
150
|
|
|
500
|
-
|
|
501
|
-
|
|
151
|
+
function getTextSibling(node, backward) {
|
|
152
|
+
let sibling = backward ? node.getPreviousSibling() : node.getNextSibling();
|
|
153
|
+
|
|
154
|
+
if (!sibling) {
|
|
155
|
+
const parent = node.getParentOrThrow();
|
|
156
|
+
|
|
157
|
+
if (parent.isInline()) {
|
|
158
|
+
sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling();
|
|
502
159
|
}
|
|
503
160
|
}
|
|
504
161
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
162
|
+
while (sibling) {
|
|
163
|
+
if (lexical.$isElementNode(sibling)) {
|
|
164
|
+
if (!sibling.isInline()) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
510
167
|
|
|
511
|
-
|
|
512
|
-
const listItem = list.$createListItemNode();
|
|
513
|
-
const indentMatch = patternMatchResults.regExCaptureGroups[0].text.match(/^\s*/);
|
|
514
|
-
const indent = indentMatch ? Math.floor(indentMatch[0].length / 4) : 0;
|
|
515
|
-
listItem.append(...children); // Checking if previous element is a list, and if so append
|
|
516
|
-
// new list item inside instead of creating new list
|
|
168
|
+
const descendant = backward ? sibling.getLastDescendant() : sibling.getFirstDescendant();
|
|
517
169
|
|
|
518
|
-
|
|
170
|
+
if (lexical.$isTextNode(descendant)) {
|
|
171
|
+
return descendant;
|
|
172
|
+
} else {
|
|
173
|
+
sibling = backward ? sibling.getPreviousSibling() : sibling.getNextSibling();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
519
176
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
} else {
|
|
524
|
-
const list$1 = list.$createListNode(tag, start);
|
|
525
|
-
list$1.append(listItem);
|
|
526
|
-
element.replace(list$1);
|
|
177
|
+
if (lexical.$isTextNode(sibling)) {
|
|
178
|
+
return sibling;
|
|
179
|
+
}
|
|
527
180
|
}
|
|
528
181
|
|
|
529
|
-
|
|
530
|
-
listItem.setIndent(indent);
|
|
531
|
-
}
|
|
182
|
+
return null;
|
|
532
183
|
}
|
|
533
184
|
|
|
534
|
-
function
|
|
535
|
-
|
|
536
|
-
transformTextNodeForElementNode(elementNode, scanningContext, createHorizontalRuleNode);
|
|
537
|
-
} else {
|
|
538
|
-
transformTextNodeForText(scanningContext, elementNode);
|
|
539
|
-
}
|
|
185
|
+
function hasFormat(node, format) {
|
|
186
|
+
return lexical.$isTextNode(node) && node.hasFormat(format);
|
|
540
187
|
}
|
|
541
188
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
191
|
+
*
|
|
192
|
+
* This source code is licensed under the MIT license found in the
|
|
193
|
+
* LICENSE file in the root directory of this source tree.
|
|
194
|
+
*
|
|
195
|
+
*
|
|
196
|
+
*/
|
|
197
|
+
const CODE_BLOCK_REG_EXP = /^```(\w{1,10})?\s?$/;
|
|
198
|
+
function createMarkdownImport(transformers) {
|
|
199
|
+
const byType = transformersByType(transformers);
|
|
200
|
+
const textFormatTransformersIndex = createTextFormatTransformersIndex(byType.textFormat);
|
|
201
|
+
return markdownString => {
|
|
202
|
+
const lines = markdownString.split('\n');
|
|
203
|
+
const linesLength = lines.length;
|
|
204
|
+
const root = lexical.$getRoot();
|
|
205
|
+
root.clear();
|
|
545
206
|
|
|
546
|
-
|
|
547
|
-
const
|
|
207
|
+
for (let i = 0; i < linesLength; i++) {
|
|
208
|
+
const lineText = lines[i]; // Codeblocks are processed first as anything inside such block
|
|
209
|
+
// is ignored for further processing
|
|
210
|
+
// TODO:
|
|
211
|
+
// Abstract it to be dynamic as other transformers (add multiline match option)
|
|
548
212
|
|
|
549
|
-
const
|
|
213
|
+
const [codeBlockNode, shiftedIndex] = importCodeBlock(lines, i, root);
|
|
550
214
|
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
215
|
+
if (codeBlockNode != null) {
|
|
216
|
+
i = shiftedIndex;
|
|
217
|
+
continue;
|
|
554
218
|
}
|
|
555
|
-
}
|
|
556
|
-
} // Transform the current element kind to the new element kind.
|
|
557
219
|
|
|
220
|
+
importBlocks(lineText, root, byType.element, textFormatTransformersIndex, byType.textMatch);
|
|
221
|
+
}
|
|
558
222
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
shouldDelete
|
|
562
|
-
} = getNewNodeForCriteria(scanningContext, elementNode, createHorizontalRuleNode);
|
|
563
|
-
|
|
564
|
-
if (shouldDelete) {
|
|
565
|
-
elementNode.remove();
|
|
566
|
-
} else if (newNode !== null) {
|
|
567
|
-
elementNode.replace(newNode);
|
|
568
|
-
}
|
|
223
|
+
root.selectEnd();
|
|
224
|
+
};
|
|
569
225
|
}
|
|
570
226
|
|
|
571
|
-
function
|
|
572
|
-
const
|
|
227
|
+
function importBlocks(lineText, rootNode, elementTransformers, textFormatTransformersIndex, textMatchTransformers) {
|
|
228
|
+
const textNode = lexical.$createTextNode(lineText);
|
|
229
|
+
const elementNode = lexical.$createParagraphNode();
|
|
230
|
+
elementNode.append(textNode);
|
|
231
|
+
rootNode.append(elementNode);
|
|
573
232
|
|
|
574
|
-
|
|
575
|
-
|
|
233
|
+
for (const {
|
|
234
|
+
regExp,
|
|
235
|
+
replace
|
|
236
|
+
} of elementTransformers) {
|
|
237
|
+
const match = lineText.match(regExp);
|
|
576
238
|
|
|
577
|
-
if (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (markdownCriteria.markdownFormatKind === 'link') {
|
|
583
|
-
transformTextNodeWithLink(scanningContext, parentElementNode);
|
|
239
|
+
if (match) {
|
|
240
|
+
textNode.setTextContent(lineText.slice(match[0].length));
|
|
241
|
+
replace(elementNode, [textNode], match, true);
|
|
242
|
+
break;
|
|
584
243
|
}
|
|
585
244
|
}
|
|
586
|
-
}
|
|
587
245
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const groupCount = patternMatchResults.regExCaptureGroups.length;
|
|
591
|
-
|
|
592
|
-
if (groupCount !== 7) {
|
|
593
|
-
// For BIUS and similar formats which have a pattern + text + pattern:
|
|
594
|
-
// given '*italic* ' below are the capture groups by index:
|
|
595
|
-
// 0. '*italic* '
|
|
596
|
-
// 1. '*'
|
|
597
|
-
// 2. whitespace // typically this is "".
|
|
598
|
-
// 3. 'italic'
|
|
599
|
-
// 4. whitespace // typicallly this is "".
|
|
600
|
-
// 5. '*'
|
|
601
|
-
// 6. ' '
|
|
602
|
-
return;
|
|
603
|
-
} // Remove unwanted text in reg ex pattern.
|
|
604
|
-
// Remove group 5.
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
removeTextByCaptureGroups(5, 5, scanningContext, parentElementNode); // Remove group 1.
|
|
246
|
+
importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers);
|
|
247
|
+
}
|
|
608
248
|
|
|
609
|
-
|
|
249
|
+
function importCodeBlock(lines, startLineIndex, rootNode) {
|
|
250
|
+
const openMatch = lines[startLineIndex].match(CODE_BLOCK_REG_EXP);
|
|
610
251
|
|
|
611
|
-
|
|
252
|
+
if (openMatch) {
|
|
253
|
+
let endLineIndex = startLineIndex;
|
|
254
|
+
const linesLength = lines.length;
|
|
612
255
|
|
|
613
|
-
|
|
614
|
-
|
|
256
|
+
while (++endLineIndex < linesLength) {
|
|
257
|
+
const closeMatch = lines[endLineIndex].match(CODE_BLOCK_REG_EXP);
|
|
615
258
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
// 0. '[title](url) '
|
|
625
|
-
// 1. '['
|
|
626
|
-
// 2. 'title'
|
|
627
|
-
// 3. ']('
|
|
628
|
-
// 4. 'url'
|
|
629
|
-
// 5. ')'
|
|
630
|
-
// 6. ' '
|
|
631
|
-
return;
|
|
259
|
+
if (closeMatch) {
|
|
260
|
+
const codeBlockNode = code.$createCodeNode(openMatch[1]);
|
|
261
|
+
const textNode = lexical.$createTextNode(lines.slice(startLineIndex + 1, endLineIndex).join('\n'));
|
|
262
|
+
codeBlockNode.append(textNode);
|
|
263
|
+
rootNode.append(codeBlockNode);
|
|
264
|
+
return [codeBlockNode, endLineIndex];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
632
267
|
}
|
|
633
268
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
269
|
+
return [null, startLineIndex];
|
|
270
|
+
} // Processing text content and replaces text format tags.
|
|
271
|
+
// It takes outermost tag match and its content, creates text node with
|
|
272
|
+
// format based on tag and then recursively executed over node's content
|
|
273
|
+
//
|
|
274
|
+
// E.g. for "*Hello **world**!*" string it will create text node with
|
|
275
|
+
// "Hello **world**!" content and italic format and run recursively over
|
|
276
|
+
// its content to transform "**world**" part
|
|
640
277
|
|
|
641
278
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const newSelectionForLink = createSelectionWithCaptureGroups(1, 1, false, true, scanningContext, parentElementNode);
|
|
279
|
+
function importTextFormatTransformers(textNode, textFormatTransformersIndex, textMatchTransformers) {
|
|
280
|
+
const textContent = textNode.getTextContent();
|
|
281
|
+
const match = findOutermostMatch(textContent, textFormatTransformersIndex);
|
|
646
282
|
|
|
647
|
-
if (
|
|
283
|
+
if (!match) {
|
|
284
|
+
// Once text format processing is done run text match transformers, as it
|
|
285
|
+
// only can span within single text node (unline formats that can cover multiple nodes)
|
|
286
|
+
importTextMatchTransformers(textNode, textMatchTransformers);
|
|
648
287
|
return;
|
|
649
288
|
}
|
|
650
289
|
|
|
651
|
-
|
|
652
|
-
|
|
290
|
+
let currentNode, remainderNode; // If matching full content there's no need to run splitText and can reuse existing textNode
|
|
291
|
+
// to update its content and apply format. E.g. for **_Hello_** string after applying bold
|
|
292
|
+
// format (**) it will reuse the same text node to apply italic (_)
|
|
653
293
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
return getTextNodeWithOffsetOrThrow(scanningContext).node.getParentOrThrow();
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
function getJoinedTextLength(patternMatchResults) {
|
|
663
|
-
const groupCount = patternMatchResults.regExCaptureGroups.length;
|
|
294
|
+
if (match[0] === textContent) {
|
|
295
|
+
currentNode = textNode;
|
|
296
|
+
} else {
|
|
297
|
+
const startIndex = match.index;
|
|
298
|
+
const endIndex = startIndex + match[0].length;
|
|
664
299
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
300
|
+
if (startIndex === 0) {
|
|
301
|
+
[currentNode, remainderNode] = textNode.splitText(endIndex);
|
|
302
|
+
} else {
|
|
303
|
+
[, currentNode, remainderNode] = textNode.splitText(startIndex, endIndex);
|
|
304
|
+
}
|
|
668
305
|
}
|
|
669
306
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
307
|
+
currentNode.setTextContent(match[2]);
|
|
308
|
+
const transformer = textFormatTransformersIndex.transformersByTag[match[1]];
|
|
673
309
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
case 'underline':
|
|
679
|
-
case 'strikethrough':
|
|
680
|
-
case 'code':
|
|
681
|
-
return [markdownFormatKind];
|
|
682
|
-
|
|
683
|
-
case 'strikethrough_italic_bold':
|
|
684
|
-
{
|
|
685
|
-
return ['strikethrough', 'italic', 'bold'];
|
|
310
|
+
if (transformer) {
|
|
311
|
+
for (const format of transformer.format) {
|
|
312
|
+
if (!currentNode.hasFormat(format)) {
|
|
313
|
+
currentNode.toggleFormat(format);
|
|
686
314
|
}
|
|
315
|
+
}
|
|
316
|
+
} // Recursively run over inner text if it's not inline code
|
|
687
317
|
|
|
688
|
-
case 'italic_bold':
|
|
689
|
-
{
|
|
690
|
-
return ['italic', 'bold'];
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
case 'strikethrough_italic':
|
|
694
|
-
{
|
|
695
|
-
return ['strikethrough', 'italic'];
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
case 'strikethrough_bold':
|
|
699
|
-
{
|
|
700
|
-
return ['strikethrough', 'bold'];
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
318
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const regExCaptureGroupsCount = regExCaptureGroups.length;
|
|
319
|
+
if (!currentNode.hasFormat('code')) {
|
|
320
|
+
importTextFormatTransformers(currentNode, textFormatTransformersIndex, textMatchTransformers);
|
|
321
|
+
} // Run over remaining text if any
|
|
711
322
|
|
|
712
|
-
if (anchorCaptureGroupIndex >= regExCaptureGroupsCount || focusCaptureGroupIndex >= regExCaptureGroupsCount) {
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
323
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const focusCaptureGroupDetail = regExCaptureGroups[focusCaptureGroupIndex];
|
|
719
|
-
const anchorLocation = startAtEndOfAnchor ? anchorCaptureGroupDetail.offsetInParent + anchorCaptureGroupDetail.text.length : anchorCaptureGroupDetail.offsetInParent;
|
|
720
|
-
const focusLocation = finishAtEndOfFocus ? focusCaptureGroupDetail.offsetInParent + focusCaptureGroupDetail.text.length : focusCaptureGroupDetail.offsetInParent;
|
|
721
|
-
const anchorTextNodeWithOffset = text.$findNodeWithOffsetFromJoinedText(anchorLocation, joinedTextLength, SEPARATOR_LENGTH, parentElementNode);
|
|
722
|
-
const focusTextNodeWithOffset = text.$findNodeWithOffsetFromJoinedText(focusLocation, joinedTextLength, SEPARATOR_LENGTH, parentElementNode);
|
|
723
|
-
|
|
724
|
-
if (anchorTextNodeWithOffset == null && focusTextNodeWithOffset == null && parentElementNode.getChildren().length === 0) {
|
|
725
|
-
const emptyElementSelection = lexical.$createRangeSelection();
|
|
726
|
-
emptyElementSelection.anchor.set(parentElementNode.getKey(), 0, 'element');
|
|
727
|
-
emptyElementSelection.focus.set(parentElementNode.getKey(), 0, 'element');
|
|
728
|
-
return emptyElementSelection;
|
|
324
|
+
if (remainderNode) {
|
|
325
|
+
importTextFormatTransformers(remainderNode, textFormatTransformersIndex, textMatchTransformers);
|
|
729
326
|
}
|
|
730
|
-
|
|
731
|
-
if (anchorTextNodeWithOffset == null || focusTextNodeWithOffset == null) {
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
const selection = lexical.$createRangeSelection();
|
|
736
|
-
selection.anchor.set(anchorTextNodeWithOffset.node.getKey(), anchorTextNodeWithOffset.offset, 'text');
|
|
737
|
-
selection.focus.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset, 'text');
|
|
738
|
-
return selection;
|
|
739
327
|
}
|
|
740
328
|
|
|
741
|
-
function
|
|
742
|
-
|
|
743
|
-
const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
|
|
744
|
-
const newSelection = createSelectionWithCaptureGroups(anchorCaptureGroupIndex, focusCaptureGroupIndex, false, true, scanningContext, parentElementNode);
|
|
745
|
-
|
|
746
|
-
if (newSelection != null) {
|
|
747
|
-
lexical.$setSelection(newSelection);
|
|
748
|
-
const currentSelection = lexical.$getSelection();
|
|
749
|
-
|
|
750
|
-
if (currentSelection != null && lexical.$isRangeSelection(currentSelection) && currentSelection.isCollapsed() === false) {
|
|
751
|
-
currentSelection.removeText(); // Shift all group offsets and clear out group text.
|
|
329
|
+
function importTextMatchTransformers(textNode_, textMatchTransformers) {
|
|
330
|
+
let textNode = textNode_;
|
|
752
331
|
|
|
753
|
-
|
|
754
|
-
|
|
332
|
+
mainLoop: while (textNode) {
|
|
333
|
+
for (const transformer of textMatchTransformers) {
|
|
334
|
+
const match = textNode.getTextContent().match(transformer.importRegExp);
|
|
755
335
|
|
|
756
|
-
|
|
757
|
-
|
|
336
|
+
if (!match) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
758
339
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
340
|
+
const startIndex = match.index;
|
|
341
|
+
const endIndex = startIndex + match[0].length;
|
|
342
|
+
let replaceNode;
|
|
762
343
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
344
|
+
if (startIndex === 0) {
|
|
345
|
+
[replaceNode, textNode] = textNode.splitText(endIndex);
|
|
346
|
+
} else {
|
|
347
|
+
[, replaceNode, textNode] = textNode.splitText(startIndex, endIndex);
|
|
767
348
|
}
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
349
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const regExCaptureGroupsCount = regExCaptureGroups.length;
|
|
350
|
+
transformer.replace(replaceNode, match);
|
|
351
|
+
continue mainLoop;
|
|
352
|
+
}
|
|
776
353
|
|
|
777
|
-
|
|
778
|
-
return;
|
|
354
|
+
break;
|
|
779
355
|
}
|
|
356
|
+
} // Finds first "<tag>content<tag>" match that is not nested into another tag
|
|
780
357
|
|
|
781
|
-
const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
|
|
782
|
-
const newCaptureGroupDetail = {
|
|
783
|
-
offsetInParent: captureGroupDetail.offsetInParent,
|
|
784
|
-
text
|
|
785
|
-
};
|
|
786
|
-
const newSelection = createSelectionWithCaptureGroups(captureGroupIndex, captureGroupIndex, false, false, scanningContext, parentElementNode);
|
|
787
|
-
|
|
788
|
-
if (newSelection != null) {
|
|
789
|
-
lexical.$setSelection(newSelection);
|
|
790
|
-
const currentSelection = lexical.$getSelection();
|
|
791
|
-
|
|
792
|
-
if (currentSelection != null && lexical.$isRangeSelection(currentSelection) && currentSelection.isCollapsed()) {
|
|
793
|
-
currentSelection.insertText(newCaptureGroupDetail.text); // Update the capture groups.
|
|
794
|
-
|
|
795
|
-
regExCaptureGroups.splice(captureGroupIndex, 0, newCaptureGroupDetail);
|
|
796
|
-
const textLength = newCaptureGroupDetail.text.length;
|
|
797
|
-
const newGroupCount = regExCaptureGroups.length;
|
|
798
|
-
|
|
799
|
-
for (let i = captureGroupIndex + 1; i < newGroupCount; i++) {
|
|
800
|
-
const currentCaptureGroupDetail = regExCaptureGroups[i];
|
|
801
|
-
currentCaptureGroupDetail.offsetInParent += textLength;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
358
|
|
|
807
|
-
function
|
|
808
|
-
const
|
|
809
|
-
const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
|
|
810
|
-
const regExCaptureGroupsCount = regExCaptureGroups.length;
|
|
359
|
+
function findOutermostMatch(textContent, textTransformersIndex) {
|
|
360
|
+
const openTagsMatch = textContent.match(textTransformersIndex.openTagsRegExp);
|
|
811
361
|
|
|
812
|
-
if (
|
|
813
|
-
|
|
362
|
+
if (openTagsMatch == null) {
|
|
363
|
+
return null;
|
|
814
364
|
}
|
|
815
365
|
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
366
|
+
for (const match of openTagsMatch) {
|
|
367
|
+
// Open tags reg exp might capture leading space so removing it
|
|
368
|
+
// before using match to find transformer
|
|
369
|
+
const fullMatchRegExp = textTransformersIndex.fullMatchRegExpByTag[match.replace(/^\s/, '')];
|
|
821
370
|
|
|
822
|
-
|
|
371
|
+
if (fullMatchRegExp == null) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
823
374
|
|
|
824
|
-
|
|
825
|
-
lexical.$setSelection(newSelection);
|
|
826
|
-
const currentSelection = lexical.$getSelection();
|
|
375
|
+
const fullMatch = textContent.match(fullMatchRegExp);
|
|
827
376
|
|
|
828
|
-
if (
|
|
829
|
-
|
|
830
|
-
currentSelection.formatText(formatTypes[i]);
|
|
831
|
-
}
|
|
377
|
+
if (fullMatch != null) {
|
|
378
|
+
return fullMatch;
|
|
832
379
|
}
|
|
833
380
|
}
|
|
834
|
-
} // Place caret at end of final capture group.
|
|
835
381
|
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
836
384
|
|
|
837
|
-
function
|
|
838
|
-
const
|
|
839
|
-
const
|
|
385
|
+
function createTextFormatTransformersIndex(textTransformers) {
|
|
386
|
+
const transformersByTag = {};
|
|
387
|
+
const fullMatchRegExpByTag = {};
|
|
388
|
+
const openTagsRegExp = [];
|
|
840
389
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
390
|
+
for (const transformer of textTransformers) {
|
|
391
|
+
const {
|
|
392
|
+
tag
|
|
393
|
+
} = transformer;
|
|
394
|
+
transformersByTag[tag] = transformer;
|
|
395
|
+
const tagRegExp = tag.replace(/(\*|\^)/g, '\\$1');
|
|
396
|
+
openTagsRegExp.push(tagRegExp);
|
|
397
|
+
fullMatchRegExpByTag[tag] = new RegExp(`(${tagRegExp})(?![${tagRegExp}\\s])(.*?[^${tagRegExp}\\s])${tagRegExp}(?!${tagRegExp})`);
|
|
844
398
|
}
|
|
845
399
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
400
|
+
return {
|
|
401
|
+
// Reg exp to find open tag + content + close tag
|
|
402
|
+
fullMatchRegExpByTag,
|
|
403
|
+
// Reg exp to find opening tags
|
|
404
|
+
openTagsRegExp: new RegExp('(' + openTagsRegExp.join('|') + ')', 'g'),
|
|
405
|
+
transformersByTag
|
|
406
|
+
};
|
|
852
407
|
}
|
|
853
408
|
|
|
854
409
|
/**
|
|
@@ -860,385 +415,483 @@ function selectAfterFinalCaptureGroup(scanningContext, parentElementNode) {
|
|
|
860
415
|
*
|
|
861
416
|
*/
|
|
862
417
|
|
|
863
|
-
function
|
|
864
|
-
|
|
865
|
-
|
|
418
|
+
function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers) {
|
|
419
|
+
const grandParentNode = parentNode.getParent();
|
|
420
|
+
|
|
421
|
+
if (!lexical.$isRootNode(grandParentNode) || parentNode.getFirstChild() !== anchorNode) {
|
|
422
|
+
return false;
|
|
866
423
|
}
|
|
867
424
|
|
|
868
|
-
const
|
|
425
|
+
const textContent = anchorNode.getTextContent(); // Checking for anchorOffset position to prevent any checks for cases when caret is too far
|
|
426
|
+
// from a line start to be a part of block-level markdown trigger.
|
|
427
|
+
//
|
|
428
|
+
// TODO:
|
|
429
|
+
// Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20)
|
|
430
|
+
// since otherwise it won't be a markdown shortcut, but tables are exception
|
|
869
431
|
|
|
870
|
-
if (
|
|
871
|
-
return
|
|
432
|
+
if (textContent[anchorOffset - 1] !== ' ') {
|
|
433
|
+
return false;
|
|
872
434
|
}
|
|
873
435
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
|
|
436
|
+
for (const {
|
|
437
|
+
regExp,
|
|
438
|
+
replace
|
|
439
|
+
} of elementTransformers) {
|
|
440
|
+
const match = textContent.match(regExp);
|
|
879
441
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
442
|
+
if (match && match[0].length === anchorOffset) {
|
|
443
|
+
const nextSiblings = anchorNode.getNextSiblings();
|
|
444
|
+
const [leadingNode, remainderNode] = anchorNode.splitText(anchorOffset);
|
|
445
|
+
leadingNode.remove();
|
|
446
|
+
const siblings = remainderNode ? [remainderNode, ...nextSiblings] : nextSiblings;
|
|
447
|
+
replace(parentNode, siblings, match, false);
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return false;
|
|
887
453
|
}
|
|
888
454
|
|
|
889
|
-
function
|
|
890
|
-
|
|
891
|
-
const
|
|
455
|
+
function runTextMatchTransformers(anchorNode, anchorOffset, transformersByTrigger) {
|
|
456
|
+
let textContent = anchorNode.getTextContent();
|
|
457
|
+
const lastChar = textContent[anchorOffset - 1];
|
|
458
|
+
const transformers = transformersByTrigger[lastChar];
|
|
892
459
|
|
|
893
|
-
|
|
894
|
-
|
|
460
|
+
if (transformers == null) {
|
|
461
|
+
return false;
|
|
462
|
+
} // If typing in the middle of content, remove the tail to do
|
|
463
|
+
// reg exp match up to a string end (caret position)
|
|
895
464
|
|
|
896
|
-
if (currentTriggerState != null && currentTriggerState.isCodeBlock === false || markdownCriteria.markdownFormatKind === 'paragraphCodeBlock') {
|
|
897
|
-
const patternMatchResults = getPatternMatchResultsForCriteria(markdownCriteria, scanningContext, getParentElementNodeOrThrow(scanningContext));
|
|
898
465
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
markdownCriteria,
|
|
902
|
-
patternMatchResults
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
}
|
|
466
|
+
if (anchorOffset < textContent.length) {
|
|
467
|
+
textContent = textContent.slice(0, anchorOffset);
|
|
906
468
|
}
|
|
907
469
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
patternMatchResults: null
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function findScanningContextWithValidMatch(editor, currentTriggerState) {
|
|
915
|
-
let scanningContext = null;
|
|
916
|
-
editor.getEditorState().read(() => {
|
|
917
|
-
const textNodeWithOffset = getTextNodeForAutoFormatting(lexical.$getSelection());
|
|
918
|
-
|
|
919
|
-
if (textNodeWithOffset === null) {
|
|
920
|
-
return;
|
|
921
|
-
} // Please see the declaration of ScanningContext for a detailed explanation.
|
|
470
|
+
for (const transformer of transformers) {
|
|
471
|
+
const match = textContent.match(transformer.regExp);
|
|
922
472
|
|
|
473
|
+
if (match === null) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
923
476
|
|
|
924
|
-
const
|
|
925
|
-
const
|
|
926
|
-
|
|
477
|
+
const startIndex = match.index;
|
|
478
|
+
const endIndex = startIndex + match[0].length;
|
|
479
|
+
let replaceNode;
|
|
927
480
|
|
|
928
|
-
if (
|
|
929
|
-
|
|
481
|
+
if (startIndex === 0) {
|
|
482
|
+
[replaceNode] = anchorNode.splitText(endIndex);
|
|
483
|
+
} else {
|
|
484
|
+
[, replaceNode] = anchorNode.splitText(startIndex, endIndex);
|
|
930
485
|
}
|
|
931
486
|
|
|
932
|
-
|
|
487
|
+
replaceNode.selectNext();
|
|
488
|
+
transformer.replace(replaceNode, match);
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
933
491
|
|
|
934
|
-
|
|
935
|
-
scanningContext.patternMatchResults = criteriaWithPatternMatchResults.patternMatchResults;
|
|
936
|
-
});
|
|
937
|
-
return scanningContext;
|
|
492
|
+
return false;
|
|
938
493
|
}
|
|
939
494
|
|
|
940
|
-
function
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
495
|
+
function runTextFormatTransformers(editor, anchorNode, anchorOffset, textFormatTransformers) {
|
|
496
|
+
const textContent = anchorNode.getTextContent();
|
|
497
|
+
const closeTagEndIndex = anchorOffset - 1;
|
|
498
|
+
const closeChar = textContent[closeTagEndIndex]; // Quick check if we're possibly at the end of inline markdown style
|
|
944
499
|
|
|
945
|
-
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
500
|
+
const matchers = textFormatTransformers[closeChar];
|
|
948
501
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const isParentAListItemNode = list.$isListItemNode(parentNode);
|
|
952
|
-
const hasParentNode = parentNode !== null;
|
|
953
|
-
criteria = {
|
|
954
|
-
anchorOffset: selection.anchor.offset,
|
|
955
|
-
hasParentNode,
|
|
956
|
-
isCodeBlock: code.$isCodeNode(node),
|
|
957
|
-
isParentAListItemNode,
|
|
958
|
-
isSelectionCollapsed: true,
|
|
959
|
-
isSimpleText: lexical.$isTextNode(node) && node.isSimpleText(),
|
|
960
|
-
nodeKey: node.getKey(),
|
|
961
|
-
textContent: node.getTextContent()
|
|
962
|
-
};
|
|
963
|
-
});
|
|
964
|
-
return criteria;
|
|
965
|
-
}
|
|
966
|
-
function findScanningContext(editor, currentTriggerState, priorTriggerState) {
|
|
967
|
-
if (currentTriggerState == null || priorTriggerState == null) {
|
|
968
|
-
return null;
|
|
502
|
+
if (!matchers) {
|
|
503
|
+
return false;
|
|
969
504
|
}
|
|
970
505
|
|
|
971
|
-
const
|
|
972
|
-
|
|
506
|
+
for (const matcher of matchers) {
|
|
507
|
+
const {
|
|
508
|
+
tag
|
|
509
|
+
} = matcher;
|
|
510
|
+
const tagLength = tag.length;
|
|
511
|
+
const closeTagStartIndex = closeTagEndIndex - tagLength + 1; // If tag is not single char check if rest of it matches with text content
|
|
973
512
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
513
|
+
if (tagLength > 1) {
|
|
514
|
+
if (!isEqualSubString(textContent, closeTagStartIndex, tag, 0, tagLength)) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
} // Space before closing tag cancels inline markdown
|
|
978
518
|
|
|
979
|
-
const triggerStringLength = triggerString.length;
|
|
980
|
-
const currentTextContentLength = currentTriggerState.textContent.length;
|
|
981
|
-
const triggerOffset = currentTriggerState.anchorOffset - triggerStringLength; // Todo: these checks help w/ performance, yet we can do more.
|
|
982
|
-
// We might consider looking for ** + space or __ + space and so on to boost performance
|
|
983
|
-
// even further. Make sure the patter is driven from the trigger state type.
|
|
984
519
|
|
|
985
|
-
if (
|
|
986
|
-
|
|
987
|
-
return null;
|
|
520
|
+
if (textContent[closeTagStartIndex - 1] === ' ') {
|
|
521
|
+
continue;
|
|
988
522
|
}
|
|
989
|
-
}
|
|
990
523
|
|
|
991
|
-
|
|
992
|
-
|
|
524
|
+
const closeNode = anchorNode;
|
|
525
|
+
let openNode = closeNode;
|
|
526
|
+
let openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag); // Go through text node siblings and search for opening tag
|
|
527
|
+
// if haven't found it within the same text node as closing tag
|
|
993
528
|
|
|
994
|
-
|
|
995
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
996
|
-
*
|
|
997
|
-
* This source code is licensed under the MIT license found in the
|
|
998
|
-
* LICENSE file in the root directory of this source tree.
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*/
|
|
1002
|
-
function convertStringToLexical(text, editor) {
|
|
1003
|
-
if (!text.length) {
|
|
1004
|
-
return null;
|
|
1005
|
-
}
|
|
529
|
+
let sibling = openNode;
|
|
1006
530
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
531
|
+
while (openTagStartIndex < 0 && (sibling = sibling.getPreviousSibling())) {
|
|
532
|
+
if (lexical.$isLineBreakNode(sibling)) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
1010
535
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
536
|
+
if (lexical.$isTextNode(sibling)) {
|
|
537
|
+
const siblingTextContent = sibling.getTextContent();
|
|
538
|
+
openNode = sibling;
|
|
539
|
+
openTagStartIndex = getOpenTagStartIndex(siblingTextContent, siblingTextContent.length, tag);
|
|
540
|
+
}
|
|
541
|
+
} // Opening tag is not found
|
|
1018
542
|
|
|
1019
|
-
if (nodes.length) {
|
|
1020
|
-
const root = lexical.$getRoot();
|
|
1021
|
-
root.clear();
|
|
1022
|
-
root.append(...nodes);
|
|
1023
|
-
return root;
|
|
1024
|
-
}
|
|
1025
543
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
// Please see the declaration of ScanningContext for a detailed explanation.
|
|
1030
|
-
const scanningContext = getInitialScanningContext(editor, false, null, null);
|
|
1031
|
-
const root = lexical.$getRoot();
|
|
1032
|
-
let done = false;
|
|
1033
|
-
let startIndex = 0; // Handle the paragraph level markdown.
|
|
544
|
+
if (openTagStartIndex < 0) {
|
|
545
|
+
continue;
|
|
546
|
+
} // No content between opening and closing tag
|
|
1034
547
|
|
|
1035
|
-
while (!done) {
|
|
1036
|
-
done = true;
|
|
1037
|
-
const elementNodes = root.getChildren();
|
|
1038
|
-
const countOfElementNodes = elementNodes.length;
|
|
1039
548
|
|
|
1040
|
-
|
|
1041
|
-
|
|
549
|
+
if (openNode === closeNode && openTagStartIndex + tagLength === closeTagStartIndex) {
|
|
550
|
+
continue;
|
|
551
|
+
} // Checking longer tags for repeating chars (e.g. *** vs **)
|
|
1042
552
|
|
|
1043
|
-
if (lexical.$isElementNode(elementNode)) {
|
|
1044
|
-
convertParagraphLevelMarkdown(scanningContext, elementNode, createHorizontalRuleNode);
|
|
1045
|
-
} // Reset the scanning information that relates to the particular element node.
|
|
1046
553
|
|
|
554
|
+
const prevOpenNodeText = openNode.getTextContent();
|
|
1047
555
|
|
|
1048
|
-
|
|
556
|
+
if (openTagStartIndex > 0 && prevOpenNodeText[openTagStartIndex - 1] === closeChar) {
|
|
557
|
+
continue;
|
|
558
|
+
} // Clean text from opening and closing tags (starting from closing tag
|
|
559
|
+
// to prevent any offset shifts if we start from opening one)
|
|
1049
560
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
561
|
+
|
|
562
|
+
const prevCloseNodeText = closeNode.getTextContent();
|
|
563
|
+
const closeNodeText = prevCloseNodeText.slice(0, closeTagStartIndex) + prevCloseNodeText.slice(closeTagEndIndex + 1);
|
|
564
|
+
closeNode.setTextContent(closeNodeText);
|
|
565
|
+
const openNodeText = openNode === closeNode ? closeNodeText : prevOpenNodeText;
|
|
566
|
+
openNode.setTextContent(openNodeText.slice(0, openTagStartIndex) + openNodeText.slice(openTagStartIndex + tagLength));
|
|
567
|
+
const nextSelection = lexical.$createRangeSelection();
|
|
568
|
+
lexical.$setSelection(nextSelection); // Adjust offset based on deleted chars
|
|
569
|
+
|
|
570
|
+
const newOffset = closeTagEndIndex - tagLength * (openNode === closeNode ? 2 : 1) + 1;
|
|
571
|
+
nextSelection.anchor.set(openNode.__key, openTagStartIndex, 'text');
|
|
572
|
+
nextSelection.focus.set(closeNode.__key, newOffset, 'text'); // Apply formatting to selected text
|
|
573
|
+
|
|
574
|
+
for (const format of matcher.format) {
|
|
575
|
+
if (!nextSelection.hasFormat(format)) {
|
|
576
|
+
nextSelection.formatText(format);
|
|
1055
577
|
}
|
|
1056
|
-
}
|
|
1057
|
-
} // while
|
|
578
|
+
} // Collapse selection up to the focus point
|
|
1058
579
|
|
|
1059
580
|
|
|
1060
|
-
|
|
1061
|
-
startIndex = 0; // Handle the text level markdown.
|
|
581
|
+
nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type); // Remove formatting from collapsed selection
|
|
1062
582
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
583
|
+
for (const format of matcher.format) {
|
|
584
|
+
if (nextSelection.hasFormat(format)) {
|
|
585
|
+
nextSelection.toggleFormat(format);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
1067
588
|
|
|
1068
|
-
|
|
1069
|
-
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
1070
591
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
} // Reset the scanning information that relates to the particular element node.
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
1074
594
|
|
|
595
|
+
function getOpenTagStartIndex(string, maxIndex, tag) {
|
|
596
|
+
const tagLength = tag.length;
|
|
1075
597
|
|
|
1076
|
-
|
|
598
|
+
for (let i = maxIndex; i >= tagLength; i--) {
|
|
599
|
+
const startIndex = i - tagLength;
|
|
600
|
+
|
|
601
|
+
if (isEqualSubString(string, startIndex, tag, 0, tagLength) && // Space after opening tag cancels transformation
|
|
602
|
+
string[startIndex + tagLength] !== ' ') {
|
|
603
|
+
return startIndex;
|
|
1077
604
|
}
|
|
1078
|
-
}
|
|
605
|
+
}
|
|
1079
606
|
|
|
607
|
+
return -1;
|
|
1080
608
|
}
|
|
1081
609
|
|
|
1082
|
-
function
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const firstChildIsTextNode = lexical.$isTextNode(firstChild); // Handle conversion to code block.
|
|
1089
|
-
|
|
1090
|
-
if (scanningContext.isWithinCodeBlock === true) {
|
|
1091
|
-
if (firstChild != null && firstChildIsTextNode) {
|
|
1092
|
-
// Test if we encounter ending code block.
|
|
1093
|
-
scanningContext.textNodeWithOffset = {
|
|
1094
|
-
node: firstChild,
|
|
1095
|
-
offset: 0
|
|
1096
|
-
};
|
|
1097
|
-
const patternMatchResults = getPatternMatchResultsForCodeBlock(scanningContext, textContent);
|
|
1098
|
-
|
|
1099
|
-
if (patternMatchResults != null) {
|
|
1100
|
-
// Toggle transform to or from code block.
|
|
1101
|
-
scanningContext.patternMatchResults = patternMatchResults;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
610
|
+
function isEqualSubString(stringA, aStart, stringB, bStart, length) {
|
|
611
|
+
for (let i = 0; i < length; i++) {
|
|
612
|
+
if (stringA[aStart + i] !== stringB[bStart + i]) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
1104
616
|
|
|
1105
|
-
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
1106
619
|
|
|
1107
|
-
|
|
620
|
+
function registerMarkdownShortcuts(editor, transformers = TRANSFORMERS) {
|
|
621
|
+
const byType = transformersByType(transformers);
|
|
622
|
+
const textFormatTransformersIndex = indexBy(byType.textFormat, ({
|
|
623
|
+
tag
|
|
624
|
+
}) => tag[tag.length - 1]);
|
|
625
|
+
const textMatchTransformersIndex = indexBy(byType.textMatch, ({
|
|
626
|
+
trigger
|
|
627
|
+
}) => trigger);
|
|
628
|
+
|
|
629
|
+
const transform = (parentNode, anchorNode, anchorOffset) => {
|
|
630
|
+
if (runElementTransformers(parentNode, anchorNode, anchorOffset, byType.element)) {
|
|
1108
631
|
return;
|
|
1109
632
|
}
|
|
1110
633
|
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
scanningContext.joinedText = paragraphNode.getTextContent();
|
|
634
|
+
if (runTextMatchTransformers(anchorNode, anchorOffset, textMatchTransformersIndex)) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
1115
637
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
638
|
+
runTextFormatTransformers(editor, anchorNode, anchorOffset, textFormatTransformersIndex);
|
|
639
|
+
};
|
|
1119
640
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
641
|
+
return editor.registerUpdateListener(({
|
|
642
|
+
tags,
|
|
643
|
+
dirtyLeaves,
|
|
644
|
+
editorState,
|
|
645
|
+
prevEditorState
|
|
646
|
+
}) => {
|
|
647
|
+
// Ignore updates from undo/redo (as changes already calculated)
|
|
648
|
+
if (tags.has('historic')) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
1124
651
|
|
|
1125
|
-
|
|
1126
|
-
|
|
652
|
+
const selection = editorState.read(lexical.$getSelection);
|
|
653
|
+
const prevSelection = prevEditorState.read(lexical.$getSelection);
|
|
1127
654
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
655
|
+
if (!lexical.$isRangeSelection(prevSelection) || !lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
1131
658
|
|
|
1132
|
-
|
|
659
|
+
const anchorKey = selection.anchor.key;
|
|
660
|
+
const anchorOffset = selection.anchor.offset;
|
|
1133
661
|
|
|
1134
|
-
|
|
1135
|
-
scanningContext.markdownCriteria = criteria;
|
|
1136
|
-
scanningContext.patternMatchResults = patternMatchResults; // Perform text transformation here.
|
|
662
|
+
const anchorNode = editorState._nodeMap.get(anchorKey);
|
|
1137
663
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
664
|
+
if (!lexical.$isTextNode(anchorNode) || !dirtyLeaves.has(anchorKey) || anchorOffset !== 1 && anchorOffset !== prevSelection.anchor.offset + 1) {
|
|
665
|
+
return;
|
|
1142
666
|
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function convertTextLevelMarkdown(scanningContext, elementNode, createHorizontalRuleNode) {
|
|
1147
|
-
const firstChild = elementNode.getFirstChild();
|
|
1148
|
-
|
|
1149
|
-
if (lexical.$isTextNode(firstChild)) {
|
|
1150
|
-
// This function will convert all text nodes within the elementNode.
|
|
1151
|
-
convertMarkdownForTextCriteria(scanningContext, elementNode, createHorizontalRuleNode);
|
|
1152
|
-
return;
|
|
1153
|
-
} // Handle the case where the elementNode has child elementNodes like lists.
|
|
1154
|
-
// Since we started at a text import, we don't need to worry about anything but textNodes.
|
|
1155
667
|
|
|
668
|
+
editor.update(() => {
|
|
669
|
+
// Markdown is not available inside code
|
|
670
|
+
if (anchorNode.hasFormat('code')) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
1156
673
|
|
|
1157
|
-
|
|
1158
|
-
const countOfChildren = children.length;
|
|
674
|
+
const parentNode = anchorNode.getParent();
|
|
1159
675
|
|
|
1160
|
-
|
|
1161
|
-
|
|
676
|
+
if (parentNode === null || code.$isCodeNode(parentNode)) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
1162
679
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
680
|
+
transform(parentNode, anchorNode, selection.anchor.offset);
|
|
681
|
+
});
|
|
682
|
+
});
|
|
1168
683
|
}
|
|
1169
684
|
|
|
1170
|
-
|
|
1171
|
-
|
|
685
|
+
/**
|
|
686
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
687
|
+
*
|
|
688
|
+
* This source code is licensed under the MIT license found in the
|
|
689
|
+
* LICENSE file in the root directory of this source tree.
|
|
690
|
+
*
|
|
691
|
+
*
|
|
692
|
+
*/
|
|
1172
693
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
694
|
+
const replaceWithBlock = createNode => {
|
|
695
|
+
return (parentNode, children, match) => {
|
|
696
|
+
const node = createNode(match);
|
|
697
|
+
node.append(...children);
|
|
698
|
+
parentNode.replace(node);
|
|
699
|
+
node.select(0, 0);
|
|
700
|
+
};
|
|
701
|
+
}; // Amount of spaces that define indentation level
|
|
702
|
+
// TODO: should be an option
|
|
1178
703
|
|
|
1179
|
-
while (!done) {
|
|
1180
|
-
done = true;
|
|
1181
704
|
|
|
1182
|
-
|
|
1183
|
-
const criteria = allCriteria[i];
|
|
705
|
+
const LIST_INDENT_SIZE = 4;
|
|
1184
706
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
707
|
+
const listReplace = listType => {
|
|
708
|
+
return (parentNode, children, match) => {
|
|
709
|
+
const previousNode = parentNode.getPreviousSibling();
|
|
710
|
+
const listItem = list.$createListItemNode(listType === 'check' ? match[3] === 'x' : undefined);
|
|
1188
711
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
712
|
+
if (list.$isListNode(previousNode) && previousNode.getListType() === listType) {
|
|
713
|
+
previousNode.append(listItem);
|
|
714
|
+
parentNode.remove();
|
|
715
|
+
} else {
|
|
716
|
+
const list$1 = list.$createListNode(listType, listType === 'number' ? Number(match[2]) : undefined);
|
|
717
|
+
list$1.append(listItem);
|
|
718
|
+
parentNode.replace(list$1);
|
|
719
|
+
}
|
|
1193
720
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
721
|
+
listItem.append(...children);
|
|
722
|
+
listItem.select(0, 0);
|
|
723
|
+
const indent = Math.floor(match[1].length / LIST_INDENT_SIZE);
|
|
1199
724
|
|
|
1200
|
-
|
|
725
|
+
if (indent) {
|
|
726
|
+
listItem.setIndent(indent);
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
};
|
|
1201
730
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
731
|
+
const listExport = (listNode, exportChildren, depth) => {
|
|
732
|
+
const output = [];
|
|
733
|
+
const children = listNode.getChildren();
|
|
734
|
+
let index = 0;
|
|
1205
735
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
736
|
+
for (const listItemNode of children) {
|
|
737
|
+
if (list.$isListItemNode(listItemNode)) {
|
|
738
|
+
if (listItemNode.getChildrenSize() === 1) {
|
|
739
|
+
const firstChild = listItemNode.getFirstChild();
|
|
1209
740
|
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
1212
|
-
|
|
741
|
+
if (list.$isListNode(firstChild)) {
|
|
742
|
+
output.push(listExport(firstChild, exportChildren, depth + 1));
|
|
743
|
+
continue;
|
|
1213
744
|
}
|
|
745
|
+
}
|
|
1214
746
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
747
|
+
const indent = ' '.repeat(depth * LIST_INDENT_SIZE);
|
|
748
|
+
const listType = listNode.getListType();
|
|
749
|
+
const prefix = listType === 'number' ? `${listNode.getStart() + index}. ` : listType === 'check' ? `- [${listItemNode.getChecked() ? 'x' : ' '}] ` : '- ';
|
|
750
|
+
output.push(indent + prefix + exportChildren(listItemNode));
|
|
751
|
+
index++;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
1219
754
|
|
|
755
|
+
return output.join('\n');
|
|
756
|
+
};
|
|
1220
757
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
}
|
|
758
|
+
const HEADING = {
|
|
759
|
+
export: (node, exportChildren) => {
|
|
760
|
+
if (!richText.$isHeadingNode(node)) {
|
|
761
|
+
return null;
|
|
1226
762
|
}
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
763
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
764
|
+
const level = Number(node.getTag().slice(1));
|
|
765
|
+
return '#'.repeat(level) + ' ' + exportChildren(node);
|
|
766
|
+
},
|
|
767
|
+
regExp: /^(#{1,6})\s/,
|
|
768
|
+
replace: replaceWithBlock(match => {
|
|
769
|
+
// $FlowFixMe[incompatible-cast]
|
|
770
|
+
const tag = 'h' + match[1].length;
|
|
771
|
+
return richText.$createHeadingNode(tag);
|
|
772
|
+
}),
|
|
773
|
+
type: 'element'
|
|
774
|
+
};
|
|
775
|
+
const QUOTE = {
|
|
776
|
+
export: (node, exportChildren) => {
|
|
777
|
+
return richText.$isQuoteNode(node) ? '> ' + exportChildren(node) : null;
|
|
778
|
+
},
|
|
779
|
+
regExp: /^>\s/,
|
|
780
|
+
replace: replaceWithBlock(() => richText.$createQuoteNode()),
|
|
781
|
+
type: 'element'
|
|
782
|
+
};
|
|
783
|
+
const CODE = {
|
|
784
|
+
export: node => {
|
|
785
|
+
if (!code.$isCodeNode(node)) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
1233
788
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
789
|
+
const textContent = node.getTextContent();
|
|
790
|
+
return '```' + (node.getLanguage() || '') + (textContent ? '\n' + textContent : '') + '\n' + '```';
|
|
791
|
+
},
|
|
792
|
+
regExp: /^```(\w{1,10})?\s/,
|
|
793
|
+
replace: replaceWithBlock(match => {
|
|
794
|
+
return code.$createCodeNode(match ? match[1] : undefined);
|
|
795
|
+
}),
|
|
796
|
+
type: 'element'
|
|
797
|
+
};
|
|
798
|
+
const UNORDERED_LIST = {
|
|
799
|
+
export: (node, exportChildren) => {
|
|
800
|
+
return list.$isListNode(node) ? listExport(node, exportChildren, 0) : null;
|
|
801
|
+
},
|
|
802
|
+
regExp: /^(\s*)[-*+]\s/,
|
|
803
|
+
replace: listReplace('bullet'),
|
|
804
|
+
type: 'element'
|
|
805
|
+
};
|
|
806
|
+
const CHECK_LIST = {
|
|
807
|
+
export: (node, exportChildren) => {
|
|
808
|
+
return list.$isListNode(node) ? listExport(node, exportChildren, 0) : null;
|
|
809
|
+
},
|
|
810
|
+
regExp: /^(\s*)(?:-\s)?\s?(\[(\s|x)?\])\s/i,
|
|
811
|
+
replace: listReplace('check'),
|
|
812
|
+
type: 'element'
|
|
813
|
+
};
|
|
814
|
+
const ORDERED_LIST = {
|
|
815
|
+
export: (node, exportChildren) => {
|
|
816
|
+
return list.$isListNode(node) ? listExport(node, exportChildren, 0) : null;
|
|
817
|
+
},
|
|
818
|
+
regExp: /^(\s*)(\d{1,})\.\s/,
|
|
819
|
+
replace: listReplace('number'),
|
|
820
|
+
type: 'element'
|
|
821
|
+
};
|
|
822
|
+
const INLINE_CODE = {
|
|
823
|
+
format: ['code'],
|
|
824
|
+
tag: '`',
|
|
825
|
+
type: 'text-format'
|
|
826
|
+
};
|
|
827
|
+
const BOLD_ITALIC_STAR = {
|
|
828
|
+
format: ['bold', 'italic'],
|
|
829
|
+
tag: '***',
|
|
830
|
+
type: 'text-format'
|
|
831
|
+
};
|
|
832
|
+
const BOLD_ITALIC_UNDERSCORE = {
|
|
833
|
+
format: ['bold', 'italic'],
|
|
834
|
+
tag: '___',
|
|
835
|
+
type: 'text-format'
|
|
836
|
+
};
|
|
837
|
+
const BOLD_STAR = {
|
|
838
|
+
format: ['bold'],
|
|
839
|
+
tag: '**',
|
|
840
|
+
type: 'text-format'
|
|
841
|
+
};
|
|
842
|
+
const BOLD_UNDERSCORE = {
|
|
843
|
+
format: ['bold'],
|
|
844
|
+
tag: '__',
|
|
845
|
+
type: 'text-format'
|
|
846
|
+
};
|
|
847
|
+
const STRIKETHROUGH = {
|
|
848
|
+
format: ['strikethrough'],
|
|
849
|
+
tag: '~~',
|
|
850
|
+
type: 'text-format'
|
|
851
|
+
};
|
|
852
|
+
const ITALIC_STAR = {
|
|
853
|
+
format: ['italic'],
|
|
854
|
+
tag: '*',
|
|
855
|
+
type: 'text-format'
|
|
856
|
+
};
|
|
857
|
+
const ITALIC_UNDERSCORE = {
|
|
858
|
+
format: ['italic'],
|
|
859
|
+
tag: '_',
|
|
860
|
+
type: 'text-format'
|
|
861
|
+
}; // Order of text transformers matters:
|
|
862
|
+
//
|
|
863
|
+
// - code should go first as it prevents any transformations inside
|
|
864
|
+
// - then longer tags match (e.g. ** or __ should go before * or _)
|
|
865
|
+
|
|
866
|
+
const LINK = {
|
|
867
|
+
export: (node, exportChildren, exportFormat) => {
|
|
868
|
+
if (!link.$isLinkNode(node)) {
|
|
869
|
+
return null;
|
|
1237
870
|
}
|
|
1238
|
-
}
|
|
1239
871
|
|
|
1240
|
-
|
|
1241
|
-
|
|
872
|
+
const linkContent = `[${node.getTextContent()}](${node.getURL()})`;
|
|
873
|
+
const firstChild = node.getFirstChild(); // Add text styles only if link has single text node inside. If it's more
|
|
874
|
+
// then one we ignore it as markdown does not support nested styles for links
|
|
875
|
+
|
|
876
|
+
if (node.getChildrenSize() === 1 && lexical.$isTextNode(firstChild)) {
|
|
877
|
+
return exportFormat(firstChild, linkContent);
|
|
878
|
+
} else {
|
|
879
|
+
return linkContent;
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
importRegExp: /(?:\[([^[]+)\])(?:\(([^(]+)\))/,
|
|
883
|
+
regExp: /(?:\[([^[]+)\])(?:\(([^(]+)\))$/,
|
|
884
|
+
replace: (textNode, match) => {
|
|
885
|
+
const [, linkText, linkUrl] = match;
|
|
886
|
+
const linkNode = link.$createLinkNode(linkUrl);
|
|
887
|
+
const linkTextNode = lexical.$createTextNode(linkText);
|
|
888
|
+
linkTextNode.setFormat(textNode.getFormat());
|
|
889
|
+
linkNode.append(linkTextNode);
|
|
890
|
+
textNode.replace(linkNode);
|
|
891
|
+
},
|
|
892
|
+
trigger: ')',
|
|
893
|
+
type: 'text-match'
|
|
894
|
+
};
|
|
1242
895
|
|
|
1243
896
|
/**
|
|
1244
897
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -1248,35 +901,44 @@ function getLastTextNodeInElementNode(elementNode) {
|
|
|
1248
901
|
*
|
|
1249
902
|
*
|
|
1250
903
|
*/
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
// However, given "#A B", where the user delets "A" should not.
|
|
1256
|
-
let priorTriggerState = null;
|
|
1257
|
-
return editor.registerUpdateListener(({
|
|
1258
|
-
tags
|
|
1259
|
-
}) => {
|
|
1260
|
-
// Examine historic so that we are not running autoformatting within markdown.
|
|
1261
|
-
if (tags.has('historic') === false) {
|
|
1262
|
-
const currentTriggerState = getTriggerState(editor.getEditorState());
|
|
1263
|
-
const scanningContext = currentTriggerState == null ? null : findScanningContext(editor, currentTriggerState, priorTriggerState);
|
|
904
|
+
const ELEMENT_TRANSFORMERS = [HEADING, QUOTE, CODE, UNORDERED_LIST, ORDERED_LIST]; // Order of text format transformers matters:
|
|
905
|
+
//
|
|
906
|
+
// - code should go first as it prevents any transformations inside
|
|
907
|
+
// - then longer tags match (e.g. ** or __ should go before * or _)
|
|
1264
908
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
909
|
+
const TEXT_FORMAT_TRANSFORMERS = [INLINE_CODE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH];
|
|
910
|
+
const TEXT_MATCH_TRANSFORMERS = [LINK];
|
|
911
|
+
const TRANSFORMERS = [...ELEMENT_TRANSFORMERS, ...TEXT_FORMAT_TRANSFORMERS, ...TEXT_MATCH_TRANSFORMERS];
|
|
1268
912
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
});
|
|
913
|
+
function $convertFromMarkdownString(markdown, transformers = TRANSFORMERS) {
|
|
914
|
+
const importMarkdown = createMarkdownImport(transformers);
|
|
915
|
+
return importMarkdown(markdown);
|
|
1274
916
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
917
|
+
|
|
918
|
+
function $convertToMarkdownString(transformers = TRANSFORMERS) {
|
|
919
|
+
const exportMarkdown = createMarkdownExport(transformers);
|
|
920
|
+
return exportMarkdown();
|
|
1279
921
|
}
|
|
1280
922
|
|
|
1281
923
|
exports.$convertFromMarkdownString = $convertFromMarkdownString;
|
|
924
|
+
exports.$convertToMarkdownString = $convertToMarkdownString;
|
|
925
|
+
exports.BOLD_ITALIC_STAR = BOLD_ITALIC_STAR;
|
|
926
|
+
exports.BOLD_ITALIC_UNDERSCORE = BOLD_ITALIC_UNDERSCORE;
|
|
927
|
+
exports.BOLD_STAR = BOLD_STAR;
|
|
928
|
+
exports.BOLD_UNDERSCORE = BOLD_UNDERSCORE;
|
|
929
|
+
exports.CHECK_LIST = CHECK_LIST;
|
|
930
|
+
exports.CODE = CODE;
|
|
931
|
+
exports.ELEMENT_TRANSFORMERS = ELEMENT_TRANSFORMERS;
|
|
932
|
+
exports.HEADING = HEADING;
|
|
933
|
+
exports.INLINE_CODE = INLINE_CODE;
|
|
934
|
+
exports.ITALIC_STAR = ITALIC_STAR;
|
|
935
|
+
exports.ITALIC_UNDERSCORE = ITALIC_UNDERSCORE;
|
|
936
|
+
exports.LINK = LINK;
|
|
937
|
+
exports.ORDERED_LIST = ORDERED_LIST;
|
|
938
|
+
exports.QUOTE = QUOTE;
|
|
939
|
+
exports.STRIKETHROUGH = STRIKETHROUGH;
|
|
940
|
+
exports.TEXT_FORMAT_TRANSFORMERS = TEXT_FORMAT_TRANSFORMERS;
|
|
941
|
+
exports.TEXT_MATCH_TRANSFORMERS = TEXT_MATCH_TRANSFORMERS;
|
|
942
|
+
exports.TRANSFORMERS = TRANSFORMERS;
|
|
943
|
+
exports.UNORDERED_LIST = UNORDERED_LIST;
|
|
1282
944
|
exports.registerMarkdownShortcuts = registerMarkdownShortcuts;
|