@lexical/html 0.44.1-nightly.20260519.0 → 0.45.1-dev.0
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/{DOMRenderExtension.d.ts → dist/DOMRenderExtension.d.ts} +12 -1
- package/dist/DOMRenderRuntime.d.ts +51 -0
- package/dist/LexicalHtml.dev.js +3289 -0
- package/dist/LexicalHtml.dev.mjs +3242 -0
- package/{LexicalHtml.js.flow → dist/LexicalHtml.js.flow} +16 -16
- package/dist/LexicalHtml.mjs +57 -0
- package/dist/LexicalHtml.node.mjs +55 -0
- package/dist/LexicalHtml.prod.js +9 -0
- package/dist/LexicalHtml.prod.mjs +9 -0
- package/dist/RenderContext.d.ts +68 -0
- package/{compileDOMRenderConfigOverrides.d.ts → dist/compileDOMRenderConfigOverrides.d.ts} +1 -1
- package/{constants.d.ts → dist/constants.d.ts} +2 -0
- package/dist/domOverride.d.ts +23 -0
- package/dist/import/CoreImportExtension.d.ts +11 -0
- package/dist/import/DOMImportExtension.d.ts +82 -0
- package/dist/import/HorizontalRuleImportExtension.d.ts +28 -0
- package/dist/import/ImportContext.d.ts +208 -0
- package/dist/import/compileImportRules.d.ts +50 -0
- package/dist/import/coreImportRules.d.ts +25 -0
- package/dist/import/defineImportRule.d.ts +32 -0
- package/dist/import/defineOverlayRules.d.ts +66 -0
- package/dist/import/index.d.ts +38 -0
- package/dist/import/inlineStylesFromStyleSheets.d.ts +28 -0
- package/dist/import/parseCss.d.ts +18 -0
- package/dist/import/runImport.d.ts +19 -0
- package/dist/import/schemas.d.ts +106 -0
- package/dist/import/sel.d.ts +74 -0
- package/dist/import/types.d.ts +394 -0
- package/dist/index.d.ts +44 -0
- package/{types.d.ts → dist/types.d.ts} +96 -8
- package/package.json +33 -18
- package/src/ContextRecord.ts +243 -0
- package/src/DOMRenderExtension.ts +96 -0
- package/src/DOMRenderRuntime.ts +265 -0
- package/src/RenderContext.ts +168 -0
- package/src/compileDOMRenderConfigOverrides.ts +416 -0
- package/src/constants.ts +18 -0
- package/src/domOverride.ts +46 -0
- package/src/import/CoreImportExtension.ts +26 -0
- package/src/import/DOMImportExtension.ts +221 -0
- package/src/import/HorizontalRuleImportExtension.ts +52 -0
- package/src/import/ImportContext.ts +339 -0
- package/src/import/compileImportRules.ts +178 -0
- package/src/import/coreImportRules.ts +545 -0
- package/src/import/defineImportRule.ts +40 -0
- package/src/import/defineOverlayRules.ts +105 -0
- package/src/import/index.ts +97 -0
- package/src/import/inlineStylesFromStyleSheets.ts +104 -0
- package/src/import/parseCss.ts +219 -0
- package/src/import/runImport.ts +245 -0
- package/src/import/schemas.ts +280 -0
- package/src/import/sel.ts +314 -0
- package/src/import/types.ts +471 -0
- package/src/index.ts +561 -0
- package/src/types.ts +470 -0
- package/LexicalHtml.dev.js +0 -914
- package/LexicalHtml.dev.mjs +0 -900
- package/LexicalHtml.mjs +0 -24
- package/LexicalHtml.node.mjs +0 -22
- package/LexicalHtml.prod.js +0 -9
- package/LexicalHtml.prod.mjs +0 -9
- package/RenderContext.d.ts +0 -32
- package/domOverride.d.ts +0 -18
- package/index.d.ts +0 -32
- /package/{ContextRecord.d.ts → dist/ContextRecord.d.ts} +0 -0
- /package/{LexicalHtml.js → dist/LexicalHtml.js} +0 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
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 {
|
|
10
|
+
BaseSelection,
|
|
11
|
+
DOMChildConversion,
|
|
12
|
+
DOMConversion,
|
|
13
|
+
DOMConversionFn,
|
|
14
|
+
EditorDOMRenderConfig,
|
|
15
|
+
ElementFormatType,
|
|
16
|
+
LexicalEditor,
|
|
17
|
+
LexicalNode,
|
|
18
|
+
} from 'lexical';
|
|
19
|
+
|
|
20
|
+
import invariant from '@lexical/internal/invariant';
|
|
21
|
+
import {$sliceSelectedTextNodeContent} from '@lexical/selection';
|
|
22
|
+
import {
|
|
23
|
+
$assumeActiveEditor,
|
|
24
|
+
$createLineBreakNode,
|
|
25
|
+
$createParagraphNode,
|
|
26
|
+
$getEditor,
|
|
27
|
+
$getEditorDOMRenderConfig,
|
|
28
|
+
$getRoot,
|
|
29
|
+
$isBlockElementNode,
|
|
30
|
+
$isElementNode,
|
|
31
|
+
$isRootOrShadowRoot,
|
|
32
|
+
$isTextNode,
|
|
33
|
+
ArtificialNode__DO_NOT_USE,
|
|
34
|
+
ElementNode,
|
|
35
|
+
isBlockDomNode,
|
|
36
|
+
isDocumentFragment,
|
|
37
|
+
isDOMDocumentNode,
|
|
38
|
+
isHTMLElement,
|
|
39
|
+
isInlineDomNode,
|
|
40
|
+
} from 'lexical';
|
|
41
|
+
|
|
42
|
+
import {contextValue} from './ContextRecord';
|
|
43
|
+
import {$inlineStylesFromStyleSheetsDOM} from './import/inlineStylesFromStyleSheets';
|
|
44
|
+
import {
|
|
45
|
+
$getSessionDOMRenderConfig,
|
|
46
|
+
$withRenderContext,
|
|
47
|
+
RenderContextExport,
|
|
48
|
+
RenderContextRoot,
|
|
49
|
+
} from './RenderContext';
|
|
50
|
+
|
|
51
|
+
export {contextUpdater, contextValue} from './ContextRecord';
|
|
52
|
+
export {domOverride} from './domOverride';
|
|
53
|
+
export {DOMRenderExtension} from './DOMRenderExtension';
|
|
54
|
+
export type {
|
|
55
|
+
AnyDOMImportRule,
|
|
56
|
+
AttrMatchOptions,
|
|
57
|
+
CapturesOfSelector,
|
|
58
|
+
ChildSchema,
|
|
59
|
+
CompiledOverlayRules,
|
|
60
|
+
CompiledSelector,
|
|
61
|
+
DOMImportContext,
|
|
62
|
+
DOMImportExtensionOutput,
|
|
63
|
+
DOMImportFn,
|
|
64
|
+
DOMImportRule,
|
|
65
|
+
DOMImportRuleEntry,
|
|
66
|
+
DOMPreprocessContext,
|
|
67
|
+
DOMPreprocessFn,
|
|
68
|
+
ElementSelectorBuilder,
|
|
69
|
+
GenerateNodesFromDOMOptions,
|
|
70
|
+
ImportChildrenOpts,
|
|
71
|
+
ImportContextPairOrUpdater,
|
|
72
|
+
ImportNodeOpts,
|
|
73
|
+
ImportSession,
|
|
74
|
+
ImportStateConfig,
|
|
75
|
+
NodeOfSelector,
|
|
76
|
+
StyleMatchOptions,
|
|
77
|
+
} from './import';
|
|
78
|
+
export {
|
|
79
|
+
$distributeInlineWrapper,
|
|
80
|
+
$generateNodesFromDOMViaExtension,
|
|
81
|
+
$getImportContextValue,
|
|
82
|
+
$inlineStylesFromStyleSheets,
|
|
83
|
+
$isBlockLevel,
|
|
84
|
+
$propagateTextAlignToBlockChildren,
|
|
85
|
+
$withImportContext,
|
|
86
|
+
BlockSchema,
|
|
87
|
+
CoreImportExtension,
|
|
88
|
+
CoreImportRules,
|
|
89
|
+
createImportState,
|
|
90
|
+
defaultIsInline,
|
|
91
|
+
defaultPreservesWhitespace,
|
|
92
|
+
defineImportRule,
|
|
93
|
+
defineOverlayRules,
|
|
94
|
+
type DOMImportConfig,
|
|
95
|
+
DOMImportExtension,
|
|
96
|
+
HorizontalRuleImportExtension,
|
|
97
|
+
HorizontalRuleImportRules,
|
|
98
|
+
ImportOverlays,
|
|
99
|
+
ImportSource,
|
|
100
|
+
ImportSourceDataTransfer,
|
|
101
|
+
type ImportSourceKind,
|
|
102
|
+
ImportTextFormat,
|
|
103
|
+
ImportTextStyle,
|
|
104
|
+
ImportWhitespaceConfig,
|
|
105
|
+
InlineSchema,
|
|
106
|
+
isElementOfTag,
|
|
107
|
+
type IsInlineForWhitespace,
|
|
108
|
+
type IsPreserveWhitespaceDom,
|
|
109
|
+
NestedBlockSchema,
|
|
110
|
+
parseSelector,
|
|
111
|
+
RootSchema,
|
|
112
|
+
sel,
|
|
113
|
+
type WhitespaceImportConfig,
|
|
114
|
+
} from './import';
|
|
115
|
+
export {
|
|
116
|
+
$getRenderContextValue,
|
|
117
|
+
$getSessionDOMRenderConfig,
|
|
118
|
+
$setRenderContextValue,
|
|
119
|
+
$updateRenderContextValue,
|
|
120
|
+
$withRenderContext,
|
|
121
|
+
createRenderState,
|
|
122
|
+
RenderContextExport,
|
|
123
|
+
RenderContextRoot,
|
|
124
|
+
} from './RenderContext';
|
|
125
|
+
export type {
|
|
126
|
+
AnyDOMRenderMatch,
|
|
127
|
+
AnyRenderStateConfig,
|
|
128
|
+
AnyRenderStateConfigPairOrUpdater,
|
|
129
|
+
ContextPairOrUpdater,
|
|
130
|
+
DOMOverrideOptions,
|
|
131
|
+
DOMRenderConfig,
|
|
132
|
+
DOMRenderExtensionOutput,
|
|
133
|
+
DOMRenderMatch,
|
|
134
|
+
DOMRenderMatchConfig,
|
|
135
|
+
NodeMatch,
|
|
136
|
+
RenderContextReader,
|
|
137
|
+
} from './types';
|
|
138
|
+
|
|
139
|
+
const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
143
|
+
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
144
|
+
* or an equivalent library and pass in the document here.
|
|
145
|
+
*/
|
|
146
|
+
export function $generateNodesFromDOM(
|
|
147
|
+
editor: LexicalEditor,
|
|
148
|
+
dom: Document | ParentNode,
|
|
149
|
+
): Array<LexicalNode> {
|
|
150
|
+
$inlineStylesFromStyleSheetsDOM(dom);
|
|
151
|
+
|
|
152
|
+
const elements = isDOMDocumentNode(dom)
|
|
153
|
+
? dom.body.childNodes
|
|
154
|
+
: dom.childNodes;
|
|
155
|
+
const lexicalNodes: Array<LexicalNode> = [];
|
|
156
|
+
const allArtificialNodes: Array<ArtificialNode__DO_NOT_USE> = [];
|
|
157
|
+
for (const element of elements) {
|
|
158
|
+
if (!IGNORE_TAGS.has(element.nodeName)) {
|
|
159
|
+
const lexicalNode = $createNodesFromDOM(
|
|
160
|
+
element,
|
|
161
|
+
editor,
|
|
162
|
+
allArtificialNodes,
|
|
163
|
+
false,
|
|
164
|
+
);
|
|
165
|
+
if (lexicalNode !== null) {
|
|
166
|
+
for (const node of lexicalNode) {
|
|
167
|
+
lexicalNodes.push(node);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
$unwrapArtificialNodes(allArtificialNodes);
|
|
173
|
+
|
|
174
|
+
return lexicalNodes;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generate DOM nodes from the editor state into the given container element,
|
|
179
|
+
* using the editor's {@link EditorDOMRenderConfig}.
|
|
180
|
+
* @experimental
|
|
181
|
+
*/
|
|
182
|
+
export function $generateDOMFromNodes<T extends HTMLElement | DocumentFragment>(
|
|
183
|
+
container: T,
|
|
184
|
+
selection: null | BaseSelection = null,
|
|
185
|
+
editor: LexicalEditor = $getEditor(),
|
|
186
|
+
): T {
|
|
187
|
+
return $withRenderContext(
|
|
188
|
+
[contextValue(RenderContextExport, true)],
|
|
189
|
+
editor,
|
|
190
|
+
)(() => {
|
|
191
|
+
const root = $getRoot();
|
|
192
|
+
const domConfig = $getSessionDOMRenderConfig(editor);
|
|
193
|
+
|
|
194
|
+
const parentElementAppend = container.append.bind(container);
|
|
195
|
+
for (const topLevelNode of root.getChildren()) {
|
|
196
|
+
$appendNodesToHTML(
|
|
197
|
+
editor,
|
|
198
|
+
topLevelNode,
|
|
199
|
+
parentElementAppend,
|
|
200
|
+
selection,
|
|
201
|
+
domConfig,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return container;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Generate DOM nodes from a root node into the given container element,
|
|
210
|
+
* including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}.
|
|
211
|
+
* @experimental
|
|
212
|
+
*/
|
|
213
|
+
export function $generateDOMFromRoot<T extends HTMLElement | DocumentFragment>(
|
|
214
|
+
container: T,
|
|
215
|
+
root: LexicalNode = $getRoot(),
|
|
216
|
+
): T {
|
|
217
|
+
const editor = $getEditor();
|
|
218
|
+
return $withRenderContext(
|
|
219
|
+
[
|
|
220
|
+
contextValue(RenderContextExport, true),
|
|
221
|
+
contextValue(RenderContextRoot, true),
|
|
222
|
+
],
|
|
223
|
+
editor,
|
|
224
|
+
)(() => {
|
|
225
|
+
const selection = null;
|
|
226
|
+
const domConfig = $getSessionDOMRenderConfig(editor);
|
|
227
|
+
const parentElementAppend = container.append.bind(container);
|
|
228
|
+
$appendNodesToHTML(editor, root, parentElementAppend, selection, domConfig);
|
|
229
|
+
return container;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate an HTML string from the editor's current state (or `selection`
|
|
235
|
+
* if provided).
|
|
236
|
+
*
|
|
237
|
+
* Must be called inside an active editor scope — i.e. `editor.update(...)`,
|
|
238
|
+
* `editor.read(...)`, or `editor.getEditorState().read(callback, {editor})`.
|
|
239
|
+
* The legacy `editor.getEditorState().read(callback)` call (without the
|
|
240
|
+
* `{editor}` option) does not set an active editor and is not supported;
|
|
241
|
+
* `editor.read(...)` is the drop-in replacement.
|
|
242
|
+
*/
|
|
243
|
+
export function $generateHtmlFromNodes(
|
|
244
|
+
editor: LexicalEditor,
|
|
245
|
+
selection: BaseSelection | null = null,
|
|
246
|
+
): string {
|
|
247
|
+
if (
|
|
248
|
+
typeof document === 'undefined' ||
|
|
249
|
+
(typeof window === 'undefined' && typeof global.window === 'undefined')
|
|
250
|
+
) {
|
|
251
|
+
invariant(
|
|
252
|
+
false,
|
|
253
|
+
'To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom or use withDOM from @lexical/headless/dom before calling this function.',
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
// BC: $setTextContent now requires an active-editor scope (added in #8519).
|
|
257
|
+
// If the caller is in a legacy `editorState.read(cb)` scope (no active editor),
|
|
258
|
+
// establish one via internal API.
|
|
259
|
+
$assumeActiveEditor(editor);
|
|
260
|
+
return $generateDOMFromNodes(document.createElement('div'), selection, editor)
|
|
261
|
+
.innerHTML;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function $appendNodesToHTML(
|
|
265
|
+
editor: LexicalEditor,
|
|
266
|
+
currentNode: LexicalNode,
|
|
267
|
+
parentElementAppend: (element: Node) => void,
|
|
268
|
+
selection: BaseSelection | null = null,
|
|
269
|
+
domConfig: EditorDOMRenderConfig = $getEditorDOMRenderConfig(editor),
|
|
270
|
+
): boolean {
|
|
271
|
+
let shouldInclude = domConfig.$shouldInclude(currentNode, selection, editor);
|
|
272
|
+
const shouldExclude = domConfig.$shouldExclude(
|
|
273
|
+
currentNode,
|
|
274
|
+
selection,
|
|
275
|
+
editor,
|
|
276
|
+
);
|
|
277
|
+
let target = currentNode;
|
|
278
|
+
|
|
279
|
+
if (selection !== null && $isTextNode(currentNode)) {
|
|
280
|
+
target = $sliceSelectedTextNodeContent(selection, currentNode, 'clone');
|
|
281
|
+
}
|
|
282
|
+
const exportProps = domConfig.$exportDOM(target, editor);
|
|
283
|
+
const {element, after, append, $getChildNodes} = exportProps;
|
|
284
|
+
|
|
285
|
+
if (!element) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const fragment = document.createDocumentFragment();
|
|
290
|
+
const children = $getChildNodes
|
|
291
|
+
? $getChildNodes()
|
|
292
|
+
: $isElementNode(target)
|
|
293
|
+
? target.getChildren()
|
|
294
|
+
: [];
|
|
295
|
+
|
|
296
|
+
const fragmentAppend = fragment.append.bind(fragment);
|
|
297
|
+
for (const childNode of children) {
|
|
298
|
+
const shouldIncludeChild = $appendNodesToHTML(
|
|
299
|
+
editor,
|
|
300
|
+
childNode,
|
|
301
|
+
fragmentAppend,
|
|
302
|
+
selection,
|
|
303
|
+
domConfig,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (
|
|
307
|
+
!shouldInclude &&
|
|
308
|
+
shouldIncludeChild &&
|
|
309
|
+
domConfig.$extractWithChild(
|
|
310
|
+
currentNode,
|
|
311
|
+
childNode,
|
|
312
|
+
selection,
|
|
313
|
+
'html',
|
|
314
|
+
editor,
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
shouldInclude = true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (shouldInclude && !shouldExclude) {
|
|
322
|
+
if (isHTMLElement(element) || isDocumentFragment(element)) {
|
|
323
|
+
if (append) {
|
|
324
|
+
append(fragment);
|
|
325
|
+
} else {
|
|
326
|
+
element.append(fragment);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
parentElementAppend(element);
|
|
330
|
+
|
|
331
|
+
if (after) {
|
|
332
|
+
const newElement = after.call(target, element);
|
|
333
|
+
if (newElement) {
|
|
334
|
+
if (isDocumentFragment(element)) {
|
|
335
|
+
element.replaceChildren(newElement);
|
|
336
|
+
} else {
|
|
337
|
+
element.replaceWith(newElement);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
parentElementAppend(fragment);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return shouldInclude;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getConversionFunction(
|
|
349
|
+
domNode: Node,
|
|
350
|
+
editor: LexicalEditor,
|
|
351
|
+
): DOMConversionFn | null {
|
|
352
|
+
const {nodeName} = domNode;
|
|
353
|
+
|
|
354
|
+
const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase());
|
|
355
|
+
|
|
356
|
+
let currentConversion: DOMConversion | null = null;
|
|
357
|
+
|
|
358
|
+
if (cachedConversions !== undefined) {
|
|
359
|
+
for (const cachedConversion of cachedConversions) {
|
|
360
|
+
const domConversion = cachedConversion(domNode);
|
|
361
|
+
if (
|
|
362
|
+
domConversion !== null &&
|
|
363
|
+
(currentConversion === null ||
|
|
364
|
+
// Given equal priority, prefer the last registered importer
|
|
365
|
+
// which is typically an application custom node or HTMLConfig['import']
|
|
366
|
+
(currentConversion.priority || 0) <= (domConversion.priority || 0))
|
|
367
|
+
) {
|
|
368
|
+
currentConversion = domConversion;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return currentConversion !== null ? currentConversion.conversion : null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function $createNodesFromDOM(
|
|
377
|
+
node: Node,
|
|
378
|
+
editor: LexicalEditor,
|
|
379
|
+
allArtificialNodes: Array<ArtificialNode__DO_NOT_USE>,
|
|
380
|
+
hasBlockAncestorLexicalNode: boolean,
|
|
381
|
+
forChildMap: Map<string, DOMChildConversion> = new Map(),
|
|
382
|
+
parentLexicalNode?: LexicalNode | null | undefined,
|
|
383
|
+
): Array<LexicalNode> {
|
|
384
|
+
const lexicalNodes: Array<LexicalNode> = [];
|
|
385
|
+
|
|
386
|
+
if (IGNORE_TAGS.has(node.nodeName)) {
|
|
387
|
+
return lexicalNodes;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let currentLexicalNode = null;
|
|
391
|
+
const transformFunction = getConversionFunction(node, editor);
|
|
392
|
+
const transformOutput = transformFunction
|
|
393
|
+
? transformFunction(node as HTMLElement)
|
|
394
|
+
: null;
|
|
395
|
+
let postTransform = null;
|
|
396
|
+
|
|
397
|
+
if (transformOutput !== null) {
|
|
398
|
+
postTransform = transformOutput.after;
|
|
399
|
+
const transformNodes = transformOutput.node;
|
|
400
|
+
currentLexicalNode = Array.isArray(transformNodes)
|
|
401
|
+
? transformNodes[transformNodes.length - 1]
|
|
402
|
+
: transformNodes;
|
|
403
|
+
|
|
404
|
+
if (currentLexicalNode !== null) {
|
|
405
|
+
for (const [, forChildFunction] of forChildMap) {
|
|
406
|
+
currentLexicalNode = forChildFunction(
|
|
407
|
+
currentLexicalNode,
|
|
408
|
+
parentLexicalNode,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
if (!currentLexicalNode) {
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (currentLexicalNode) {
|
|
417
|
+
lexicalNodes.push(
|
|
418
|
+
...(Array.isArray(transformNodes)
|
|
419
|
+
? transformNodes
|
|
420
|
+
: [currentLexicalNode]),
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (transformOutput.forChild != null) {
|
|
426
|
+
forChildMap.set(node.nodeName, transformOutput.forChild);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// If the DOM node doesn't have a transformer, we don't know what
|
|
431
|
+
// to do with it but we still need to process any childNodes.
|
|
432
|
+
const children = node.childNodes;
|
|
433
|
+
let childLexicalNodes = [];
|
|
434
|
+
|
|
435
|
+
const hasBlockAncestorLexicalNodeForChildren =
|
|
436
|
+
currentLexicalNode != null && $isRootOrShadowRoot(currentLexicalNode)
|
|
437
|
+
? false
|
|
438
|
+
: (currentLexicalNode != null &&
|
|
439
|
+
$isBlockElementNode(currentLexicalNode)) ||
|
|
440
|
+
hasBlockAncestorLexicalNode;
|
|
441
|
+
|
|
442
|
+
for (let i = 0; i < children.length; i++) {
|
|
443
|
+
childLexicalNodes.push(
|
|
444
|
+
...$createNodesFromDOM(
|
|
445
|
+
children[i],
|
|
446
|
+
editor,
|
|
447
|
+
allArtificialNodes,
|
|
448
|
+
hasBlockAncestorLexicalNodeForChildren,
|
|
449
|
+
new Map(forChildMap),
|
|
450
|
+
currentLexicalNode,
|
|
451
|
+
),
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (postTransform != null) {
|
|
456
|
+
childLexicalNodes = postTransform(childLexicalNodes);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (isBlockDomNode(node)) {
|
|
460
|
+
if (!hasBlockAncestorLexicalNodeForChildren) {
|
|
461
|
+
childLexicalNodes = wrapContinuousInlines(
|
|
462
|
+
node,
|
|
463
|
+
childLexicalNodes,
|
|
464
|
+
$createParagraphNode,
|
|
465
|
+
);
|
|
466
|
+
} else {
|
|
467
|
+
childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => {
|
|
468
|
+
const artificialNode = new ArtificialNode__DO_NOT_USE();
|
|
469
|
+
allArtificialNodes.push(artificialNode);
|
|
470
|
+
return artificialNode;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (currentLexicalNode == null) {
|
|
476
|
+
if (childLexicalNodes.length > 0) {
|
|
477
|
+
// If it hasn't been converted to a LexicalNode, we hoist its children
|
|
478
|
+
// up to the same level as it.
|
|
479
|
+
for (const childNode of childLexicalNodes) {
|
|
480
|
+
lexicalNodes.push(childNode);
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
if (isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) {
|
|
484
|
+
// Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes
|
|
485
|
+
lexicalNodes.push($createLineBreakNode());
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
if ($isElementNode(currentLexicalNode)) {
|
|
490
|
+
// If the current node is a ElementNode after conversion,
|
|
491
|
+
// we can append all the children to it.
|
|
492
|
+
currentLexicalNode.append(...childLexicalNodes);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return lexicalNodes;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function wrapContinuousInlines(
|
|
500
|
+
domNode: Node,
|
|
501
|
+
nodes: Array<LexicalNode>,
|
|
502
|
+
createWrapperFn: () => ElementNode,
|
|
503
|
+
): Array<LexicalNode> {
|
|
504
|
+
const textAlign = (domNode as HTMLElement).style
|
|
505
|
+
.textAlign as ElementFormatType;
|
|
506
|
+
const out: Array<LexicalNode> = [];
|
|
507
|
+
let continuousInlines: Array<LexicalNode> = [];
|
|
508
|
+
// wrap contiguous inline child nodes in para
|
|
509
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
510
|
+
const node = nodes[i];
|
|
511
|
+
if ($isBlockElementNode(node)) {
|
|
512
|
+
if (textAlign && !node.getFormat()) {
|
|
513
|
+
node.setFormat(textAlign);
|
|
514
|
+
}
|
|
515
|
+
out.push(node);
|
|
516
|
+
} else {
|
|
517
|
+
continuousInlines.push(node);
|
|
518
|
+
if (
|
|
519
|
+
i === nodes.length - 1 ||
|
|
520
|
+
(i < nodes.length - 1 && $isBlockElementNode(nodes[i + 1]))
|
|
521
|
+
) {
|
|
522
|
+
const wrapper = createWrapperFn();
|
|
523
|
+
wrapper.setFormat(textAlign);
|
|
524
|
+
wrapper.append(...continuousInlines);
|
|
525
|
+
out.push(wrapper);
|
|
526
|
+
continuousInlines = [];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return out;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function $unwrapArtificialNodes(
|
|
534
|
+
allArtificialNodes: Array<ArtificialNode__DO_NOT_USE>,
|
|
535
|
+
) {
|
|
536
|
+
// Replace artificial node with its children, inserting a linebreak
|
|
537
|
+
// between adjacent artificial nodes
|
|
538
|
+
for (const node of allArtificialNodes) {
|
|
539
|
+
if (
|
|
540
|
+
node.getParent() &&
|
|
541
|
+
node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE
|
|
542
|
+
) {
|
|
543
|
+
node.insertAfter($createLineBreakNode());
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
for (const node of allArtificialNodes) {
|
|
547
|
+
const parent = node.getParent();
|
|
548
|
+
if (parent) {
|
|
549
|
+
parent.splice(node.getIndexWithinParent(), 1, node.getChildren());
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function isDomNodeBetweenTwoInlineNodes(node: Node): boolean {
|
|
555
|
+
if (node.nextSibling == null || node.previousSibling == null) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
return (
|
|
559
|
+
isInlineDomNode(node.nextSibling) && isInlineDomNode(node.previousSibling)
|
|
560
|
+
);
|
|
561
|
+
}
|