@lexical/markdown 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Meta Platforms, Inc. and affiliates.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
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
+ */
8
+
9
+ import type {DecoratorNode, LexicalEditor} from 'lexical';
10
+
11
+ export function registerMarkdownShortcuts<T>(
12
+ editor: LexicalEditor,
13
+ createHorizontalRuleNode: () => DecoratorNode<T>,
14
+ ): () => void;
15
+ export function $convertFromMarkdownString(
16
+ markdownString: string,
17
+ editor: LexicalEditor,
18
+ ): void;
@@ -0,0 +1,893 @@
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
+ 'use strict';
8
+
9
+ var lexical = require('lexical');
10
+ var code = require('@lexical/code');
11
+ var list = require('@lexical/list');
12
+ var link = require('@lexical/link');
13
+ var richText = require('@lexical/rich-text');
14
+ var text = require('@lexical/text');
15
+
16
+ /**
17
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
18
+ *
19
+ * This source code is licensed under the MIT license found in the
20
+ * LICENSE file in the root directory of this source tree.
21
+ *
22
+ *
23
+ */
24
+ /*
25
+ How to add a new syntax to capture and transform.
26
+ 1. Create a new enumeration by adding to AutoFormatKind.
27
+ 2. Add a new criteria with a regEx pattern. See markdownStrikethrough as an example.
28
+ 3. Add your block criteria (e.g. '# ') to allMarkdownCriteria or
29
+ your text criteria (e.g. *MyItalic*) to allMarkdownCriteriaForTextNodes.
30
+ 4. Add your Lexical block specific transforming code here: transformTextNodeForText.
31
+ Add your Lexical text specific transforming code here: transformTextNodeForText.
32
+ */
33
+ // The trigger state helps to capture EditorState information
34
+ // from the prior and current EditorState.
35
+ // This is then used to determined if an auto format has been triggered.
36
+
37
+ // Eventually we need to support multiple trigger string's including newlines.
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
+ }; // 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
+ ]; // Todo: speed up performance by having non-capture group variations of the regex.
53
+
54
+ const autoFormatBase = {
55
+ autoFormatKind: null,
56
+ regEx: /(?:)/,
57
+ regExForAutoFormatting: /(?:)/,
58
+ requiresParagraphStart: false
59
+ };
60
+ const paragraphStartBase = { ...autoFormatBase,
61
+ requiresParagraphStart: true
62
+ };
63
+ const markdownHeader1 = { ...paragraphStartBase,
64
+ autoFormatKind: 'paragraphH1',
65
+ regEx: /^(?:#)/,
66
+ regExForAutoFormatting: /^(?:# )/
67
+ };
68
+ const markdownHeader2 = { ...paragraphStartBase,
69
+ autoFormatKind: 'paragraphH2',
70
+ regEx: /^(?:##)/,
71
+ regExForAutoFormatting: /^(?:## )/
72
+ };
73
+ const markdownHeader3 = { ...paragraphStartBase,
74
+ autoFormatKind: 'paragraphH2',
75
+ regEx: /^(?:###)/,
76
+ regExForAutoFormatting: /^(?:### )/
77
+ };
78
+ const markdownBlockQuote = { ...paragraphStartBase,
79
+ autoFormatKind: 'paragraphBlockQuote',
80
+ regEx: /^(?:>)/,
81
+ regExForAutoFormatting: /^(?:> )/
82
+ };
83
+ const markdownUnorderedListDash = { ...paragraphStartBase,
84
+ autoFormatKind: 'paragraphUnorderedList',
85
+ regEx: /^(?:- )/,
86
+ regExForAutoFormatting: /^(?:- )/
87
+ };
88
+ const markdownUnorderedListAsterisk = { ...paragraphStartBase,
89
+ autoFormatKind: 'paragraphUnorderedList',
90
+ regEx: /^(?:\* )/,
91
+ regExForAutoFormatting: /^(?:\* )/
92
+ };
93
+ const markdownCodeBlock = { ...paragraphStartBase,
94
+ autoFormatKind: 'paragraphCodeBlock',
95
+ regEx: /^(```)$/,
96
+ regExForAutoFormatting: /^(```)([a-z]*)( )/
97
+ };
98
+ const markdownOrderedList = { ...paragraphStartBase,
99
+ autoFormatKind: 'paragraphOrderedList',
100
+ regEx: /^(\d+)\.\s/,
101
+ regExForAutoFormatting: /^(\d+)\.\s/
102
+ };
103
+ const markdownHorizontalRule = { ...paragraphStartBase,
104
+ autoFormatKind: 'horizontalRule',
105
+ regEx: /^(?:\*\*\*)$/,
106
+ regExForAutoFormatting: /^(?:\*\*\* )/
107
+ };
108
+ const markdownHorizontalRuleUsingDashes = { ...paragraphStartBase,
109
+ autoFormatKind: 'horizontalRule',
110
+ regEx: /^(?:---)$/,
111
+ regExForAutoFormatting: /^(?:--- )/
112
+ };
113
+ const markdownItalic = { ...autoFormatBase,
114
+ autoFormatKind: 'italic',
115
+ regEx: /(\*)(\s*\b)([^\*]*)(\b\s*)(\*)/,
116
+ regExForAutoFormatting: /(\*)(\s*\b)([^\*]*)(\b\s*)(\*)(\s)$/
117
+ };
118
+ const markdownBold = { ...autoFormatBase,
119
+ autoFormatKind: 'bold',
120
+ regEx: /(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*)/,
121
+ regExForAutoFormatting: /(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*)(\s)$/
122
+ };
123
+ const markdownBoldWithUnderlines = { ...autoFormatBase,
124
+ autoFormatKind: 'bold',
125
+ regEx: /(__)(\s*)([^__]*)(\s*)(__)/,
126
+ regExForAutoFormatting: /(__)(\s*)([^__]*)(\s*)(__)(\s)$/
127
+ };
128
+ const markdownBoldItalic = { ...autoFormatBase,
129
+ autoFormatKind: 'bold_italic',
130
+ regEx: /(\*\*\*)(\s*\b)([^\*\*\*]*)(\b\s*)(\*\*\*)/,
131
+ regExForAutoFormatting: /(\*\*\*)(\s*\b)([^\*\*\*]*)(\b\s*)(\*\*\*)(\s)$/
132
+ }; // Markdown does not support underline, but we can allow folks to use
133
+ // the HTML tags for underline.
134
+
135
+ const fakeMarkdownUnderline = { ...autoFormatBase,
136
+ autoFormatKind: 'underline',
137
+ regEx: /(\<u\>)(\s*\b)([^\<]*)(\b\s*)(\<\/u\>)/,
138
+ regExForAutoFormatting: /(\<u\>)(\s*\b)([^\<]*)(\b\s*)(\<\/u\>)(\s)$/
139
+ };
140
+ const markdownStrikethrough = { ...autoFormatBase,
141
+ autoFormatKind: 'strikethrough',
142
+ regEx: /(~~)(\s*\b)([^~~]*)(\b\s*)(~~)/,
143
+ regExForAutoFormatting: /(~~)(\s*\b)([^~~]*)(\b\s*)(~~)(\s)$/
144
+ };
145
+ const markdownLink = { ...autoFormatBase,
146
+ autoFormatKind: 'link',
147
+ regEx: /(\[)(.+)(\]\()([^ ]+)(?: \"(?:.+)\")?(\))/,
148
+ regExForAutoFormatting: /(\[)(.+)(\]\()([^ ]+)(?: \"(?:.+)\")?(\))(\s)$/
149
+ };
150
+ const allMarkdownCriteriaForTextNodes = [markdownBoldItalic, markdownItalic, markdownBold, markdownBoldWithUnderlines, fakeMarkdownUnderline, markdownStrikethrough, markdownLink];
151
+ const allMarkdownCriteria = [markdownHeader1, markdownHeader2, markdownHeader3, markdownBlockQuote, markdownUnorderedListDash, markdownUnorderedListAsterisk, markdownOrderedList, markdownCodeBlock, markdownHorizontalRule, markdownHorizontalRuleUsingDashes, ...allMarkdownCriteriaForTextNodes];
152
+ function getInitialScanningContext(editor, isAutoFormatting, textNodeWithOffset, triggerState) {
153
+ return {
154
+ editor,
155
+ isAutoFormatting,
156
+ joinedText: null,
157
+ markdownCriteria: {
158
+ autoFormatKind: 'noTransformation',
159
+ regEx: /(?:)/,
160
+ // Empty reg ex.
161
+ regExForAutoFormatting: /(?:)/,
162
+ // Empty reg ex.
163
+ requiresParagraphStart: null
164
+ },
165
+ patternMatchResults: {
166
+ regExCaptureGroups: []
167
+ },
168
+ textNodeWithOffset,
169
+ triggerState
170
+ };
171
+ }
172
+ function resetScanningContext(scanningContext) {
173
+ scanningContext.joinedText = '';
174
+ scanningContext.markdownCriteria = {
175
+ autoFormatKind: 'noTransformation',
176
+ regEx: /(?:)/,
177
+ // Empty reg ex.
178
+ regExForAutoFormatting: /(?:)/,
179
+ // Empty reg ex.
180
+ requiresParagraphStart: null
181
+ };
182
+ scanningContext.patternMatchResults = {
183
+ regExCaptureGroups: []
184
+ };
185
+ scanningContext.triggerState = null;
186
+ scanningContext.textNodeWithOffset = null;
187
+ return scanningContext;
188
+ }
189
+
190
+ function getPatternMatchResultsWithRegEx(textToSearch, matchMustAppearAtStartOfString, matchMustAppearAtEndOfString, regEx) {
191
+ const patternMatchResults = {
192
+ regExCaptureGroups: []
193
+ };
194
+ const regExMatches = textToSearch.match(regEx);
195
+
196
+ if (regExMatches !== null && regExMatches.length > 0 && (matchMustAppearAtStartOfString === false || regExMatches.index === 0) && (matchMustAppearAtEndOfString === false || regExMatches.index + regExMatches[0].length === textToSearch.length)) {
197
+ const captureGroupsCount = regExMatches.length;
198
+ let runningLength = regExMatches.index;
199
+
200
+ for (let captureGroupIndex = 0; captureGroupIndex < captureGroupsCount; captureGroupIndex++) {
201
+ const textContent = regExMatches[captureGroupIndex];
202
+ patternMatchResults.regExCaptureGroups.push({
203
+ offsetInParent: runningLength,
204
+ text: textContent
205
+ }); // The 0th capture group is special in that it's text contents is
206
+ // a join of all subsequent capture groups. So, skip this group
207
+ // when calculating the runningLength.
208
+
209
+ if (captureGroupIndex > 0) {
210
+ runningLength += textContent.length;
211
+ }
212
+ }
213
+
214
+ return patternMatchResults;
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ function getTextNodeWithOffsetOrThrow(scanningContext) {
221
+ const textNodeWithOffset = scanningContext.textNodeWithOffset;
222
+
223
+ if (textNodeWithOffset == null) {
224
+ {
225
+ throw Error(`Expect to have a text node with offset.`);
226
+ }
227
+ }
228
+
229
+ return textNodeWithOffset;
230
+ }
231
+ function getPatternMatchResultsForParagraphs(markdownCriteria, scanningContext) {
232
+ const textNodeWithOffset = getTextNodeWithOffsetOrThrow(scanningContext); // At start of paragraph.
233
+
234
+ if (textNodeWithOffset.node.getPreviousSibling() === null) {
235
+ const textToSearch = textNodeWithOffset.node.getTextContent();
236
+ return getPatternMatchResultsWithRegEx(textToSearch, true, false, markdownCriteria.regExForAutoFormatting);
237
+ }
238
+
239
+ return null;
240
+ }
241
+ function getPatternMatchResultsForText(markdownCriteria, scanningContext) {
242
+ if (scanningContext.joinedText == null) {
243
+ const parentNode = getParent(scanningContext);
244
+
245
+ if (lexical.$isElementNode(parentNode)) {
246
+ if (scanningContext.joinedText == null) {
247
+ // Lazy calculate the text to search.
248
+ scanningContext.joinedText = text.$joinTextNodesInElementNode(parentNode, SEPARATOR_BETWEEN_TEXT_AND_NON_TEXT_NODES, getTextNodeWithOffsetOrThrow(scanningContext));
249
+ }
250
+ } else {
251
+ {
252
+ throw Error(`Expected node ${parentNode.__key} to to be a ElementNode.`);
253
+ }
254
+ }
255
+ }
256
+
257
+ return getPatternMatchResultsWithRegEx(scanningContext.joinedText, false, true, markdownCriteria.regExForAutoFormatting);
258
+ }
259
+
260
+ function getNewNodeForCriteria(scanningContext, element, createHorizontalRuleNode) {
261
+ let newNode = null;
262
+ const children = element.getChildren();
263
+ const markdownCriteria = scanningContext.markdownCriteria;
264
+ const patternMatchResults = scanningContext.patternMatchResults;
265
+
266
+ if (markdownCriteria.autoFormatKind != null) {
267
+ switch (markdownCriteria.autoFormatKind) {
268
+ case 'paragraphH1':
269
+ {
270
+ newNode = richText.$createHeadingNode('h1');
271
+ newNode.append(...children);
272
+ return newNode;
273
+ }
274
+
275
+ case 'paragraphH2':
276
+ {
277
+ newNode = richText.$createHeadingNode('h2');
278
+ newNode.append(...children);
279
+ return newNode;
280
+ }
281
+
282
+ case 'paragraphH3':
283
+ {
284
+ newNode = richText.$createHeadingNode('h3');
285
+ newNode.append(...children);
286
+ return newNode;
287
+ }
288
+
289
+ case 'paragraphBlockQuote':
290
+ {
291
+ newNode = richText.$createQuoteNode();
292
+ newNode.append(...children);
293
+ return newNode;
294
+ }
295
+
296
+ case 'paragraphUnorderedList':
297
+ {
298
+ newNode = list.$createListNode('ul');
299
+ const listItem = list.$createListItemNode();
300
+ listItem.append(...children);
301
+ newNode.append(listItem);
302
+ return newNode;
303
+ }
304
+
305
+ case 'paragraphOrderedList':
306
+ {
307
+ const startAsString = patternMatchResults.regExCaptureGroups.length > 1 ? patternMatchResults.regExCaptureGroups[patternMatchResults.regExCaptureGroups.length - 1].text : '1';
308
+ const start = parseInt(startAsString, 10);
309
+ newNode = list.$createListNode('ol', start);
310
+ const listItem = list.$createListItemNode();
311
+ listItem.append(...children);
312
+ newNode.append(listItem);
313
+ return newNode;
314
+ }
315
+
316
+ case 'paragraphCodeBlock':
317
+ {
318
+ // Toggle code and paragraph nodes.
319
+ if (scanningContext.triggerState != null && scanningContext.triggerState.isCodeBlock) {
320
+ newNode = lexical.$createParagraphNode();
321
+ } else {
322
+ newNode = code.$createCodeNode();
323
+ const codingLanguage = patternMatchResults.regExCaptureGroups.length >= 3 ? patternMatchResults.regExCaptureGroups[2].text : null;
324
+
325
+ if (codingLanguage != null && codingLanguage.length > 0) {
326
+ newNode.setLanguage(codingLanguage);
327
+ }
328
+ }
329
+
330
+ newNode.append(...children);
331
+ return newNode;
332
+ }
333
+
334
+ case 'horizontalRule':
335
+ {
336
+ // return null for newNode. Insert the HR here.
337
+ const horizontalRuleNode = createHorizontalRuleNode();
338
+ element.insertBefore(horizontalRuleNode);
339
+ break;
340
+ }
341
+ }
342
+ }
343
+
344
+ return newNode;
345
+ }
346
+
347
+ function transformTextNodeForParagraphs(scanningContext, createHorizontalRuleNode) {
348
+ const textNodeWithOffset = getTextNodeWithOffsetOrThrow(scanningContext);
349
+ const element = textNodeWithOffset.node.getParentOrThrow();
350
+ const text = scanningContext.patternMatchResults.regExCaptureGroups[0].text; // Remove the text which we matched.
351
+
352
+ const textNode = textNodeWithOffset.node.spliceText(0, text.length, '', true);
353
+
354
+ if (textNode.getTextContent() === '') {
355
+ textNode.selectPrevious();
356
+ textNode.remove();
357
+ } // Transform the current element kind to the new element kind.
358
+
359
+
360
+ const elementNode = getNewNodeForCriteria(scanningContext, element, createHorizontalRuleNode);
361
+
362
+ if (elementNode !== null) {
363
+ element.replace(elementNode);
364
+ }
365
+ }
366
+ function transformTextNodeForText(scanningContext) {
367
+ const markdownCriteria = scanningContext.markdownCriteria;
368
+
369
+ if (markdownCriteria.autoFormatKind != null) {
370
+ const formatting = getTextFormatType(markdownCriteria.autoFormatKind);
371
+
372
+ if (formatting != null) {
373
+ transformTextNodeWithFormatting(formatting, scanningContext);
374
+ return;
375
+ }
376
+
377
+ if (markdownCriteria.autoFormatKind === 'link') {
378
+ transformTextNodeWithLink(scanningContext);
379
+ }
380
+ }
381
+ }
382
+
383
+ function transformTextNodeWithFormatting(formatting, scanningContext) {
384
+ const patternMatchResults = scanningContext.patternMatchResults;
385
+ const groupCount = patternMatchResults.regExCaptureGroups.length;
386
+
387
+ if (groupCount !== 7) {
388
+ // For BIUS and similar formats which have a pattern + text + pattern:
389
+ // given '*italic* ' below are the capture groups by index:
390
+ // 0. '*italic* '
391
+ // 1. '*'
392
+ // 2. whitespace // typically this is "".
393
+ // 3. 'italic'
394
+ // 4. whitespace // typicallly this is "".
395
+ // 5. '*'
396
+ // 6. ' '
397
+ return;
398
+ } // Remove unwanted text in reg ex pattern.
399
+ // Remove group 5.
400
+
401
+
402
+ removeTextByCaptureGroups(5, 5, scanningContext); // Remove group 1.
403
+
404
+ removeTextByCaptureGroups(1, 1, scanningContext); // Apply the formatting.
405
+
406
+ formatTextInCaptureGroupIndex(formatting, 3, scanningContext); // Place caret at end of final capture group.
407
+
408
+ selectAfterFinalCaptureGroup(scanningContext);
409
+ }
410
+
411
+ function transformTextNodeWithLink(scanningContext) {
412
+ const patternMatchResults = scanningContext.patternMatchResults;
413
+ const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
414
+ const groupCount = regExCaptureGroups.length;
415
+
416
+ if (groupCount !== 7) {
417
+ // For links and similar formats which have: pattern + text + pattern + pattern2 text2 + pattern2:
418
+ // Given '[title](url) ', below are the capture groups by index:
419
+ // 0. '[title](url) '
420
+ // 1. '['
421
+ // 2. 'title'
422
+ // 3. ']('
423
+ // 4. 'url'
424
+ // 5. ')'
425
+ // 6. ' '
426
+ return;
427
+ }
428
+
429
+ const title = regExCaptureGroups[2].text;
430
+ const url = regExCaptureGroups[4].text;
431
+
432
+ if (title.length === 0 || url.length === 0) {
433
+ return;
434
+ } // Remove the initial pattern through to the final pattern.
435
+
436
+
437
+ removeTextByCaptureGroups(1, 5, scanningContext);
438
+ insertTextPriorToCaptureGroup(1, // Insert at the beginning of the meaningful capture groups, namely index 1. Index 0 refers to the whole matched string.
439
+ title, scanningContext);
440
+ const newSelectionForLink = createSelectionWithCaptureGroups(1, 1, false, true, scanningContext);
441
+
442
+ if (newSelectionForLink == null) {
443
+ return;
444
+ }
445
+
446
+ lexical.$setSelection(newSelectionForLink);
447
+ scanningContext.editor.dispatchCommand(link.TOGGLE_LINK_COMMAND, url); // Place caret at end of final capture group.
448
+
449
+ selectAfterFinalCaptureGroup(scanningContext);
450
+ } // Below are lower level helper functions.
451
+
452
+
453
+ function getParent(scanningContext) {
454
+ return getTextNodeWithOffsetOrThrow(scanningContext).node.getParentOrThrow();
455
+ }
456
+
457
+ function getJoinedTextLength(patternMatchResults) {
458
+ const groupCount = patternMatchResults.regExCaptureGroups.length;
459
+
460
+ if (groupCount < 2) {
461
+ // Ignore capture group 0, as regEx defaults the 0th one to the entire matched string.
462
+ return 0;
463
+ }
464
+
465
+ const lastGroupIndex = groupCount - 1;
466
+ return patternMatchResults.regExCaptureGroups[lastGroupIndex].offsetInParent + patternMatchResults.regExCaptureGroups[lastGroupIndex].text.length;
467
+ }
468
+
469
+ function getTextFormatType(autoFormatKind) {
470
+ switch (autoFormatKind) {
471
+ case 'italic':
472
+ case 'bold':
473
+ case 'underline':
474
+ case 'strikethrough':
475
+ return [autoFormatKind];
476
+
477
+ case 'bold_italic':
478
+ {
479
+ return ['bold', 'italic'];
480
+ }
481
+ }
482
+
483
+ return null;
484
+ }
485
+
486
+ function createSelectionWithCaptureGroups(anchorCaptureGroupIndex, focusCaptureGroupIndex, startAtEndOfAnchor, finishAtEndOfFocus, scanningContext) {
487
+ const patternMatchResults = scanningContext.patternMatchResults;
488
+ const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
489
+ const regExCaptureGroupsCount = regExCaptureGroups.length;
490
+
491
+ if (anchorCaptureGroupIndex >= regExCaptureGroupsCount || focusCaptureGroupIndex >= regExCaptureGroupsCount) {
492
+ return null;
493
+ }
494
+
495
+ const parentElementNode = getParent(scanningContext);
496
+ const joinedTextLength = getJoinedTextLength(patternMatchResults);
497
+ const anchorCaptureGroupDetail = regExCaptureGroups[anchorCaptureGroupIndex];
498
+ const focusCaptureGroupDetail = regExCaptureGroups[focusCaptureGroupIndex];
499
+ const anchorLocation = startAtEndOfAnchor ? anchorCaptureGroupDetail.offsetInParent + anchorCaptureGroupDetail.text.length : anchorCaptureGroupDetail.offsetInParent;
500
+ const focusLocation = finishAtEndOfFocus ? focusCaptureGroupDetail.offsetInParent + focusCaptureGroupDetail.text.length : focusCaptureGroupDetail.offsetInParent;
501
+ const anchorTextNodeWithOffset = text.$findNodeWithOffsetFromJoinedText(anchorLocation, joinedTextLength, SEPARATOR_LENGTH, parentElementNode);
502
+ const focusTextNodeWithOffset = text.$findNodeWithOffsetFromJoinedText(focusLocation, joinedTextLength, SEPARATOR_LENGTH, parentElementNode);
503
+
504
+ if (anchorTextNodeWithOffset == null || focusTextNodeWithOffset == null) {
505
+ return null;
506
+ }
507
+
508
+ const selection = lexical.$createRangeSelection();
509
+ selection.anchor.set(anchorTextNodeWithOffset.node.getKey(), anchorTextNodeWithOffset.offset, 'text');
510
+ selection.focus.set(focusTextNodeWithOffset.node.getKey(), focusTextNodeWithOffset.offset, 'text');
511
+ return selection;
512
+ }
513
+
514
+ function removeTextByCaptureGroups(anchorCaptureGroupIndex, focusCaptureGroupIndex, scanningContext) {
515
+ const patternMatchResults = scanningContext.patternMatchResults;
516
+ const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
517
+ const newSelection = createSelectionWithCaptureGroups(anchorCaptureGroupIndex, focusCaptureGroupIndex, false, true, scanningContext);
518
+
519
+ if (newSelection != null) {
520
+ lexical.$setSelection(newSelection);
521
+ const currentSelection = lexical.$getSelection();
522
+
523
+ if (currentSelection != null && lexical.$isRangeSelection(currentSelection) && currentSelection.isCollapsed() === false) {
524
+ currentSelection.removeText(); // Shift all group offsets and clear out group text.
525
+
526
+ let runningLength = 0;
527
+ const groupCount = regExCaptureGroups.length;
528
+
529
+ for (let i = anchorCaptureGroupIndex; i < groupCount; i++) {
530
+ const captureGroupDetail = regExCaptureGroups[i];
531
+
532
+ if (i > anchorCaptureGroupIndex) {
533
+ captureGroupDetail.offsetInParent -= runningLength;
534
+ }
535
+
536
+ if (i <= focusCaptureGroupIndex) {
537
+ runningLength += captureGroupDetail.text.length;
538
+ captureGroupDetail.text = '';
539
+ }
540
+ }
541
+ }
542
+ }
543
+ }
544
+
545
+ function insertTextPriorToCaptureGroup(captureGroupIndex, text, scanningContext) {
546
+ const patternMatchResults = scanningContext.patternMatchResults;
547
+ const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
548
+ const regExCaptureGroupsCount = regExCaptureGroups.length;
549
+
550
+ if (captureGroupIndex >= regExCaptureGroupsCount) {
551
+ return;
552
+ }
553
+
554
+ const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
555
+ const newCaptureGroupDetail = {
556
+ offsetInParent: captureGroupDetail.offsetInParent,
557
+ text
558
+ };
559
+ const newSelection = createSelectionWithCaptureGroups(captureGroupIndex, captureGroupIndex, false, false, scanningContext);
560
+
561
+ if (newSelection != null) {
562
+ lexical.$setSelection(newSelection);
563
+ const currentSelection = lexical.$getSelection();
564
+
565
+ if (currentSelection != null && lexical.$isRangeSelection(currentSelection) && currentSelection.isCollapsed()) {
566
+ currentSelection.insertText(newCaptureGroupDetail.text); // Update the capture groups.
567
+
568
+ regExCaptureGroups.splice(captureGroupIndex, 0, newCaptureGroupDetail);
569
+ const textLength = newCaptureGroupDetail.text.length;
570
+ const newGroupCount = regExCaptureGroups.length;
571
+
572
+ for (let i = captureGroupIndex + 1; i < newGroupCount; i++) {
573
+ const currentCaptureGroupDetail = regExCaptureGroups[i];
574
+ currentCaptureGroupDetail.offsetInParent += textLength;
575
+ }
576
+ }
577
+ }
578
+ }
579
+
580
+ function formatTextInCaptureGroupIndex(formatTypes, captureGroupIndex, scanningContext) {
581
+ const patternMatchResults = scanningContext.patternMatchResults;
582
+ const regExCaptureGroups = patternMatchResults.regExCaptureGroups;
583
+ const regExCaptureGroupsCount = regExCaptureGroups.length;
584
+
585
+ if (!(captureGroupIndex < regExCaptureGroupsCount)) {
586
+ throw Error(`The capture group count in the RegEx does match the actual capture group count.`);
587
+ }
588
+
589
+ const captureGroupDetail = regExCaptureGroups[captureGroupIndex];
590
+
591
+ if (captureGroupDetail.text.length === 0) {
592
+ return;
593
+ }
594
+
595
+ const newSelection = createSelectionWithCaptureGroups(captureGroupIndex, captureGroupIndex, false, true, scanningContext);
596
+
597
+ if (newSelection != null) {
598
+ lexical.$setSelection(newSelection);
599
+ const currentSelection = lexical.$getSelection();
600
+
601
+ if (lexical.$isRangeSelection(currentSelection)) {
602
+ for (let i = 0; i < formatTypes.length; i++) {
603
+ currentSelection.formatText(formatTypes[i]);
604
+ }
605
+ }
606
+ }
607
+ } // Place caret at end of final capture group.
608
+
609
+
610
+ function selectAfterFinalCaptureGroup(scanningContext) {
611
+ const patternMatchResults = scanningContext.patternMatchResults;
612
+ const groupCount = patternMatchResults.regExCaptureGroups.length;
613
+
614
+ if (groupCount < 2) {
615
+ // Ignore capture group 0, as regEx defaults the 0th one to the entire matched string.
616
+ return;
617
+ }
618
+
619
+ const lastGroupIndex = groupCount - 1;
620
+ const newSelection = createSelectionWithCaptureGroups(lastGroupIndex, lastGroupIndex, true, true, scanningContext);
621
+
622
+ if (newSelection != null) {
623
+ lexical.$setSelection(newSelection);
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
629
+ *
630
+ * This source code is licensed under the MIT license found in the
631
+ * LICENSE file in the root directory of this source tree.
632
+ *
633
+ *
634
+ */
635
+ function getAllTriggers() {
636
+ return triggers;
637
+ }
638
+ function getAllMarkdownCriteriaForTextNodes() {
639
+ return allMarkdownCriteriaForTextNodes;
640
+ }
641
+ function getAllMarkdownCriteria() {
642
+ return allMarkdownCriteria;
643
+ }
644
+ function transformTextNodeForMarkdownCriteria(scanningContext, createHorizontalRuleNode) {
645
+ if (scanningContext.markdownCriteria.requiresParagraphStart === true) {
646
+ transformTextNodeForParagraphs(scanningContext, createHorizontalRuleNode);
647
+ } else {
648
+ transformTextNodeForText(scanningContext);
649
+ }
650
+ }
651
+
652
+ function getPatternMatchResultsForCriteria(markdownCriteria, scanningContext) {
653
+ if (markdownCriteria.requiresParagraphStart === true) {
654
+ return getPatternMatchResultsForParagraphs(markdownCriteria, scanningContext);
655
+ }
656
+
657
+ return getPatternMatchResultsForText(markdownCriteria, scanningContext);
658
+ }
659
+
660
+ function getTextNodeForAutoFormatting(selection) {
661
+ if (!lexical.$isRangeSelection(selection)) {
662
+ return null;
663
+ }
664
+
665
+ const node = selection.anchor.getNode();
666
+
667
+ if (!lexical.$isTextNode(node)) {
668
+ return null;
669
+ }
670
+
671
+ return {
672
+ node,
673
+ offset: selection.anchor.offset
674
+ };
675
+ }
676
+
677
+ function updateAutoFormatting(editor, scanningContext, createHorizontalRuleNode) {
678
+ editor.update(() => {
679
+ transformTextNodeForMarkdownCriteria(scanningContext, createHorizontalRuleNode);
680
+ }, {
681
+ tag: 'history-push'
682
+ });
683
+ }
684
+
685
+ function getCriteriaWithPatternMatchResults(markdownCriteriaArray, scanningContext) {
686
+ const currentTriggerState = scanningContext.triggerState;
687
+ const count = markdownCriteriaArray.length;
688
+
689
+ for (let i = 0; i < count; i++) {
690
+ const markdownCriteria = markdownCriteriaArray[i]; // Skip code block nodes, unless the autoFormatKind calls for toggling the code block.
691
+
692
+ if (currentTriggerState != null && currentTriggerState.isCodeBlock === false || markdownCriteria.autoFormatKind === 'paragraphCodeBlock') {
693
+ const patternMatchResults = getPatternMatchResultsForCriteria(markdownCriteria, scanningContext);
694
+
695
+ if (patternMatchResults != null) {
696
+ return {
697
+ markdownCriteria,
698
+ patternMatchResults
699
+ };
700
+ }
701
+ }
702
+ }
703
+
704
+ return {
705
+ markdownCriteria: null,
706
+ patternMatchResults: null
707
+ };
708
+ }
709
+
710
+ function findScanningContextWithValidMatch(editor, currentTriggerState) {
711
+ let scanningContext = null;
712
+ editor.getEditorState().read(() => {
713
+ const textNodeWithOffset = getTextNodeForAutoFormatting(lexical.$getSelection());
714
+
715
+ if (textNodeWithOffset === null) {
716
+ return;
717
+ } // Please see the declaration of ScanningContext for a detailed explanation.
718
+
719
+
720
+ const initialScanningContext = getInitialScanningContext(editor, true, textNodeWithOffset, currentTriggerState);
721
+ const criteriaWithPatternMatchResults = getCriteriaWithPatternMatchResults( // Do not apply paragraph node changes like blockQuote or H1 to listNodes. Also, do not attempt to transform a list into a list using * or -.
722
+ currentTriggerState.isParentAListItemNode === false ? getAllMarkdownCriteria() : getAllMarkdownCriteriaForTextNodes(), initialScanningContext);
723
+
724
+ if (criteriaWithPatternMatchResults.markdownCriteria === null || criteriaWithPatternMatchResults.patternMatchResults === null) {
725
+ return;
726
+ }
727
+
728
+ scanningContext = initialScanningContext; // Lazy fill-in the particular format criteria and any matching result information.
729
+
730
+ scanningContext.markdownCriteria = criteriaWithPatternMatchResults.markdownCriteria;
731
+ scanningContext.patternMatchResults = criteriaWithPatternMatchResults.patternMatchResults;
732
+ });
733
+ return scanningContext;
734
+ }
735
+
736
+ function getTriggerState(editorState) {
737
+ let criteria = null;
738
+ editorState.read(() => {
739
+ const selection = lexical.$getSelection();
740
+
741
+ if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
742
+ return;
743
+ }
744
+
745
+ const node = selection.anchor.getNode();
746
+ const parentNode = node.getParent();
747
+ const isParentAListItemNode = list.$isListItemNode(parentNode);
748
+ const hasParentNode = parentNode !== null;
749
+ criteria = {
750
+ anchorOffset: selection.anchor.offset,
751
+ hasParentNode,
752
+ isCodeBlock: code.$isCodeNode(node),
753
+ isParentAListItemNode,
754
+ isSelectionCollapsed: true,
755
+ isSimpleText: lexical.$isTextNode(node) && node.isSimpleText(),
756
+ nodeKey: node.getKey(),
757
+ textContent: node.getTextContent()
758
+ };
759
+ });
760
+ return criteria;
761
+ }
762
+ function findScanningContext(editor, currentTriggerState, priorTriggerState) {
763
+ if (currentTriggerState == null || priorTriggerState == null) {
764
+ return null;
765
+ }
766
+
767
+ const triggerArray = getAllTriggers();
768
+ const triggerCount = triggers.length;
769
+
770
+ for (let ti = 0; ti < triggerCount; ti++) {
771
+ const triggerString = triggerArray[ti].triggerString; // The below checks needs to execute relativey quickly, so perform the light-weight ones first.
772
+ // The substr check is a quick way to avoid autoformat parsing in that it looks for the autoformat
773
+ // trigger which is the trigger string (" ").
774
+
775
+ const triggerStringLength = triggerString.length;
776
+ const currentTextContentLength = currentTriggerState.textContent.length;
777
+ const triggerOffset = currentTriggerState.anchorOffset - triggerStringLength;
778
+
779
+ if ((currentTriggerState.hasParentNode === true && currentTriggerState.isSimpleText && currentTriggerState.isSelectionCollapsed && currentTriggerState.nodeKey === priorTriggerState.nodeKey && currentTriggerState.anchorOffset !== priorTriggerState.anchorOffset && triggerOffset >= 0 && triggerOffset + triggerStringLength <= currentTextContentLength && currentTriggerState.textContent.substr(triggerOffset, triggerStringLength) === triggerString && // Some code differentiation needed if trigger kind is not a simple space character.
780
+ currentTriggerState.textContent !== priorTriggerState.textContent) === false) {
781
+ return null;
782
+ }
783
+ }
784
+
785
+ return findScanningContextWithValidMatch(editor, currentTriggerState);
786
+ }
787
+
788
+ /**
789
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
790
+ *
791
+ * This source code is licensed under the MIT license found in the
792
+ * LICENSE file in the root directory of this source tree.
793
+ *
794
+ *
795
+ */
796
+ function convertStringToLexical(text, editor) {
797
+ const nodes = text.split('\n').map(splitText => lexical.$createParagraphNode().append(lexical.$createTextNode(splitText)));
798
+ const root = lexical.$getRoot();
799
+ root.clear();
800
+ root.append(...nodes);
801
+ }
802
+
803
+ function convertElementNodeContainingMarkdown(scanningContext, elementNode) {
804
+ // Handle code block to be done.
805
+ // Handle paragraph nodes below.
806
+ if (lexical.$isParagraphNode(elementNode)) {
807
+ const paragraphNode = elementNode;
808
+ const allCriteria = getAllMarkdownCriteria();
809
+ const count = allCriteria.length;
810
+
811
+ for (let i = 0; i < count; i++) {
812
+ const criteria = allCriteria[i];
813
+
814
+ if (criteria.requiresParagraphStart === true) {
815
+ const firstChild = paragraphNode.getFirstChild();
816
+
817
+ if (!lexical.$isTextNode(firstChild)) {
818
+ throw Error(`Expect paragraph containing only text nodes.`);
819
+ }
820
+
821
+ scanningContext.textNodeWithOffset = {
822
+ node: firstChild,
823
+ offset: 0
824
+ };
825
+ scanningContext.joinedText = paragraphNode.getTextContent();
826
+ const patternMatchResults = getPatternMatchResultsForParagraphs(criteria, scanningContext);
827
+
828
+ if (patternMatchResults != null) {
829
+ // Lazy fill-in the particular format criteria and any matching result information.
830
+ scanningContext.markdownCriteria = criteria;
831
+ scanningContext.patternMatchResults = patternMatchResults; // Todo: perform text transformation here.
832
+ }
833
+ }
834
+ }
835
+ }
836
+ }
837
+
838
+ function convertMarkdownForElementNodes(elementNodes, editor) {
839
+ // Please see the declaration of ScanningContext for a detailed explanation.
840
+ const scanningContext = getInitialScanningContext(editor, false, null, null);
841
+ const count = elementNodes.length;
842
+
843
+ for (let i = 0; i < count; i++) {
844
+ const elementNode = elementNodes[i];
845
+
846
+ if (lexical.$isElementNode(elementNode) && elementNode.getTextContent().length && elementNode.getChildren().length) {
847
+ convertElementNodeContainingMarkdown(scanningContext, elementNode);
848
+ } // Reset the scanning information that relates to the particular element node.
849
+
850
+
851
+ resetScanningContext(scanningContext);
852
+ }
853
+ }
854
+
855
+ /**
856
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
857
+ *
858
+ * This source code is licensed under the MIT license found in the
859
+ * LICENSE file in the root directory of this source tree.
860
+ *
861
+ *
862
+ */
863
+ function registerMarkdownShortcuts(editor, createHorizontalRuleNode) {
864
+ // The priorTriggerState is compared against the currentTriggerState to determine
865
+ // if the user has performed some typing event that warrants an auto format.
866
+ // For example, typing "#" and then " ", shoud trigger an format.
867
+ // However, given "#A B", where the user delets "A" should not.
868
+ let priorTriggerState = null;
869
+ return editor.registerUpdateListener(({
870
+ tags
871
+ }) => {
872
+ // Examine historic so that we are not running autoformatting within markdown.
873
+ if (tags.has('historic') === false) {
874
+ const currentTriggerState = getTriggerState(editor.getEditorState());
875
+ const scanningContext = currentTriggerState == null ? null : findScanningContext(editor, currentTriggerState, priorTriggerState);
876
+
877
+ if (scanningContext != null) {
878
+ updateAutoFormatting(editor, scanningContext, createHorizontalRuleNode);
879
+ }
880
+
881
+ priorTriggerState = currentTriggerState;
882
+ } else {
883
+ priorTriggerState = null;
884
+ }
885
+ });
886
+ }
887
+ function $convertFromMarkdownString(markdownString, editor, createHorizontalRuleNode) {
888
+ convertStringToLexical(markdownString);
889
+ convertMarkdownForElementNodes(lexical.$getRoot().getChildren(), editor);
890
+ }
891
+
892
+ exports.$convertFromMarkdownString = $convertFromMarkdownString;
893
+ exports.registerMarkdownShortcuts = registerMarkdownShortcuts;
@@ -0,0 +1,9 @@
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
+ 'use strict'
8
+ const LexicalMarkdown = process.env.NODE_ENV === 'development' ? require('./LexicalMarkdown.dev.js') : require('./LexicalMarkdown.prod.js')
9
+ module.exports = LexicalMarkdown;
@@ -0,0 +1,19 @@
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
+ * @flow strict
8
+ */
9
+
10
+ import type {DecoratorNode, LexicalEditor} from 'lexical';
11
+
12
+ declare export function registerMarkdownShortcuts<T>(
13
+ editor: LexicalEditor,
14
+ createHorizontalRuleNode: () => DecoratorNode<T>,
15
+ ): () => void;
16
+ declare export function $convertFromMarkdownString(
17
+ markdownString: string,
18
+ editor: LexicalEditor,
19
+ ): void;
@@ -0,0 +1,29 @@
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
+ var h=require("lexical"),n=require("@lexical/code"),p=require("@lexical/list"),r=require("@lexical/link"),t=require("@lexical/rich-text"),u=require("@lexical/text");function w(e){throw Error(`Minified Lexical error #${e}; see codes.json for the full message or `+"use the non-minified dev environment for full errors and additional helpful warnings.");}
8
+ const x=[{triggerKind:"space_trigger",triggerString:" "}],y={autoFormatKind:null,regEx:/(?:)/,regExForAutoFormatting:/(?:)/,requiresParagraphStart:!1},z={...y,requiresParagraphStart:!0},A=[{...y,autoFormatKind:"bold_italic",regEx:/(\*\*\*)(\s*\b)([^\*\*\*]*)(\b\s*)(\*\*\*)/,regExForAutoFormatting:/(\*\*\*)(\s*\b)([^\*\*\*]*)(\b\s*)(\*\*\*)(\s)$/},{...y,autoFormatKind:"italic",regEx:/(\*)(\s*\b)([^\*]*)(\b\s*)(\*)/,regExForAutoFormatting:/(\*)(\s*\b)([^\*]*)(\b\s*)(\*)(\s)$/},{...y,autoFormatKind:"bold",
9
+ regEx:/(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*)/,regExForAutoFormatting:/(\*\*)(\s*\b)([^\*\*]*)(\b\s*)(\*\*)(\s)$/},{...y,autoFormatKind:"bold",regEx:/(__)(\s*)([^__]*)(\s*)(__)/,regExForAutoFormatting:/(__)(\s*)([^__]*)(\s*)(__)(\s)$/},{...y,autoFormatKind:"underline",regEx:/(<u>)(\s*\b)([^<]*)(\b\s*)(<\/u>)/,regExForAutoFormatting:/(<u>)(\s*\b)([^<]*)(\b\s*)(<\/u>)(\s)$/},{...y,autoFormatKind:"strikethrough",regEx:/(~~)(\s*\b)([^~~]*)(\b\s*)(~~)/,regExForAutoFormatting:/(~~)(\s*\b)([^~~]*)(\b\s*)(~~)(\s)$/},
10
+ {...y,autoFormatKind:"link",regEx:/(\[)(.+)(\]\()([^ ]+)(?: "(?:.+)")?(\))/,regExForAutoFormatting:/(\[)(.+)(\]\()([^ ]+)(?: "(?:.+)")?(\))(\s)$/}],B=[{...z,autoFormatKind:"paragraphH1",regEx:/^(?:#)/,regExForAutoFormatting:/^(?:# )/},{...z,autoFormatKind:"paragraphH2",regEx:/^(?:##)/,regExForAutoFormatting:/^(?:## )/},{...z,autoFormatKind:"paragraphH2",regEx:/^(?:###)/,regExForAutoFormatting:/^(?:### )/},{...z,autoFormatKind:"paragraphBlockQuote",regEx:/^(?:>)/,regExForAutoFormatting:/^(?:> )/},
11
+ {...z,autoFormatKind:"paragraphUnorderedList",regEx:/^(?:- )/,regExForAutoFormatting:/^(?:- )/},{...z,autoFormatKind:"paragraphUnorderedList",regEx:/^(?:\* )/,regExForAutoFormatting:/^(?:\* )/},{...z,autoFormatKind:"paragraphOrderedList",regEx:/^(\d+)\.\s/,regExForAutoFormatting:/^(\d+)\.\s/},{...z,autoFormatKind:"paragraphCodeBlock",regEx:/^(```)$/,regExForAutoFormatting:/^(```)([a-z]*)( )/},{...z,autoFormatKind:"horizontalRule",regEx:/^(?:\*\*\*)$/,regExForAutoFormatting:/^(?:\*\*\* )/},{...z,autoFormatKind:"horizontalRule",
12
+ regEx:/^(?:---)$/,regExForAutoFormatting:/^(?:--- )/},...A];function C(e,c,f,a){return{editor:e,isAutoFormatting:c,joinedText:null,markdownCriteria:{autoFormatKind:"noTransformation",regEx:/(?:)/,regExForAutoFormatting:/(?:)/,requiresParagraphStart:null},patternMatchResults:{regExCaptureGroups:[]},textNodeWithOffset:f,triggerState:a}}
13
+ function D(e,c,f,a){const b={regExCaptureGroups:[]};a=e.match(a);if(null!==a&&0<a.length&&(!1===c||0===a.index)&&(!1===f||a.index+a[0].length===e.length)){e=a.length;c=a.index;for(f=0;f<e;f++){const d=a[f];b.regExCaptureGroups.push({offsetInParent:c,text:d});0<f&&(c+=d.length)}return b}return null}function E(e){e=e.textNodeWithOffset;null==e&&w(82);return e}function F(e,c){c=E(c);return null===c.node.getPreviousSibling()?(c=c.node.getTextContent(),D(c,!0,!1,e.regExForAutoFormatting)):null}
14
+ function G(e,c,f,a,b){var d=b.patternMatchResults;const g=d.regExCaptureGroups;var k=g.length;if(e>=k||c>=k)return null;b=E(b).node.getParentOrThrow();k=d.regExCaptureGroups.length;2>k?d=0:(--k,d=d.regExCaptureGroups[k].offsetInParent+d.regExCaptureGroups[k].text.length);e=g[e];c=g[c];a=a?c.offsetInParent+c.text.length:c.offsetInParent;f=u.$findNodeWithOffsetFromJoinedText(f?e.offsetInParent+e.text.length:e.offsetInParent,d,1,b);a=u.$findNodeWithOffsetFromJoinedText(a,d,1,b);if(null==f||null==a)return null;
15
+ b=h.$createRangeSelection();b.anchor.set(f.node.getKey(),f.offset,"text");b.focus.set(a.node.getKey(),a.offset,"text");return b}function H(e,c,f){const a=f.patternMatchResults.regExCaptureGroups;f=G(e,c,!1,!0,f);if(null!=f&&(h.$setSelection(f),f=h.$getSelection(),null!=f&&h.$isRangeSelection(f)&&!1===f.isCollapsed())){f.removeText();f=0;const b=a.length;for(let d=e;d<b;d++){const g=a[d];d>e&&(g.offsetInParent-=f);d<=c&&(f+=g.text.length,g.text="")}}}
16
+ function I(e){var c=e.patternMatchResults.regExCaptureGroups.length;2>c||(--c,e=G(c,c,!0,!0,e),null!=e&&h.$setSelection(e))}
17
+ function J(e,c,f){e.update(()=>{if(!0===c.markdownCriteria.requiresParagraphStart){var a=E(c),b=a.node.getParentOrThrow();a=a.node.spliceText(0,c.patternMatchResults.regExCaptureGroups[0].text.length,"",!0);""===a.getTextContent()&&(a.selectPrevious(),a.remove());var d=b;a=null;var g=d.getChildren(),k=c.markdownCriteria;const l=c.patternMatchResults;if(null!=k.autoFormatKind)switch(k.autoFormatKind){case "paragraphH1":a=t.$createHeadingNode("h1");a.append(...g);break;case "paragraphH2":a=t.$createHeadingNode("h2");
18
+ a.append(...g);break;case "paragraphH3":a=t.$createHeadingNode("h3");a.append(...g);break;case "paragraphBlockQuote":a=t.$createQuoteNode();a.append(...g);break;case "paragraphUnorderedList":a=p.$createListNode("ul");d=p.$createListItemNode();d.append(...g);a.append(d);break;case "paragraphOrderedList":a=parseInt(1<l.regExCaptureGroups.length?l.regExCaptureGroups[l.regExCaptureGroups.length-1].text:"1",10);a=p.$createListNode("ol",a);d=p.$createListItemNode();d.append(...g);a.append(d);break;case "paragraphCodeBlock":null!=
19
+ c.triggerState&&c.triggerState.isCodeBlock?a=h.$createParagraphNode():(a=n.$createCodeNode(),d=3<=l.regExCaptureGroups.length?l.regExCaptureGroups[2].text:null,null!=d&&0<d.length&&a.setLanguage(d));a.append(...g);break;case "horizontalRule":g=f(),d.insertBefore(g)}null!==a&&b.replace(a)}else if(b=c.markdownCriteria,null!=b.autoFormatKind){a:{a=b.autoFormatKind;switch(a){case "italic":case "bold":case "underline":case "strikethrough":a=[a];break a;case "bold_italic":a=["bold","italic"];break a}a=
20
+ null}if(null!=a){if(b=a,7===c.patternMatchResults.regExCaptureGroups.length){H(5,5,c);H(1,1,c);a=c.patternMatchResults.regExCaptureGroups;3<a.length||w(65);if(0!==a[3].text.length&&(a=G(3,3,!1,!0,c),null!=a&&(h.$setSelection(a),a=h.$getSelection(),h.$isRangeSelection(a))))for(g=0;g<b.length;g++)a.formatText(b[g]);I(c)}}else if("link"===b.autoFormatKind&&(b=c.patternMatchResults.regExCaptureGroups,7===b.length&&(g=b[2].text,b=b[4].text,0!==g.length&&0!==b.length))){H(1,5,c);a=c.patternMatchResults.regExCaptureGroups;
21
+ if(!(1>=a.length)&&(g={offsetInParent:a[1].offsetInParent,text:g},d=G(1,1,!1,!1,c),null!=d&&(h.$setSelection(d),d=h.$getSelection(),null!=d&&h.$isRangeSelection(d)&&d.isCollapsed())))for(d.insertText(g.text),a.splice(1,0,g),g=g.text.length,d=a.length,k=2;k<d;k++)a[k].offsetInParent+=g;a=G(1,1,!1,!0,c);null!=a&&(h.$setSelection(a),c.editor.dispatchCommand(r.TOGGLE_LINK_COMMAND,b),I(c))}}},{tag:"history-push"})}
22
+ function K(e,c){let f=null;e.getEditorState().read(()=>{var a=h.$getSelection();if(h.$isRangeSelection(a)){var b=a.anchor.getNode();a=h.$isTextNode(b)?{node:b,offset:a.anchor.offset}:null}else a=null;if(null!==a){a=C(e,!0,a,c);a:{b=!1===c.isParentAListItemNode?B:A;const k=a.triggerState,l=b.length;for(let m=0;m<l;m++){const v=b[m];if(null!=k&&!1===k.isCodeBlock||"paragraphCodeBlock"===v.autoFormatKind){var d=v,g=a;if(!0===d.requiresParagraphStart)d=F(d,g);else{if(null==g.joinedText){const q=E(g).node.getParentOrThrow();
23
+ h.$isElementNode(q)?null==g.joinedText&&(g.joinedText=u.$joinTextNodesInElementNode(q,"\u0004",E(g))):w(52,q.__key)}d=D(g.joinedText,!1,!0,d.regExForAutoFormatting)}if(null!=d){b={markdownCriteria:v,patternMatchResults:d};break a}}}b={markdownCriteria:null,patternMatchResults:null}}null!==b.markdownCriteria&&null!==b.patternMatchResults&&(f=a,f.markdownCriteria=b.markdownCriteria,f.patternMatchResults=b.patternMatchResults)}});return f}
24
+ function L(e){let c=null;e.read(()=>{const f=h.$getSelection();if(h.$isRangeSelection(f)&&f.isCollapsed()){var a=f.anchor.getNode(),b=a.getParent(),d=p.$isListItemNode(b);c={anchorOffset:f.anchor.offset,hasParentNode:null!==b,isCodeBlock:n.$isCodeNode(a),isParentAListItemNode:d,isSelectionCollapsed:!0,isSimpleText:h.$isTextNode(a)&&a.isSimpleText(),nodeKey:a.getKey(),textContent:a.getTextContent()}}});return c}
25
+ function M(e){e=e.split("\n").map(f=>h.$createParagraphNode().append(h.$createTextNode(f)));const c=h.$getRoot();c.clear();c.append(...e)}
26
+ exports.$convertFromMarkdownString=function(e,c){M(e);e=h.$getRoot().getChildren();c=C(c,!1,null,null);const f=e.length;for(let g=0;g<f;g++){var a=e[g];if(h.$isElementNode(a)&&a.getTextContent().length&&a.getChildren().length){var b=c;if(h.$isParagraphNode(a)){const k=B.length;for(let l=0;l<k;l++){const m=B[l];if(!0===m.requiresParagraphStart){var d=a.getFirstChild();h.$isTextNode(d)||w(80);b.textNodeWithOffset={node:d,offset:0};b.joinedText=a.getTextContent();d=F(m,b);null!=d&&(b.markdownCriteria=
27
+ m,b.patternMatchResults=d)}}}}b=c;b.joinedText="";b.markdownCriteria={autoFormatKind:"noTransformation",regEx:/(?:)/,regExForAutoFormatting:/(?:)/,requiresParagraphStart:null};b.patternMatchResults={regExCaptureGroups:[]};b.triggerState=null;b.textNodeWithOffset=null}};
28
+ exports.registerMarkdownShortcuts=function(e,c){let f=null;return e.registerUpdateListener(({tags:a})=>{if(!1===a.has("historic")){a=L(e.getEditorState());if(null==a)var b=null;else a:{b=a;var d=f;if(null==b||null==d)b=null;else{var g=x.length;for(let k=0;k<g;k++){const l=x[k].triggerString,m=l.length,v=b.textContent.length,q=b.anchorOffset-m;if(!1===(!0===b.hasParentNode&&b.isSimpleText&&b.isSelectionCollapsed&&b.nodeKey===d.nodeKey&&b.anchorOffset!==d.anchorOffset&&0<=q&&q+m<=v&&b.textContent.substr(q,
29
+ m)===l&&b.textContent!==d.textContent)){b=null;break a}}b=K(e,b)}}null!=b&&J(e,b,c);f=a}else f=null})};
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # `@lexical/markdown`
2
+
3
+ This package contains markdown helpers and functionality for Lexical.
4
+
5
+ The package focuses on markdown conversion.
6
+
7
+ The package has 3 main functions:
8
+
9
+ 1. It imports a string and converts into Lexical and then converts markup within the imported nodes. See convertFromPlainTextUtils.js
10
+ 2. It exports Lexical to a plain text with markup. See convertToPlainTextUtils.js
11
+ 3. It autoformats newly typed text by converting the markdown + some trigger to the appropriate stylized text. See autoFormatUtils.js
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@lexical/markdown",
3
+ "description": "This package contains Markdown helpers and functionality for Lexical.",
4
+ "keywords": [
5
+ "lexical",
6
+ "editor",
7
+ "rich-text",
8
+ "markdown"
9
+ ],
10
+ "license": "MIT",
11
+ "version": "0.1.17",
12
+ "main": "LexicalMarkdown.js",
13
+ "peerDependencies": {
14
+ "lexical": "0.1.17"
15
+ },
16
+ "dependencies": {
17
+ "@lexical/utils": "0.1.17",
18
+ "@lexical/code": "0.1.17",
19
+ "@lexical/text": "0.1.17",
20
+ "@lexical/rich-text": "0.1.17",
21
+ "@lexical/list": "0.1.17",
22
+ "@lexical/link": "0.1.17"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/facebook/lexical",
27
+ "directory": "packages/lexical-markdown"
28
+ }
29
+ }