@blue-patterns/richtext 0.0.0-stage → 0.7.18
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/CHANGELOG.md +275 -0
- package/LICENSE +22 -0
- package/README.md +88 -2
- package/astryx.integration.mjs +11 -0
- package/blocks/RichTextEditorShowcase.template.mjs +13 -0
- package/blocks/RichTextEditorShowcase.tsx +19 -0
- package/dist/RichTextEditor.d.ts +282 -0
- package/dist/RichTextEditor.d.ts.map +1 -0
- package/dist/RichTextEditor.js +657 -0
- package/dist/RichTextEditorAutoLinkPlugin.d.ts +83 -0
- package/dist/RichTextEditorAutoLinkPlugin.d.ts.map +1 -0
- package/dist/RichTextEditorAutoLinkPlugin.js +99 -0
- package/dist/RichTextEditorToolbar.d.ts +186 -0
- package/dist/RichTextEditorToolbar.d.ts.map +1 -0
- package/dist/RichTextEditorToolbar.js +688 -0
- package/dist/RichTextView.d.ts +92 -0
- package/dist/RichTextView.d.ts.map +1 -0
- package/dist/RichTextView.js +267 -0
- package/dist/codeLanguages.d.ts +14 -0
- package/dist/codeLanguages.d.ts.map +1 -0
- package/dist/codeLanguages.js +34 -0
- package/dist/codeLineTokens.d.ts +53 -0
- package/dist/codeLineTokens.d.ts.map +1 -0
- package/dist/codeLineTokens.js +188 -0
- package/dist/editorNodes.d.ts +8 -0
- package/dist/editorNodes.d.ts.map +1 -0
- package/dist/editorNodes.js +30 -0
- package/dist/editorTheme.d.ts +7 -0
- package/dist/editorTheme.d.ts.map +1 -0
- package/dist/editorTheme.js +328 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/internal/CodeBackspacePlugin.d.ts +2 -0
- package/dist/internal/CodeBackspacePlugin.d.ts.map +1 -0
- package/dist/internal/CodeBackspacePlugin.js +70 -0
- package/dist/internal/CodeBlockHeaderPlugin.d.ts +11 -0
- package/dist/internal/CodeBlockHeaderPlugin.d.ts.map +1 -0
- package/dist/internal/CodeBlockHeaderPlugin.js +204 -0
- package/dist/internal/CodePastePlugin.d.ts +5 -0
- package/dist/internal/CodePastePlugin.d.ts.map +1 -0
- package/dist/internal/CodePastePlugin.js +104 -0
- package/dist/internal/CodeSyntaxPlugin.d.ts +8 -0
- package/dist/internal/CodeSyntaxPlugin.d.ts.map +1 -0
- package/dist/internal/CodeSyntaxPlugin.js +397 -0
- package/dist/internal/CodeTabPlugin.d.ts +2 -0
- package/dist/internal/CodeTabPlugin.d.ts.map +1 -0
- package/dist/internal/CodeTabPlugin.js +256 -0
- package/dist/internal/DisabledLinkGuardPlugin.d.ts +4 -0
- package/dist/internal/DisabledLinkGuardPlugin.d.ts.map +1 -0
- package/dist/internal/DisabledLinkGuardPlugin.js +93 -0
- package/dist/internal/ImageSourceCaretPlugin.d.ts +3 -0
- package/dist/internal/ImageSourceCaretPlugin.d.ts.map +1 -0
- package/dist/internal/ImageSourceCaretPlugin.js +105 -0
- package/dist/internal/ListItemShortcutsPlugin.d.ts +6 -0
- package/dist/internal/ListItemShortcutsPlugin.d.ts.map +1 -0
- package/dist/internal/ListItemShortcutsPlugin.js +314 -0
- package/dist/internal/MarkdownExtensionsPlugin.d.ts +17 -0
- package/dist/internal/MarkdownExtensionsPlugin.d.ts.map +1 -0
- package/dist/internal/MarkdownExtensionsPlugin.js +185 -0
- package/dist/internal/SelectAllScopePlugin.d.ts +2 -0
- package/dist/internal/SelectAllScopePlugin.d.ts.map +1 -0
- package/dist/internal/SelectAllScopePlugin.js +67 -0
- package/dist/internal/TableColumnFloorPlugin.d.ts +12 -0
- package/dist/internal/TableColumnFloorPlugin.d.ts.map +1 -0
- package/dist/internal/TableColumnFloorPlugin.js +84 -0
- package/dist/internal/TablePastePlugin.d.ts +9 -0
- package/dist/internal/TablePastePlugin.d.ts.map +1 -0
- package/dist/internal/TablePastePlugin.js +183 -0
- package/dist/internal/TableScrollRegionPlugin.d.ts +9 -0
- package/dist/internal/TableScrollRegionPlugin.d.ts.map +1 -0
- package/dist/internal/TableScrollRegionPlugin.js +95 -0
- package/dist/internal/TaskCheckboxPlugin.d.ts +7 -0
- package/dist/internal/TaskCheckboxPlugin.d.ts.map +1 -0
- package/dist/internal/TaskCheckboxPlugin.js +193 -0
- package/dist/internal/ToolbarLinkPopover.d.ts +74 -0
- package/dist/internal/ToolbarLinkPopover.d.ts.map +1 -0
- package/dist/internal/ToolbarLinkPopover.js +292 -0
- package/dist/linkEditing.d.ts +6 -0
- package/dist/linkEditing.d.ts.map +1 -0
- package/dist/linkEditing.js +20 -0
- package/dist/linkForm.d.ts +28 -0
- package/dist/linkForm.d.ts.map +1 -0
- package/dist/linkForm.js +137 -0
- package/dist/linkUtils.d.ts +27 -0
- package/dist/linkUtils.d.ts.map +1 -0
- package/dist/linkUtils.js +101 -0
- package/dist/markdown.d.ts +17 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +17 -0
- package/dist/markdownCharacterReferences.d.ts +135 -0
- package/dist/markdownCharacterReferences.d.ts.map +1 -0
- package/dist/markdownCharacterReferences.js +1127 -0
- package/dist/markdownCodeFence.d.ts +4 -0
- package/dist/markdownCodeFence.d.ts.map +1 -0
- package/dist/markdownCodeFence.js +167 -0
- package/dist/markdownExtensionNode.d.ts +88 -0
- package/dist/markdownExtensionNode.d.ts.map +1 -0
- package/dist/markdownExtensionNode.js +248 -0
- package/dist/markdownExtensions.d.ts +115 -0
- package/dist/markdownExtensions.d.ts.map +1 -0
- package/dist/markdownExtensions.js +392 -0
- package/dist/markdownHardLineBreak.d.ts +25 -0
- package/dist/markdownHardLineBreak.d.ts.map +1 -0
- package/dist/markdownHardLineBreak.js +59 -0
- package/dist/markdownHeading.d.ts +20 -0
- package/dist/markdownHeading.d.ts.map +1 -0
- package/dist/markdownHeading.js +30 -0
- package/dist/markdownLink.d.ts +5 -0
- package/dist/markdownLink.d.ts.map +1 -0
- package/dist/markdownLink.js +80 -0
- package/dist/markdownListExport.d.ts +3 -0
- package/dist/markdownListExport.d.ts.map +1 -0
- package/dist/markdownListExport.js +68 -0
- package/dist/markdownListIndentation.d.ts +8 -0
- package/dist/markdownListIndentation.d.ts.map +1 -0
- package/dist/markdownListIndentation.js +101 -0
- package/dist/markdownOrderedList.d.ts +3 -0
- package/dist/markdownOrderedList.d.ts.map +1 -0
- package/dist/markdownOrderedList.js +51 -0
- package/dist/markdownQuote.d.ts +22 -0
- package/dist/markdownQuote.d.ts.map +1 -0
- package/dist/markdownQuote.js +32 -0
- package/dist/markdownSerializers.d.ts +49 -0
- package/dist/markdownSerializers.d.ts.map +1 -0
- package/dist/markdownSerializers.js +83 -0
- package/dist/markdownSource.d.ts +71 -0
- package/dist/markdownSource.d.ts.map +1 -0
- package/dist/markdownSource.js +804 -0
- package/dist/markdownSpaceRuns.d.ts +24 -0
- package/dist/markdownSpaceRuns.d.ts.map +1 -0
- package/dist/markdownSpaceRuns.js +147 -0
- package/dist/markdownTable.d.ts +35 -0
- package/dist/markdownTable.d.ts.map +1 -0
- package/dist/markdownTable.js +227 -0
- package/dist/markdownTaskList.d.ts +48 -0
- package/dist/markdownTaskList.d.ts.map +1 -0
- package/dist/markdownTaskList.js +166 -0
- package/dist/markdownThematicBreak.d.ts +3 -0
- package/dist/markdownThematicBreak.d.ts.map +1 -0
- package/dist/markdownThematicBreak.js +55 -0
- package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts +12 -0
- package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/layerPortalBoundary.stylex.js +16 -0
- package/dist/richtext.css +213 -0
- package/dist/textSemantics.d.ts +3 -0
- package/dist/textSemantics.d.ts.map +1 -0
- package/dist/textSemantics.js +87 -0
- package/package.json +158 -3
- package/src/RichTextEditor.doc.mjs +308 -0
- package/src/RichTextEditor.tsx +1109 -0
- package/src/RichTextEditorAutoLinkPlugin.doc.mjs +43 -0
- package/src/RichTextEditorAutoLinkPlugin.tsx +126 -0
- package/src/RichTextEditorToolbar.doc.mjs +92 -0
- package/src/RichTextEditorToolbar.tsx +871 -0
- package/src/RichTextView.doc.mjs +101 -0
- package/src/RichTextView.tsx +351 -0
- package/src/codeLanguages.ts +42 -0
- package/src/codeLineTokens.ts +241 -0
- package/src/doc-mjs.d.ts +23 -0
- package/src/editorNodes.ts +44 -0
- package/src/editorTheme.ts +550 -0
- package/src/index.ts +59 -0
- package/src/internal/CodeBackspacePlugin.tsx +96 -0
- package/src/internal/CodeBlockHeaderPlugin.tsx +282 -0
- package/src/internal/CodePastePlugin.tsx +144 -0
- package/src/internal/CodeSyntaxPlugin.tsx +493 -0
- package/src/internal/CodeTabPlugin.tsx +298 -0
- package/src/internal/DisabledLinkGuardPlugin.tsx +93 -0
- package/src/internal/ImageSourceCaretPlugin.tsx +159 -0
- package/src/internal/ListItemShortcutsPlugin.tsx +404 -0
- package/src/internal/MarkdownExtensionsPlugin.tsx +289 -0
- package/src/internal/SelectAllScopePlugin.tsx +86 -0
- package/src/internal/TableColumnFloorPlugin.tsx +99 -0
- package/src/internal/TablePastePlugin.tsx +223 -0
- package/src/internal/TableScrollRegionPlugin.tsx +107 -0
- package/src/internal/TaskCheckboxPlugin.tsx +246 -0
- package/src/internal/ToolbarLinkPopover.tsx +392 -0
- package/src/linkEditing.ts +20 -0
- package/src/linkForm.tsx +156 -0
- package/src/linkUtils.ts +106 -0
- package/src/markdown.ts +25 -0
- package/src/markdownCharacterReferences.ts +1137 -0
- package/src/markdownCodeFence.ts +216 -0
- package/src/markdownExtensionNode.ts +339 -0
- package/src/markdownExtensions.ts +496 -0
- package/src/markdownHardLineBreak.ts +73 -0
- package/src/markdownHeading.ts +33 -0
- package/src/markdownLink.ts +86 -0
- package/src/markdownListExport.ts +85 -0
- package/src/markdownListIndentation.ts +105 -0
- package/src/markdownOrderedList.ts +60 -0
- package/src/markdownQuote.ts +35 -0
- package/src/markdownSerializers.ts +108 -0
- package/src/markdownSource.ts +1045 -0
- package/src/markdownSpaceRuns.ts +164 -0
- package/src/markdownTable.ts +309 -0
- package/src/markdownTaskList.ts +222 -0
- package/src/markdownThematicBreak.ts +66 -0
- package/src/privateAttributes/layerPortalBoundary.stylex.ts +18 -0
- package/src/textSemantics.ts +104 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownCodeFence.d.ts","sourceRoot":"","sources":["../src/markdownCodeFence.ts"],"names":[],"mappings":"AAyBA,OAAO,EAGL,KAAK,2BAA2B,EACjC,MAAM,mBAAmB,CAAC;AAuJ3B,eAAO,MAAM,aAAa,EAAE,2BAqB3B,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,2BAYxB,CAAC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file markdownCodeFence.ts
|
|
5
|
+
* @input Uses @lexical/markdown (CODE) and @lexical/code.
|
|
6
|
+
* @output Exports BACKTICK_CODE and TILDE_CODE, the transformers for fenced
|
|
7
|
+
* code blocks opened with backticks and with tildes.
|
|
8
|
+
* @position Part of DEFAULT_TRANSFORMERS (markdownTable.ts): BACKTICK_CODE
|
|
9
|
+
* stands in for Lexical's CODE, and TILDE_CODE reads the `~~~` fences CODE
|
|
10
|
+
* does not (CommonMark 0.31 §4.5, spec:AST-061 FR5). Both read the opening
|
|
11
|
+
* line as core Markdown does: the rest of the line after the fence is the
|
|
12
|
+
* info string, never code; the language is its first word, after any
|
|
13
|
+
* spaces (so `~~~ c++` reads `c++`), as core and CommonMark read it. A
|
|
14
|
+
* block closes at
|
|
15
|
+
* a fence of its own character at least as long, or at the end of the
|
|
16
|
+
* document, and its code is exactly the lines between, blank lines
|
|
17
|
+
* included, each less as much indentation as the opening fence has (up to
|
|
18
|
+
* three spaces), as core reads it. Each keeps its fence and info
|
|
19
|
+
* string (spec:AST-062): an edited block exports them as written, with its
|
|
20
|
+
* language first when that was changed, and its fence lengthened when its
|
|
21
|
+
* code holds a run of the fence's character that long.
|
|
22
|
+
* A one-line backtick block (```` ```code``` ````) stays Lexical's.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { $createCodeNode, $isCodeNode } from '@lexical/code';
|
|
26
|
+
import { CODE } from '@lexical/markdown';
|
|
27
|
+
import { $createTextNode, $getState, $setState, createState } from 'lexical';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The tilde fence a code block was opened with; null for a backtick block.
|
|
31
|
+
* Lexical's own fence state holds only backtick fences.
|
|
32
|
+
*/
|
|
33
|
+
const tildeFence = createState('astryxTildeFence', {
|
|
34
|
+
parse: value => typeof value === 'string' && /^~{3,}$/.test(value) ? value : null
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The backtick fence a code block was opened with, when longer than three:
|
|
39
|
+
* Lexical's own fence state is set only by its transformer, which this file
|
|
40
|
+
* does not use to build blocks.
|
|
41
|
+
*/
|
|
42
|
+
const backtickFence = createState('astryxBacktickFence', {
|
|
43
|
+
parse: value => typeof value === 'string' && /^`{4,}$/.test(value) ? value : null
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** The info string a code block's opening fence carried, as written. */
|
|
47
|
+
const fenceInfo = createState('astryxFenceInfo', {
|
|
48
|
+
parse: value => typeof value === 'string' && value !== '' ? value : null
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** The language core Markdown reads from an info string, or null. */
|
|
52
|
+
function languageOf(info) {
|
|
53
|
+
return /^\S+/.exec(info.trimStart())?.[0] ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** An opening fence line: its indentation, its fence, and the info string. */
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reads an opening fence line: up to three spaces of indentation, then the
|
|
60
|
+
* fence (CommonMark 0.31 §4.5), as core Markdown reads it.
|
|
61
|
+
*/
|
|
62
|
+
function readOpening(line) {
|
|
63
|
+
const match = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
|
|
64
|
+
if (match == null) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const [, indentation, fence, info] = match;
|
|
68
|
+
// A backtick fence's info string holds no backtick.
|
|
69
|
+
return fence.startsWith('`') && info.includes('`') ? null : {
|
|
70
|
+
indentation: indentation.length,
|
|
71
|
+
fence,
|
|
72
|
+
info
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The longest run of three or more of `character` in `text`, or 0. */
|
|
77
|
+
function longestFenceRun(text, character) {
|
|
78
|
+
return Math.max(0, ...(text.match(character === '`' ? /`{3,}/g : /~{3,}/g) ?? []).map(run => run.length));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Builds the code block an opening line starts, from the lines after it up
|
|
83
|
+
* to its closing fence, and returns the index of the last line it used.
|
|
84
|
+
*/
|
|
85
|
+
function $importFencedCode(rootNode, lines, startLineIndex, opening) {
|
|
86
|
+
const {
|
|
87
|
+
indentation,
|
|
88
|
+
fence,
|
|
89
|
+
info
|
|
90
|
+
} = opening;
|
|
91
|
+
const closing = new RegExp(`^ {0,3}${fence[0] === '`' ? '`' : '~'}{${fence.length},}[ \\t]*$`);
|
|
92
|
+
// Each code line loses as much indentation as the opening fence has.
|
|
93
|
+
const fenceIndentation = new RegExp(`^ {0,${indentation}}`);
|
|
94
|
+
let end = startLineIndex + 1;
|
|
95
|
+
while (end < lines.length && !closing.test(lines[end])) {
|
|
96
|
+
end++;
|
|
97
|
+
}
|
|
98
|
+
// A fence left open runs to the end of the document, whose final line
|
|
99
|
+
// ending starts no line of code (CommonMark 0.31 §4.5), as core reads it.
|
|
100
|
+
const codeEnd = end === lines.length && end > startLineIndex + 1 && lines[end - 1] === '' ? end - 1 : end;
|
|
101
|
+
// The code is the lines between the fences, exactly: Lexical's own
|
|
102
|
+
// transformer trims a space from the first line and drops blank first and
|
|
103
|
+
// last lines, adjustments meant for its own reading of the opening line.
|
|
104
|
+
const block = $createCodeNode(languageOf(info) ?? undefined);
|
|
105
|
+
block.append($createTextNode(lines.slice(startLineIndex + 1, codeEnd).map(line => line.replace(fenceIndentation, '')).join('\n')));
|
|
106
|
+
rootNode.append(block);
|
|
107
|
+
if (fence.startsWith('~')) {
|
|
108
|
+
$setState(block, tildeFence, fence);
|
|
109
|
+
} else if (fence.length > 3) {
|
|
110
|
+
$setState(block, backtickFence, fence);
|
|
111
|
+
}
|
|
112
|
+
$setState(block, fenceInfo, info.trimEnd());
|
|
113
|
+
return Math.min(end, lines.length - 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Writes a code block that keeps its fence or info string; null otherwise. */
|
|
117
|
+
const exportFencedCode = node => {
|
|
118
|
+
if (!$isCodeNode(node)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const tilde = $getState(node, tildeFence);
|
|
122
|
+
const backticks = $getState(node, backtickFence);
|
|
123
|
+
const info = $getState(node, fenceInfo);
|
|
124
|
+
if (tilde == null && backticks == null && info == null) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const text = node.getTextContent();
|
|
128
|
+
const language = node.getLanguage() ?? '';
|
|
129
|
+
let written = info ?? '';
|
|
130
|
+
if ((languageOf(written) ?? '') !== language) {
|
|
131
|
+
// The language changed: it leads, before the rest of the info string.
|
|
132
|
+
const previous = languageOf(written);
|
|
133
|
+
const rest = previous == null ? '' : written.trimStart().slice(previous.length).trim();
|
|
134
|
+
written = rest === '' ? language : `${language} ${rest}`;
|
|
135
|
+
}
|
|
136
|
+
// A fence outlasts any run of its character in the code.
|
|
137
|
+
const fence = tilde != null ? '~'.repeat(Math.max(tilde.length, longestFenceRun(text, '~') + 1)) : '`'.repeat(Math.max((backticks ?? '```').length, longestFenceRun(text, '`') + 1));
|
|
138
|
+
return `${fence}${written}${text === '' ? '' : `\n${text}`}\n${fence}`;
|
|
139
|
+
};
|
|
140
|
+
export const BACKTICK_CODE = {
|
|
141
|
+
...CODE,
|
|
142
|
+
handleImportAfterStartMatch: args => {
|
|
143
|
+
const opening = readOpening(args.lines[args.startLineIndex]);
|
|
144
|
+
if (opening == null) {
|
|
145
|
+
return CODE.handleImportAfterStartMatch?.(args) ?? null;
|
|
146
|
+
}
|
|
147
|
+
return [true, $importFencedCode(args.rootNode, args.lines, args.startLineIndex, opening)];
|
|
148
|
+
},
|
|
149
|
+
export: (node, exportChildren) => exportFencedCode(node, exportChildren) ?? CODE.export?.(node, exportChildren) ?? null
|
|
150
|
+
};
|
|
151
|
+
export const TILDE_CODE = {
|
|
152
|
+
...CODE,
|
|
153
|
+
regExpStart: /^( {0,3}~{3,})(.*)$/,
|
|
154
|
+
regExpEnd: {
|
|
155
|
+
optional: true,
|
|
156
|
+
regExp: /^ {0,3}~{3,}[ \t]*$/
|
|
157
|
+
},
|
|
158
|
+
handleImportAfterStartMatch: ({
|
|
159
|
+
lines,
|
|
160
|
+
rootNode,
|
|
161
|
+
startLineIndex
|
|
162
|
+
}) => {
|
|
163
|
+
const opening = readOpening(lines[startLineIndex]);
|
|
164
|
+
return opening == null ? null : [true, $importFencedCode(rootNode, lines, startLineIndex, opening)];
|
|
165
|
+
},
|
|
166
|
+
export: (node, exportChildren) => exportFencedCode(node, exportChildren) ?? null
|
|
167
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file markdownExtensionNode.ts
|
|
3
|
+
* @input Uses lexical (DecoratorNode, node serialization, text formats).
|
|
4
|
+
* @output Exports RichTextExtensionNode, the editor node for one Markdown
|
|
5
|
+
* plugin node, with $createRichTextExtensionNode and
|
|
6
|
+
* $isRichTextExtensionNode.
|
|
7
|
+
* @position Registered with every RichText surface and serializer
|
|
8
|
+
* (editorNodes.ts), so stored state holding plugin nodes always loads. A
|
|
9
|
+
* node is atomic — the editor cannot type into it — and keeps the exact
|
|
10
|
+
* source it was read from, which is what it exports (spec:AST-064 FR7,
|
|
11
|
+
* FR10). Stored state keeps that source, the plugin's name and protocol
|
|
12
|
+
* version, the data the plugin derived for rendering (FR11), and the text
|
|
13
|
+
* formats the node sits inside, so a node inside emphasis, strong, or
|
|
14
|
+
* strikethrough stays inside it. Imports no React: the client surfaces
|
|
15
|
+
* register how an editor draws plugin nodes.
|
|
16
|
+
*/
|
|
17
|
+
import { DecoratorNode, type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalEditor, type LexicalNode, type NodeKey, type NodeSelection, type RangeSelection, type TextNode, type SerializedLexicalNode, type Spread, type TextFormatType } from 'lexical';
|
|
18
|
+
import type { MarkdownPluginData } from '@blue-patterns/core/Markdown/plugins';
|
|
19
|
+
/** What one plugin node is: the parsed node's identity, data, and source. */
|
|
20
|
+
export interface RichTextExtensionNodeFacts {
|
|
21
|
+
readonly plugin: string;
|
|
22
|
+
readonly apiVersion: number;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly display: 'inline' | 'block';
|
|
25
|
+
readonly data: MarkdownPluginData;
|
|
26
|
+
readonly source: string;
|
|
27
|
+
}
|
|
28
|
+
export type SerializedRichTextExtensionNode = Spread<RichTextExtensionNodeFacts & {
|
|
29
|
+
/** The text formats the node sits inside, as a text node's format bits. */
|
|
30
|
+
readonly format: number;
|
|
31
|
+
}, SerializedLexicalNode>;
|
|
32
|
+
/** Reads stored facts defensively: a missing field becomes an empty value. */
|
|
33
|
+
export declare function factsFrom(value: Record<string, unknown>): RichTextExtensionNodeFacts;
|
|
34
|
+
/**
|
|
35
|
+
* Draws a plugin node's content in one editor, inside the text formats the
|
|
36
|
+
* node sits in; `nodeKey` lets it select the node. The client surfaces
|
|
37
|
+
* register one (MarkdownExtensionsPlugin); without one, a node shows its
|
|
38
|
+
* source.
|
|
39
|
+
*/
|
|
40
|
+
export type RichTextExtensionNodeDecorator = (facts: RichTextExtensionNodeFacts, format: number, nodeKey: NodeKey) => unknown;
|
|
41
|
+
/** Sets how `editor` draws plugin nodes; returns a function that unsets it. */
|
|
42
|
+
export declare function setRichTextExtensionNodeDecorator(editor: LexicalEditor, decorator: RichTextExtensionNodeDecorator): () => void;
|
|
43
|
+
export declare class RichTextExtensionNode extends DecoratorNode<unknown> {
|
|
44
|
+
__facts: RichTextExtensionNodeFacts;
|
|
45
|
+
/**
|
|
46
|
+
* The text formats the node sits inside, as a text node's format bits. Only
|
|
47
|
+
* inline nodes carry any.
|
|
48
|
+
*/
|
|
49
|
+
__format: number;
|
|
50
|
+
static getType(): string;
|
|
51
|
+
static clone(node: RichTextExtensionNode): RichTextExtensionNode;
|
|
52
|
+
static importJSON(serialized: SerializedLexicalNode & Record<string, unknown>): RichTextExtensionNode;
|
|
53
|
+
static importDOM(): DOMConversionMap;
|
|
54
|
+
constructor(facts?: RichTextExtensionNodeFacts, format?: number, key?: NodeKey);
|
|
55
|
+
exportJSON(): SerializedRichTextExtensionNode;
|
|
56
|
+
/** The node's identity, data, and source. */
|
|
57
|
+
getFacts(): RichTextExtensionNodeFacts;
|
|
58
|
+
/** The exact Markdown the node was read from. */
|
|
59
|
+
getSource(): string;
|
|
60
|
+
/** The text formats the node sits inside, as a text node's format bits. */
|
|
61
|
+
getFormat(): number;
|
|
62
|
+
hasFormat(type: TextFormatType): boolean;
|
|
63
|
+
/** Puts the node inside, or takes it out of, one text format. */
|
|
64
|
+
setFormatFlag(type: TextFormatType, isOn: boolean): this;
|
|
65
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
66
|
+
exportDOM(): DOMExportOutput;
|
|
67
|
+
updateDOM(prevNode: RichTextExtensionNode, dom: HTMLElement): false;
|
|
68
|
+
isInline(): boolean;
|
|
69
|
+
/** The node's text is its source, so plain-text copies and search see it. */
|
|
70
|
+
getTextContent(): string;
|
|
71
|
+
decorate(editor: LexicalEditor): unknown;
|
|
72
|
+
}
|
|
73
|
+
export declare function $createRichTextExtensionNode(facts: RichTextExtensionNodeFacts, format?: number): RichTextExtensionNode;
|
|
74
|
+
export declare function $isRichTextExtensionNode(node: LexicalNode | null | undefined): node is RichTextExtensionNode;
|
|
75
|
+
/**
|
|
76
|
+
* The first text node a range selects characters of, as Lexical's formatText
|
|
77
|
+
* reads it: a range that starts at the end of a text node starts in the next.
|
|
78
|
+
* Null when the range selects no text, only nodes.
|
|
79
|
+
*/
|
|
80
|
+
export declare function $firstSelectedText(selection: RangeSelection): TextNode | null;
|
|
81
|
+
/**
|
|
82
|
+
* The inline plugin nodes a selection holds when it holds nothing else: a
|
|
83
|
+
* range that selects no text, only nodes, or a node selection of plugin
|
|
84
|
+
* nodes alone. Null otherwise. A format shown or toggled then belongs to
|
|
85
|
+
* them.
|
|
86
|
+
*/
|
|
87
|
+
export declare function $selectedExtensionNodesOnly(selection: RangeSelection | NodeSelection): RichTextExtensionNode[] | null;
|
|
88
|
+
//# sourceMappingURL=markdownExtensionNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownExtensionNode.d.ts","sourceRoot":"","sources":["../src/markdownExtensionNode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file markdownExtensionNode.ts
|
|
5
|
+
* @input Uses lexical (DecoratorNode, node serialization, text formats).
|
|
6
|
+
* @output Exports RichTextExtensionNode, the editor node for one Markdown
|
|
7
|
+
* plugin node, with $createRichTextExtensionNode and
|
|
8
|
+
* $isRichTextExtensionNode.
|
|
9
|
+
* @position Registered with every RichText surface and serializer
|
|
10
|
+
* (editorNodes.ts), so stored state holding plugin nodes always loads. A
|
|
11
|
+
* node is atomic — the editor cannot type into it — and keeps the exact
|
|
12
|
+
* source it was read from, which is what it exports (spec:AST-064 FR7,
|
|
13
|
+
* FR10). Stored state keeps that source, the plugin's name and protocol
|
|
14
|
+
* version, the data the plugin derived for rendering (FR11), and the text
|
|
15
|
+
* formats the node sits inside, so a node inside emphasis, strong, or
|
|
16
|
+
* strikethrough stays inside it. Imports no React: the client surfaces
|
|
17
|
+
* register how an editor draws plugin nodes.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { $isNodeSelection, $isTextNode, DecoratorNode, TEXT_TYPE_TO_FORMAT } from 'lexical';
|
|
21
|
+
|
|
22
|
+
/** What one plugin node is: the parsed node's identity, data, and source. */
|
|
23
|
+
|
|
24
|
+
/** Reads stored facts defensively: a missing field becomes an empty value. */
|
|
25
|
+
export function factsFrom(value) {
|
|
26
|
+
const {
|
|
27
|
+
plugin,
|
|
28
|
+
apiVersion,
|
|
29
|
+
name,
|
|
30
|
+
display,
|
|
31
|
+
data,
|
|
32
|
+
source
|
|
33
|
+
} = value;
|
|
34
|
+
return {
|
|
35
|
+
plugin: typeof plugin === 'string' ? plugin : '',
|
|
36
|
+
apiVersion: typeof apiVersion === 'number' ? apiVersion : 0,
|
|
37
|
+
name: typeof name === 'string' ? name : '',
|
|
38
|
+
display: display === 'block' ? 'block' : 'inline',
|
|
39
|
+
data: data ?? null,
|
|
40
|
+
source: typeof source === 'string' ? source : ''
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Draws a plugin node's content in one editor, inside the text formats the
|
|
46
|
+
* node sits in; `nodeKey` lets it select the node. The client surfaces
|
|
47
|
+
* register one (MarkdownExtensionsPlugin); without one, a node shows its
|
|
48
|
+
* source.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const decorators = new WeakMap();
|
|
52
|
+
|
|
53
|
+
/** Sets how `editor` draws plugin nodes; returns a function that unsets it. */
|
|
54
|
+
export function setRichTextExtensionNodeDecorator(editor, decorator) {
|
|
55
|
+
decorators.set(editor, decorator);
|
|
56
|
+
return () => {
|
|
57
|
+
if (decorators.get(editor) === decorator) {
|
|
58
|
+
decorators.delete(editor);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The attribute that carries a node's facts and formats in its element, so
|
|
65
|
+
* copying it as HTML — the browser's own copy, or the editor's HTML export —
|
|
66
|
+
* and pasting it into any RichText editor brings back the same node with the
|
|
67
|
+
* same source and marks.
|
|
68
|
+
*/
|
|
69
|
+
const FACTS_ATTRIBUTE = 'data-markdown-extension';
|
|
70
|
+
function factsAttribute(node) {
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
...node.__facts,
|
|
73
|
+
format: node.__format
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function convertExtensionElement(element) {
|
|
77
|
+
let node = null;
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(element.getAttribute(FACTS_ATTRIBUTE) ?? '');
|
|
80
|
+
if (parsed != null && typeof parsed === 'object') {
|
|
81
|
+
const value = parsed;
|
|
82
|
+
node = $createRichTextExtensionNode(factsFrom(value), typeof value.format === 'number' ? value.format : 0);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
node = null;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
node,
|
|
89
|
+
// The element's content is the node's rendering, not more document.
|
|
90
|
+
forChild: () => null
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const importExtensionElement = element => element.hasAttribute(FACTS_ATTRIBUTE) ? {
|
|
94
|
+
conversion: convertExtensionElement,
|
|
95
|
+
priority: 4
|
|
96
|
+
} : null;
|
|
97
|
+
export class RichTextExtensionNode extends DecoratorNode {
|
|
98
|
+
/**
|
|
99
|
+
* The text formats the node sits inside, as a text node's format bits. Only
|
|
100
|
+
* inline nodes carry any.
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
static getType() {
|
|
104
|
+
return 'astryx-markdown-extension';
|
|
105
|
+
}
|
|
106
|
+
static clone(node) {
|
|
107
|
+
return new RichTextExtensionNode(node.__facts, node.__format, node.__key);
|
|
108
|
+
}
|
|
109
|
+
static importJSON(serialized) {
|
|
110
|
+
// Stored state is read defensively, so a damaged node still loads.
|
|
111
|
+
const {
|
|
112
|
+
format
|
|
113
|
+
} = serialized;
|
|
114
|
+
return $createRichTextExtensionNode(factsFrom(serialized), typeof format === 'number' ? format : 0).updateFromJSON(serialized);
|
|
115
|
+
}
|
|
116
|
+
static importDOM() {
|
|
117
|
+
return {
|
|
118
|
+
span: importExtensionElement,
|
|
119
|
+
div: importExtensionElement
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The Yjs binding and `$create` call the constructor with no arguments.
|
|
124
|
+
// The binding reads each class's default properties from that node.
|
|
125
|
+
// With no facts, the node gets the values that damaged stored facts get.
|
|
126
|
+
constructor(facts = factsFrom({}), format = 0, key) {
|
|
127
|
+
super(key);
|
|
128
|
+
this.__facts = facts;
|
|
129
|
+
this.__format = facts.display === 'inline' ? format : 0;
|
|
130
|
+
}
|
|
131
|
+
exportJSON() {
|
|
132
|
+
const latest = this.getLatest();
|
|
133
|
+
return {
|
|
134
|
+
...super.exportJSON(),
|
|
135
|
+
...latest.__facts,
|
|
136
|
+
format: latest.__format
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The node's identity, data, and source. */
|
|
141
|
+
getFacts() {
|
|
142
|
+
return this.getLatest().__facts;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The exact Markdown the node was read from. */
|
|
146
|
+
getSource() {
|
|
147
|
+
return this.getLatest().__facts.source;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The text formats the node sits inside, as a text node's format bits. */
|
|
151
|
+
getFormat() {
|
|
152
|
+
return this.getLatest().__format;
|
|
153
|
+
}
|
|
154
|
+
hasFormat(type) {
|
|
155
|
+
return (this.getFormat() & TEXT_TYPE_TO_FORMAT[type]) !== 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Puts the node inside, or takes it out of, one text format. */
|
|
159
|
+
setFormatFlag(type, isOn) {
|
|
160
|
+
const writable = this.getWritable();
|
|
161
|
+
const flag = TEXT_TYPE_TO_FORMAT[type];
|
|
162
|
+
writable.__format = isOn ? writable.__format | flag : writable.__format & ~flag;
|
|
163
|
+
return writable;
|
|
164
|
+
}
|
|
165
|
+
createDOM(config) {
|
|
166
|
+
const element = document.createElement(this.__facts.display === 'inline' ? 'span' : 'div');
|
|
167
|
+
element.setAttribute(FACTS_ATTRIBUTE, factsAttribute(this));
|
|
168
|
+
if (this.__facts.display === 'block') {
|
|
169
|
+
// A block node is spaced and measured as Markdown spaces its block.
|
|
170
|
+
const className = config.theme.markdownExtensionBlock;
|
|
171
|
+
if (typeof className === 'string' && className !== '') {
|
|
172
|
+
element.className = className;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return element;
|
|
176
|
+
}
|
|
177
|
+
exportDOM() {
|
|
178
|
+
const element = document.createElement(this.__facts.display === 'inline' ? 'span' : 'div');
|
|
179
|
+
element.setAttribute(FACTS_ATTRIBUTE, factsAttribute(this));
|
|
180
|
+
element.textContent = this.__facts.source;
|
|
181
|
+
return {
|
|
182
|
+
element
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
updateDOM(prevNode, dom) {
|
|
186
|
+
// A mark toggled on the node changes what its element carries.
|
|
187
|
+
if (prevNode.__format !== this.__format) {
|
|
188
|
+
dom.setAttribute(FACTS_ATTRIBUTE, factsAttribute(this));
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
isInline() {
|
|
193
|
+
return this.__facts.display === 'inline';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The node's text is its source, so plain-text copies and search see it. */
|
|
197
|
+
getTextContent() {
|
|
198
|
+
return this.getLatest().__facts.source;
|
|
199
|
+
}
|
|
200
|
+
decorate(editor) {
|
|
201
|
+
return decorators.get(editor)?.(this.__facts, this.__format, this.__key) ?? this.__facts.source;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export function $createRichTextExtensionNode(facts, format = 0) {
|
|
205
|
+
return new RichTextExtensionNode(facts, format);
|
|
206
|
+
}
|
|
207
|
+
export function $isRichTextExtensionNode(node) {
|
|
208
|
+
return node instanceof RichTextExtensionNode;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The first text node a range selects characters of, as Lexical's formatText
|
|
213
|
+
* reads it: a range that starts at the end of a text node starts in the next.
|
|
214
|
+
* Null when the range selects no text, only nodes.
|
|
215
|
+
*/
|
|
216
|
+
export function $firstSelectedText(selection) {
|
|
217
|
+
const texts = selection.getNodes().filter($isTextNode);
|
|
218
|
+
const isBackward = selection.isBackward();
|
|
219
|
+
const start = isBackward ? selection.focus : selection.anchor;
|
|
220
|
+
const end = isBackward ? selection.anchor : selection.focus;
|
|
221
|
+
let first = texts[0];
|
|
222
|
+
let startOffset = start.type === 'element' ? 0 : start.offset;
|
|
223
|
+
if (first != null && start.type === 'text' && startOffset === first.getTextContentSize()) {
|
|
224
|
+
first = texts[1];
|
|
225
|
+
startOffset = 0;
|
|
226
|
+
}
|
|
227
|
+
const last = texts[texts.length - 1];
|
|
228
|
+
if (first == null || last == null) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const endOffset = end.type === 'text' ? end.offset : last.getTextContentSize();
|
|
232
|
+
return first.is(last) && startOffset === endOffset ? null : first;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The inline plugin nodes a selection holds when it holds nothing else: a
|
|
237
|
+
* range that selects no text, only nodes, or a node selection of plugin
|
|
238
|
+
* nodes alone. Null otherwise. A format shown or toggled then belongs to
|
|
239
|
+
* them.
|
|
240
|
+
*/
|
|
241
|
+
export function $selectedExtensionNodesOnly(selection) {
|
|
242
|
+
if ($isNodeSelection(selection)) {
|
|
243
|
+
const selected = selection.getNodes();
|
|
244
|
+
return selected.length > 0 && selected.every(node => $isRichTextExtensionNode(node) && node.isInline()) ? selected : null;
|
|
245
|
+
}
|
|
246
|
+
const nodes = selection.getNodes().filter(node => $isRichTextExtensionNode(node) && node.isInline());
|
|
247
|
+
return nodes.length > 0 && $firstSelectedText(selection) == null ? nodes : null;
|
|
248
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file markdownExtensions.ts
|
|
3
|
+
* @input Uses core's server-safe plugin protocol (getMarkdownPluginCapabilities)
|
|
4
|
+
* and parser (parseMarkdownAst), lexical, and RichTextExtensionNode.
|
|
5
|
+
* @output Exports createRichTextExtension, RichTextExtensionError, and the
|
|
6
|
+
* RichTextMarkdownExtension type; and, for RichText's own Markdown import and
|
|
7
|
+
* export, the configuration check, the source shielding and restoring that
|
|
8
|
+
* puts core-recognized plugin nodes into the editor, and the transformers
|
|
9
|
+
* that export them.
|
|
10
|
+
* @position Implements spec:AST-064 FR1–FR6 and FR10 for RichText's Markdown
|
|
11
|
+
* import and export. Server-safe: imports no React, DOM, or client module.
|
|
12
|
+
*
|
|
13
|
+
* A plugin is the entry core's createMarkdownPlugin returns; RichText adopts it
|
|
14
|
+
* as it is (FR1), refusing a plugin that declares a transform (FR4, DEC-3).
|
|
15
|
+
* Before Lexical imports a chunk of Markdown, core's parser finds the adopted
|
|
16
|
+
* plugins' nodes in it, and each node's exact source becomes a private-use
|
|
17
|
+
* stand-in that Lexical's transformers read as plain text (FR5, DEC-4). Core
|
|
18
|
+
* reports where a top-level block node sits but not an inline one, so each
|
|
19
|
+
* node is placed at the occurrence of its source that core itself recognizes:
|
|
20
|
+
* replacing that occurrence, and only that one, removes exactly that node from
|
|
21
|
+
* core's parse. After the import, each stand-in becomes an atomic
|
|
22
|
+
* RichTextExtensionNode holding the node's source and data.
|
|
23
|
+
*/
|
|
24
|
+
import type { Transformer } from '@lexical/markdown';
|
|
25
|
+
import { TextNode, type ElementNode } from 'lexical';
|
|
26
|
+
import { type MarkdownExtensionNode, type MarkdownPluginEntry } from '@blue-patterns/core/Markdown/plugins';
|
|
27
|
+
import { RichTextExtensionNode } from './markdownExtensionNode.js';
|
|
28
|
+
declare const extensionPlugin: unique symbol;
|
|
29
|
+
/**
|
|
30
|
+
* One Markdown plugin adopted by RichText. Opaque: create it with
|
|
31
|
+
* `createRichTextExtension`, and pass it to the surfaces and serializers that
|
|
32
|
+
* should recognize the plugin's syntax.
|
|
33
|
+
*/
|
|
34
|
+
export interface RichTextMarkdownExtension {
|
|
35
|
+
/** The adopted plugin's name. */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly [extensionPlugin]: MarkdownPluginEntry;
|
|
38
|
+
}
|
|
39
|
+
/** Why RichText refused a plugin or a configuration (spec:AST-064 FR4, FR6). */
|
|
40
|
+
export declare class RichTextExtensionError extends Error {
|
|
41
|
+
/** The plugin RichText refused, or that a configuration claims twice. */
|
|
42
|
+
readonly plugin: string;
|
|
43
|
+
/** What RichText cannot honor: `transform`, or what was claimed twice. */
|
|
44
|
+
readonly capability: string;
|
|
45
|
+
/** The transformer or second extension that claims the same thing. */
|
|
46
|
+
readonly claimant: string | null;
|
|
47
|
+
constructor(plugin: string, capability: string, claimant: string | null);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Adopts a Markdown plugin for RichText (spec:AST-064 FR1, FR4). RichText
|
|
51
|
+
* recognizes the plugin's syntax with core's parser and keeps each node's
|
|
52
|
+
* source; it never runs the plugin's immutable transform, so a plugin that
|
|
53
|
+
* declares one is refused with a `RichTextExtensionError` here, before any
|
|
54
|
+
* surface uses it.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```
|
|
58
|
+
* const mentions = createRichTextExtension(mentionsPlugin);
|
|
59
|
+
* markdownToEditorStateJSON(markdown, {extensions: [mentions]});
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function createRichTextExtension(plugin: MarkdownPluginEntry): RichTextMarkdownExtension;
|
|
63
|
+
/**
|
|
64
|
+
* The plugins behind `extensions`, after checking that the configuration is
|
|
65
|
+
* one RichText can honor (spec:AST-064 FR6): no plugin adopted twice, and no
|
|
66
|
+
* transformer that imports or exports plugin nodes.
|
|
67
|
+
*/
|
|
68
|
+
export declare function pluginsOf(extensions: ReadonlyArray<RichTextMarkdownExtension>, transformers: ReadonlyArray<Transformer>): ReadonlyArray<MarkdownPluginEntry>;
|
|
69
|
+
/** A plugin node core recognized, and the stand-in that holds its place. */
|
|
70
|
+
export interface ShieldedNode {
|
|
71
|
+
readonly node: MarkdownExtensionNode;
|
|
72
|
+
readonly apiVersion: number;
|
|
73
|
+
}
|
|
74
|
+
export interface ShieldedMarkdown {
|
|
75
|
+
/** The Markdown with a stand-in in place of each recognized node. */
|
|
76
|
+
readonly markdown: string;
|
|
77
|
+
/** What each stand-in holds the place of. */
|
|
78
|
+
readonly standIns: ReadonlyMap<string, ShieldedNode>;
|
|
79
|
+
}
|
|
80
|
+
/** Private-use characters that do not occur in `text`, in order. */
|
|
81
|
+
export declare function absentCharacters(text: string): Generator<string>;
|
|
82
|
+
/**
|
|
83
|
+
* Replaces the source of every plugin node core recognizes in `markdown` with
|
|
84
|
+
* a stand-in, so Lexical's transformers never read plugin source as base
|
|
85
|
+
* Markdown (spec:AST-064 FR5). A node whose source core does not recognize at
|
|
86
|
+
* any one occurrence — a span split across a container's line markers — stays
|
|
87
|
+
* text.
|
|
88
|
+
*/
|
|
89
|
+
export declare function shieldExtensionSources(markdown: string, plugins: ReadonlyArray<MarkdownPluginEntry>): ShieldedMarkdown;
|
|
90
|
+
/**
|
|
91
|
+
* Puts a RichTextExtensionNode in place of every stand-in Lexical imported
|
|
92
|
+
* into `holder`: inline nodes where their stand-in is, and a block node in
|
|
93
|
+
* place of the paragraph that holds only its stand-in.
|
|
94
|
+
*/
|
|
95
|
+
export declare function $restoreExtensionSources(holder: ElementNode, standIns: ReadonlyMap<string, ShieldedNode>): void;
|
|
96
|
+
/**
|
|
97
|
+
* Exports every plugin node as its exact source (spec:AST-064 FR10). Always
|
|
98
|
+
* part of RichText's export, whatever transformers the caller passes, so no
|
|
99
|
+
* transformer set can drop a plugin node's source.
|
|
100
|
+
*/
|
|
101
|
+
export declare const EXTENSION_EXPORT_TRANSFORMERS: ReadonlyArray<Transformer>;
|
|
102
|
+
/**
|
|
103
|
+
* A text view of an inline plugin node for Lexical's exporter: it reads as
|
|
104
|
+
* text holding `placeholder`, with the node's formats, so emphasis, strong, or
|
|
105
|
+
* strikethrough around the node wraps it instead of closing before it. The
|
|
106
|
+
* caller writes the node's source in place of the placeholder.
|
|
107
|
+
*/
|
|
108
|
+
export declare function extensionTextView(node: RichTextExtensionNode, placeholder: string): TextNode;
|
|
109
|
+
/**
|
|
110
|
+
* `transformers` with the plugin node export first, the same array for the
|
|
111
|
+
* same transformers so callers that cache by transformer list keep hitting.
|
|
112
|
+
*/
|
|
113
|
+
export declare function withExtensionExport(transformers: ReadonlyArray<Transformer>): Array<Transformer>;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=markdownExtensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownExtensions.d.ts","sourceRoot":"","sources":["../src/markdownExtensions.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAKL,QAAQ,EACR,KAAK,WAAW,EAGjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACzB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAGL,qBAAqB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,QAAA,MAAM,eAAe,eAA6C,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,CAAC,eAAe,CAAC,EAAE,mBAAmB,CAAC;CACjD;AAED,gFAAgF;AAChF,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;gBAErB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAWxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,mBAAmB,GAC1B,yBAAyB,CAM3B;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,aAAa,CAAC,yBAAyB,CAAC,EACpD,YAAY,EAAE,aAAa,CAAC,WAAW,CAAC,GACvC,aAAa,CAAC,mBAAmB,CAAC,CA4BpC;AAMD,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACtD;AAQD,oEAAoE;AACpE,wBAAiB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAUjE;AAgDD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAC1C,gBAAgB,CAuDlB;AAgCD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,GAC1C,IAAI,CAON;AA2FD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,WAAW,CAsBpE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,qBAAqB,EAC3B,WAAW,EAAE,MAAM,GAClB,QAAQ,CAOV;AAOD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,aAAa,CAAC,WAAW,CAAC,GACvC,KAAK,CAAC,WAAW,CAAC,CAOpB"}
|