@lexical/html 0.37.1-nightly.20251027.0 → 0.38.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/LexicalHtml.js +3 -3
- package/package.json +4 -4
- package/src/__tests__/unit/LexicalHtml.test.ts +265 -0
- package/{LexicalHtml.dev.mjs → src/index.ts} +191 -56
- package/LICENSE +0 -21
- package/LexicalHtml.dev.js +0 -248
- package/LexicalHtml.mjs +0 -13
- package/LexicalHtml.node.mjs +0 -11
- package/LexicalHtml.prod.js +0 -9
- package/LexicalHtml.prod.mjs +0 -9
- package/index.d.ts +0 -15
- /package/{LexicalHtml.js.flow → flow/LexicalHtml.js.flow} +0 -0
package/LexicalHtml.js
CHANGED
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"html"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.38.0",
|
|
12
12
|
"main": "LexicalHtml.js",
|
|
13
13
|
"types": "index.d.ts",
|
|
14
14
|
"repository": {
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"directory": "packages/lexical-html"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@lexical/selection": "0.
|
|
21
|
-
"@lexical/utils": "0.
|
|
22
|
-
"lexical": "0.
|
|
20
|
+
"@lexical/selection": "0.38.0",
|
|
21
|
+
"@lexical/utils": "0.38.0",
|
|
22
|
+
"lexical": "0.38.0"
|
|
23
23
|
},
|
|
24
24
|
"module": "LexicalHtml.mjs",
|
|
25
25
|
"sideEffects": false,
|
|
@@ -0,0 +1,265 @@
|
|
|
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 {CodeNode} from '@lexical/code';
|
|
10
|
+
import {createHeadlessEditor} from '@lexical/headless';
|
|
11
|
+
import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html';
|
|
12
|
+
import {LinkNode} from '@lexical/link';
|
|
13
|
+
import {ListItemNode, ListNode} from '@lexical/list';
|
|
14
|
+
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
|
|
15
|
+
import {
|
|
16
|
+
$createParagraphNode,
|
|
17
|
+
$createRangeSelection,
|
|
18
|
+
$createTextNode,
|
|
19
|
+
$getRoot,
|
|
20
|
+
ParagraphNode,
|
|
21
|
+
RangeSelection,
|
|
22
|
+
} from 'lexical';
|
|
23
|
+
import {describe, expect, test} from 'vitest';
|
|
24
|
+
|
|
25
|
+
describe('HTML', () => {
|
|
26
|
+
type Input = Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
html: string;
|
|
29
|
+
initializeEditorState: () => void;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
const HTML_SERIALIZE: Input = [
|
|
33
|
+
{
|
|
34
|
+
html: '<p><br></p>',
|
|
35
|
+
initializeEditorState: () => {
|
|
36
|
+
$getRoot().append($createParagraphNode());
|
|
37
|
+
},
|
|
38
|
+
name: 'Empty editor state',
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
for (const {name, html, initializeEditorState} of HTML_SERIALIZE) {
|
|
42
|
+
test(`[Lexical -> HTML]: ${name}`, () => {
|
|
43
|
+
const editor = createHeadlessEditor({
|
|
44
|
+
nodes: [
|
|
45
|
+
HeadingNode,
|
|
46
|
+
ListNode,
|
|
47
|
+
ListItemNode,
|
|
48
|
+
QuoteNode,
|
|
49
|
+
CodeNode,
|
|
50
|
+
LinkNode,
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
editor.update(initializeEditorState, {
|
|
55
|
+
discrete: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(
|
|
59
|
+
editor.getEditorState().read(() => $generateHtmlFromNodes(editor)),
|
|
60
|
+
).toBe(html);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
test(`[Lexical -> HTML]: Use provided selection`, () => {
|
|
65
|
+
const editor = createHeadlessEditor({
|
|
66
|
+
nodes: [
|
|
67
|
+
HeadingNode,
|
|
68
|
+
ListNode,
|
|
69
|
+
ListItemNode,
|
|
70
|
+
QuoteNode,
|
|
71
|
+
CodeNode,
|
|
72
|
+
LinkNode,
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let selection: RangeSelection | null = null;
|
|
77
|
+
|
|
78
|
+
editor.update(
|
|
79
|
+
() => {
|
|
80
|
+
const root = $getRoot();
|
|
81
|
+
const p1 = $createParagraphNode();
|
|
82
|
+
const text1 = $createTextNode('Hello');
|
|
83
|
+
p1.append(text1);
|
|
84
|
+
const p2 = $createParagraphNode();
|
|
85
|
+
const text2 = $createTextNode('World');
|
|
86
|
+
p2.append(text2);
|
|
87
|
+
root.append(p1).append(p2);
|
|
88
|
+
// Root
|
|
89
|
+
// - ParagraphNode
|
|
90
|
+
// -- TextNode "Hello"
|
|
91
|
+
// - ParagraphNode
|
|
92
|
+
// -- TextNode "World"
|
|
93
|
+
p1.select(0, text1.getTextContentSize());
|
|
94
|
+
selection = $createRangeSelection();
|
|
95
|
+
selection.setTextNodeRange(text2, 0, text2, text2.getTextContentSize());
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
discrete: true,
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
let html = '';
|
|
103
|
+
|
|
104
|
+
editor.update(() => {
|
|
105
|
+
html = $generateHtmlFromNodes(editor, selection);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(html).toBe('<span style="white-space: pre-wrap;">World</span>');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test(`[Lexical -> HTML]: Default selection (undefined) should serialize entire editor state`, () => {
|
|
112
|
+
const editor = createHeadlessEditor({
|
|
113
|
+
nodes: [
|
|
114
|
+
HeadingNode,
|
|
115
|
+
ListNode,
|
|
116
|
+
ListItemNode,
|
|
117
|
+
QuoteNode,
|
|
118
|
+
CodeNode,
|
|
119
|
+
LinkNode,
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
editor.update(
|
|
124
|
+
() => {
|
|
125
|
+
const root = $getRoot();
|
|
126
|
+
const p1 = $createParagraphNode();
|
|
127
|
+
const text1 = $createTextNode('Hello');
|
|
128
|
+
p1.append(text1);
|
|
129
|
+
const p2 = $createParagraphNode();
|
|
130
|
+
const text2 = $createTextNode('World');
|
|
131
|
+
p2.append(text2);
|
|
132
|
+
root.append(p1).append(p2);
|
|
133
|
+
// Root
|
|
134
|
+
// - ParagraphNode
|
|
135
|
+
// -- TextNode "Hello"
|
|
136
|
+
// - ParagraphNode
|
|
137
|
+
// -- TextNode "World"
|
|
138
|
+
p1.select(0, text1.getTextContentSize());
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
discrete: true,
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
let html = '';
|
|
146
|
+
|
|
147
|
+
editor.update(() => {
|
|
148
|
+
html = $generateHtmlFromNodes(editor);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(html).toBe(
|
|
152
|
+
'<p><span style="white-space: pre-wrap;">Hello</span></p><p><span style="white-space: pre-wrap;">World</span></p>',
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test(`If alignment is set on the paragraph, don't overwrite from parent empty format`, () => {
|
|
157
|
+
const editor = createHeadlessEditor();
|
|
158
|
+
const parser = new DOMParser();
|
|
159
|
+
const rightAlignedParagraphInDiv =
|
|
160
|
+
'<div><p style="text-align: center;">Hello world!</p></div>';
|
|
161
|
+
|
|
162
|
+
editor.update(
|
|
163
|
+
() => {
|
|
164
|
+
const root = $getRoot();
|
|
165
|
+
const dom = parser.parseFromString(
|
|
166
|
+
rightAlignedParagraphInDiv,
|
|
167
|
+
'text/html',
|
|
168
|
+
);
|
|
169
|
+
const nodes = $generateNodesFromDOM(editor, dom);
|
|
170
|
+
root.append(...nodes);
|
|
171
|
+
},
|
|
172
|
+
{discrete: true},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
let html = '';
|
|
176
|
+
|
|
177
|
+
editor.update(() => {
|
|
178
|
+
html = $generateHtmlFromNodes(editor);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(html).toBe(
|
|
182
|
+
'<p style="text-align: center;"><span style="white-space: pre-wrap;">Hello world!</span></p>',
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test(`If alignment is set on the paragraph, it should take precedence over its parent block alignment`, () => {
|
|
187
|
+
const editor = createHeadlessEditor();
|
|
188
|
+
const parser = new DOMParser();
|
|
189
|
+
const rightAlignedParagraphInDiv =
|
|
190
|
+
'<div style="text-align: right;"><p style="text-align: center;">Hello world!</p></div>';
|
|
191
|
+
|
|
192
|
+
editor.update(
|
|
193
|
+
() => {
|
|
194
|
+
const root = $getRoot();
|
|
195
|
+
const dom = parser.parseFromString(
|
|
196
|
+
rightAlignedParagraphInDiv,
|
|
197
|
+
'text/html',
|
|
198
|
+
);
|
|
199
|
+
const nodes = $generateNodesFromDOM(editor, dom);
|
|
200
|
+
root.append(...nodes);
|
|
201
|
+
},
|
|
202
|
+
{discrete: true},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
let html = '';
|
|
206
|
+
|
|
207
|
+
editor.update(() => {
|
|
208
|
+
html = $generateHtmlFromNodes(editor);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(html).toBe(
|
|
212
|
+
'<p style="text-align: center;"><span style="white-space: pre-wrap;">Hello world!</span></p>',
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('It should output correctly nodes whose export is DocumentFragment', () => {
|
|
217
|
+
const editor = createHeadlessEditor({
|
|
218
|
+
html: {
|
|
219
|
+
export: new Map([
|
|
220
|
+
[
|
|
221
|
+
ParagraphNode,
|
|
222
|
+
() => {
|
|
223
|
+
const element = document.createDocumentFragment();
|
|
224
|
+
return {
|
|
225
|
+
element,
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
]),
|
|
230
|
+
},
|
|
231
|
+
nodes: [],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
editor.update(
|
|
235
|
+
() => {
|
|
236
|
+
const root = $getRoot();
|
|
237
|
+
const p1 = $createParagraphNode();
|
|
238
|
+
const text1 = $createTextNode('Hello');
|
|
239
|
+
p1.append(text1);
|
|
240
|
+
const p2 = $createParagraphNode();
|
|
241
|
+
const text2 = $createTextNode('World');
|
|
242
|
+
p2.append(text2);
|
|
243
|
+
root.append(p1).append(p2);
|
|
244
|
+
// Root
|
|
245
|
+
// - ParagraphNode
|
|
246
|
+
// -- TextNode "Hello"
|
|
247
|
+
// - ParagraphNode
|
|
248
|
+
// -- TextNode "World"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
discrete: true,
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
let html = '';
|
|
256
|
+
|
|
257
|
+
editor.update(() => {
|
|
258
|
+
html = $generateHtmlFromNodes(editor);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(html).toBe(
|
|
262
|
+
'<span style="white-space: pre-wrap;">Hello</span><span style="white-space: pre-wrap;">World</span>',
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -6,56 +6,103 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*/
|
|
9
|
+
import type {
|
|
10
|
+
BaseSelection,
|
|
11
|
+
DOMChildConversion,
|
|
12
|
+
DOMConversion,
|
|
13
|
+
DOMConversionFn,
|
|
14
|
+
ElementFormatType,
|
|
15
|
+
LexicalEditor,
|
|
16
|
+
LexicalNode,
|
|
17
|
+
} from 'lexical';
|
|
20
18
|
|
|
19
|
+
import {$sliceSelectedTextNodeContent} from '@lexical/selection';
|
|
20
|
+
import {isBlockDomNode, isHTMLElement} from '@lexical/utils';
|
|
21
|
+
import {
|
|
22
|
+
$createLineBreakNode,
|
|
23
|
+
$createParagraphNode,
|
|
24
|
+
$getRoot,
|
|
25
|
+
$isBlockElementNode,
|
|
26
|
+
$isElementNode,
|
|
27
|
+
$isRootOrShadowRoot,
|
|
28
|
+
$isTextNode,
|
|
29
|
+
ArtificialNode__DO_NOT_USE,
|
|
30
|
+
ElementNode,
|
|
31
|
+
getRegisteredNode,
|
|
32
|
+
isDocumentFragment,
|
|
33
|
+
isDOMDocumentNode,
|
|
34
|
+
isInlineDomNode,
|
|
35
|
+
} from 'lexical';
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
38
|
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
24
39
|
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
25
40
|
* or an equivalent library and pass in the document here.
|
|
26
41
|
*/
|
|
27
|
-
function $generateNodesFromDOM(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
export function $generateNodesFromDOM(
|
|
43
|
+
editor: LexicalEditor,
|
|
44
|
+
dom: Document | ParentNode,
|
|
45
|
+
): Array<LexicalNode> {
|
|
46
|
+
const elements = isDOMDocumentNode(dom)
|
|
47
|
+
? dom.body.childNodes
|
|
48
|
+
: dom.childNodes;
|
|
49
|
+
let lexicalNodes: Array<LexicalNode> = [];
|
|
50
|
+
const allArtificialNodes: Array<ArtificialNode__DO_NOT_USE> = [];
|
|
31
51
|
for (const element of elements) {
|
|
32
52
|
if (!IGNORE_TAGS.has(element.nodeName)) {
|
|
33
|
-
const lexicalNode = $createNodesFromDOM(
|
|
53
|
+
const lexicalNode = $createNodesFromDOM(
|
|
54
|
+
element,
|
|
55
|
+
editor,
|
|
56
|
+
allArtificialNodes,
|
|
57
|
+
false,
|
|
58
|
+
);
|
|
34
59
|
if (lexicalNode !== null) {
|
|
35
60
|
lexicalNodes = lexicalNodes.concat(lexicalNode);
|
|
36
61
|
}
|
|
37
62
|
}
|
|
38
63
|
}
|
|
39
64
|
$unwrapArtificialNodes(allArtificialNodes);
|
|
65
|
+
|
|
40
66
|
return lexicalNodes;
|
|
41
67
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
|
|
69
|
+
export function $generateHtmlFromNodes(
|
|
70
|
+
editor: LexicalEditor,
|
|
71
|
+
selection?: BaseSelection | null,
|
|
72
|
+
): string {
|
|
73
|
+
if (
|
|
74
|
+
typeof document === 'undefined' ||
|
|
75
|
+
(typeof window === 'undefined' && typeof global.window === 'undefined')
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
'To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.',
|
|
79
|
+
);
|
|
45
80
|
}
|
|
81
|
+
|
|
46
82
|
const container = document.createElement('div');
|
|
47
83
|
const root = $getRoot();
|
|
48
84
|
const topLevelChildren = root.getChildren();
|
|
85
|
+
|
|
49
86
|
for (let i = 0; i < topLevelChildren.length; i++) {
|
|
50
87
|
const topLevelNode = topLevelChildren[i];
|
|
51
88
|
$appendNodesToHTML(editor, topLevelNode, container, selection);
|
|
52
89
|
}
|
|
90
|
+
|
|
53
91
|
return container.innerHTML;
|
|
54
92
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
|
|
94
|
+
function $appendNodesToHTML(
|
|
95
|
+
editor: LexicalEditor,
|
|
96
|
+
currentNode: LexicalNode,
|
|
97
|
+
parentElement: HTMLElement | DocumentFragment,
|
|
98
|
+
selection: BaseSelection | null = null,
|
|
99
|
+
): boolean {
|
|
100
|
+
let shouldInclude =
|
|
101
|
+
selection !== null ? currentNode.isSelected(selection) : true;
|
|
102
|
+
const shouldExclude =
|
|
103
|
+
$isElementNode(currentNode) && currentNode.excludeFromCopy('html');
|
|
58
104
|
let target = currentNode;
|
|
105
|
+
|
|
59
106
|
if (selection !== null && $isTextNode(currentNode)) {
|
|
60
107
|
target = $sliceSelectedTextNodeContent(selection, currentNode, 'clone');
|
|
61
108
|
}
|
|
@@ -69,26 +116,40 @@ function $appendNodesToHTML(editor, currentNode, parentElement, selection = null
|
|
|
69
116
|
} else {
|
|
70
117
|
exportOutput = target.exportDOM(editor);
|
|
71
118
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} = exportOutput;
|
|
119
|
+
|
|
120
|
+
const {element, after} = exportOutput;
|
|
121
|
+
|
|
76
122
|
if (!element) {
|
|
77
123
|
return false;
|
|
78
124
|
}
|
|
125
|
+
|
|
79
126
|
const fragment = document.createDocumentFragment();
|
|
127
|
+
|
|
80
128
|
for (let i = 0; i < children.length; i++) {
|
|
81
129
|
const childNode = children[i];
|
|
82
|
-
const shouldIncludeChild = $appendNodesToHTML(
|
|
83
|
-
|
|
130
|
+
const shouldIncludeChild = $appendNodesToHTML(
|
|
131
|
+
editor,
|
|
132
|
+
childNode,
|
|
133
|
+
fragment,
|
|
134
|
+
selection,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
!shouldInclude &&
|
|
139
|
+
$isElementNode(currentNode) &&
|
|
140
|
+
shouldIncludeChild &&
|
|
141
|
+
currentNode.extractWithChild(childNode, selection, 'html')
|
|
142
|
+
) {
|
|
84
143
|
shouldInclude = true;
|
|
85
144
|
}
|
|
86
145
|
}
|
|
146
|
+
|
|
87
147
|
if (shouldInclude && !shouldExclude) {
|
|
88
148
|
if (isHTMLElement(element) || isDocumentFragment(element)) {
|
|
89
149
|
element.append(fragment);
|
|
90
150
|
}
|
|
91
151
|
parentElement.append(element);
|
|
152
|
+
|
|
92
153
|
if (after) {
|
|
93
154
|
const newElement = after.call(target, element);
|
|
94
155
|
if (newElement) {
|
|
@@ -102,52 +163,89 @@ function $appendNodesToHTML(editor, currentNode, parentElement, selection = null
|
|
|
102
163
|
} else {
|
|
103
164
|
parentElement.append(fragment);
|
|
104
165
|
}
|
|
166
|
+
|
|
105
167
|
return shouldInclude;
|
|
106
168
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
169
|
+
|
|
170
|
+
function getConversionFunction(
|
|
171
|
+
domNode: Node,
|
|
172
|
+
editor: LexicalEditor,
|
|
173
|
+
): DOMConversionFn | null {
|
|
174
|
+
const {nodeName} = domNode;
|
|
175
|
+
|
|
111
176
|
const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase());
|
|
112
|
-
|
|
177
|
+
|
|
178
|
+
let currentConversion: DOMConversion | null = null;
|
|
179
|
+
|
|
113
180
|
if (cachedConversions !== undefined) {
|
|
114
181
|
for (const cachedConversion of cachedConversions) {
|
|
115
182
|
const domConversion = cachedConversion(domNode);
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
183
|
+
if (
|
|
184
|
+
domConversion !== null &&
|
|
185
|
+
(currentConversion === null ||
|
|
186
|
+
// Given equal priority, prefer the last registered importer
|
|
187
|
+
// which is typically an application custom node or HTMLConfig['import']
|
|
188
|
+
(currentConversion.priority || 0) <= (domConversion.priority || 0))
|
|
189
|
+
) {
|
|
120
190
|
currentConversion = domConversion;
|
|
121
191
|
}
|
|
122
192
|
}
|
|
123
193
|
}
|
|
194
|
+
|
|
124
195
|
return currentConversion !== null ? currentConversion.conversion : null;
|
|
125
196
|
}
|
|
197
|
+
|
|
126
198
|
const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
|
|
127
|
-
|
|
128
|
-
|
|
199
|
+
|
|
200
|
+
function $createNodesFromDOM(
|
|
201
|
+
node: Node,
|
|
202
|
+
editor: LexicalEditor,
|
|
203
|
+
allArtificialNodes: Array<ArtificialNode__DO_NOT_USE>,
|
|
204
|
+
hasBlockAncestorLexicalNode: boolean,
|
|
205
|
+
forChildMap: Map<string, DOMChildConversion> = new Map(),
|
|
206
|
+
parentLexicalNode?: LexicalNode | null | undefined,
|
|
207
|
+
): Array<LexicalNode> {
|
|
208
|
+
let lexicalNodes: Array<LexicalNode> = [];
|
|
209
|
+
|
|
129
210
|
if (IGNORE_TAGS.has(node.nodeName)) {
|
|
130
211
|
return lexicalNodes;
|
|
131
212
|
}
|
|
213
|
+
|
|
132
214
|
let currentLexicalNode = null;
|
|
133
215
|
const transformFunction = getConversionFunction(node, editor);
|
|
134
|
-
const transformOutput = transformFunction
|
|
216
|
+
const transformOutput = transformFunction
|
|
217
|
+
? transformFunction(node as HTMLElement)
|
|
218
|
+
: null;
|
|
135
219
|
let postTransform = null;
|
|
220
|
+
|
|
136
221
|
if (transformOutput !== null) {
|
|
137
222
|
postTransform = transformOutput.after;
|
|
138
223
|
const transformNodes = transformOutput.node;
|
|
139
|
-
currentLexicalNode = Array.isArray(transformNodes)
|
|
224
|
+
currentLexicalNode = Array.isArray(transformNodes)
|
|
225
|
+
? transformNodes[transformNodes.length - 1]
|
|
226
|
+
: transformNodes;
|
|
227
|
+
|
|
140
228
|
if (currentLexicalNode !== null) {
|
|
141
229
|
for (const [, forChildFunction] of forChildMap) {
|
|
142
|
-
currentLexicalNode = forChildFunction(
|
|
230
|
+
currentLexicalNode = forChildFunction(
|
|
231
|
+
currentLexicalNode,
|
|
232
|
+
parentLexicalNode,
|
|
233
|
+
);
|
|
234
|
+
|
|
143
235
|
if (!currentLexicalNode) {
|
|
144
236
|
break;
|
|
145
237
|
}
|
|
146
238
|
}
|
|
239
|
+
|
|
147
240
|
if (currentLexicalNode) {
|
|
148
|
-
lexicalNodes.push(
|
|
241
|
+
lexicalNodes.push(
|
|
242
|
+
...(Array.isArray(transformNodes)
|
|
243
|
+
? transformNodes
|
|
244
|
+
: [currentLexicalNode]),
|
|
245
|
+
);
|
|
149
246
|
}
|
|
150
247
|
}
|
|
248
|
+
|
|
151
249
|
if (transformOutput.forChild != null) {
|
|
152
250
|
forChildMap.set(node.nodeName, transformOutput.forChild);
|
|
153
251
|
}
|
|
@@ -157,16 +255,38 @@ function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorL
|
|
|
157
255
|
// to do with it but we still need to process any childNodes.
|
|
158
256
|
const children = node.childNodes;
|
|
159
257
|
let childLexicalNodes = [];
|
|
160
|
-
|
|
258
|
+
|
|
259
|
+
const hasBlockAncestorLexicalNodeForChildren =
|
|
260
|
+
currentLexicalNode != null && $isRootOrShadowRoot(currentLexicalNode)
|
|
261
|
+
? false
|
|
262
|
+
: (currentLexicalNode != null &&
|
|
263
|
+
$isBlockElementNode(currentLexicalNode)) ||
|
|
264
|
+
hasBlockAncestorLexicalNode;
|
|
265
|
+
|
|
161
266
|
for (let i = 0; i < children.length; i++) {
|
|
162
|
-
childLexicalNodes.push(
|
|
267
|
+
childLexicalNodes.push(
|
|
268
|
+
...$createNodesFromDOM(
|
|
269
|
+
children[i],
|
|
270
|
+
editor,
|
|
271
|
+
allArtificialNodes,
|
|
272
|
+
hasBlockAncestorLexicalNodeForChildren,
|
|
273
|
+
new Map(forChildMap),
|
|
274
|
+
currentLexicalNode,
|
|
275
|
+
),
|
|
276
|
+
);
|
|
163
277
|
}
|
|
278
|
+
|
|
164
279
|
if (postTransform != null) {
|
|
165
280
|
childLexicalNodes = postTransform(childLexicalNodes);
|
|
166
281
|
}
|
|
282
|
+
|
|
167
283
|
if (isBlockDomNode(node)) {
|
|
168
284
|
if (!hasBlockAncestorLexicalNodeForChildren) {
|
|
169
|
-
childLexicalNodes = wrapContinuousInlines(
|
|
285
|
+
childLexicalNodes = wrapContinuousInlines(
|
|
286
|
+
node,
|
|
287
|
+
childLexicalNodes,
|
|
288
|
+
$createParagraphNode,
|
|
289
|
+
);
|
|
170
290
|
} else {
|
|
171
291
|
childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => {
|
|
172
292
|
const artificialNode = new ArtificialNode__DO_NOT_USE();
|
|
@@ -175,6 +295,7 @@ function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorL
|
|
|
175
295
|
});
|
|
176
296
|
}
|
|
177
297
|
}
|
|
298
|
+
|
|
178
299
|
if (currentLexicalNode == null) {
|
|
179
300
|
if (childLexicalNodes.length > 0) {
|
|
180
301
|
// If it hasn't been converted to a LexicalNode, we hoist its children
|
|
@@ -193,12 +314,19 @@ function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorL
|
|
|
193
314
|
currentLexicalNode.append(...childLexicalNodes);
|
|
194
315
|
}
|
|
195
316
|
}
|
|
317
|
+
|
|
196
318
|
return lexicalNodes;
|
|
197
319
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
320
|
+
|
|
321
|
+
function wrapContinuousInlines(
|
|
322
|
+
domNode: Node,
|
|
323
|
+
nodes: Array<LexicalNode>,
|
|
324
|
+
createWrapperFn: () => ElementNode,
|
|
325
|
+
): Array<LexicalNode> {
|
|
326
|
+
const textAlign = (domNode as HTMLElement).style
|
|
327
|
+
.textAlign as ElementFormatType;
|
|
328
|
+
const out: Array<LexicalNode> = [];
|
|
329
|
+
let continuousInlines: Array<LexicalNode> = [];
|
|
202
330
|
// wrap contiguous inline child nodes in para
|
|
203
331
|
for (let i = 0; i < nodes.length; i++) {
|
|
204
332
|
const node = nodes[i];
|
|
@@ -209,7 +337,10 @@ function wrapContinuousInlines(domNode, nodes, createWrapperFn) {
|
|
|
209
337
|
out.push(node);
|
|
210
338
|
} else {
|
|
211
339
|
continuousInlines.push(node);
|
|
212
|
-
if (
|
|
340
|
+
if (
|
|
341
|
+
i === nodes.length - 1 ||
|
|
342
|
+
(i < nodes.length - 1 && $isBlockElementNode(nodes[i + 1]))
|
|
343
|
+
) {
|
|
213
344
|
const wrapper = createWrapperFn();
|
|
214
345
|
wrapper.setFormat(textAlign);
|
|
215
346
|
wrapper.append(...continuousInlines);
|
|
@@ -220,7 +351,10 @@ function wrapContinuousInlines(domNode, nodes, createWrapperFn) {
|
|
|
220
351
|
}
|
|
221
352
|
return out;
|
|
222
353
|
}
|
|
223
|
-
|
|
354
|
+
|
|
355
|
+
function $unwrapArtificialNodes(
|
|
356
|
+
allArtificialNodes: Array<ArtificialNode__DO_NOT_USE>,
|
|
357
|
+
) {
|
|
224
358
|
for (const node of allArtificialNodes) {
|
|
225
359
|
if (node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE) {
|
|
226
360
|
node.insertAfter($createLineBreakNode());
|
|
@@ -235,11 +369,12 @@ function $unwrapArtificialNodes(allArtificialNodes) {
|
|
|
235
369
|
node.remove();
|
|
236
370
|
}
|
|
237
371
|
}
|
|
238
|
-
|
|
372
|
+
|
|
373
|
+
function isDomNodeBetweenTwoInlineNodes(node: Node): boolean {
|
|
239
374
|
if (node.nextSibling == null || node.previousSibling == null) {
|
|
240
375
|
return false;
|
|
241
376
|
}
|
|
242
|
-
return
|
|
377
|
+
return (
|
|
378
|
+
isInlineDomNode(node.nextSibling) && isInlineDomNode(node.previousSibling)
|
|
379
|
+
);
|
|
243
380
|
}
|
|
244
|
-
|
|
245
|
-
export { $generateHtmlFromNodes, $generateNodesFromDOM };
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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.
|
package/LexicalHtml.dev.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
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
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
var selection = require('@lexical/selection');
|
|
12
|
-
var utils = require('@lexical/utils');
|
|
13
|
-
var lexical = require('lexical');
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
17
|
-
*
|
|
18
|
-
* This source code is licensed under the MIT license found in the
|
|
19
|
-
* LICENSE file in the root directory of this source tree.
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
26
|
-
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
27
|
-
* or an equivalent library and pass in the document here.
|
|
28
|
-
*/
|
|
29
|
-
function $generateNodesFromDOM(editor, dom) {
|
|
30
|
-
const elements = lexical.isDOMDocumentNode(dom) ? dom.body.childNodes : dom.childNodes;
|
|
31
|
-
let lexicalNodes = [];
|
|
32
|
-
const allArtificialNodes = [];
|
|
33
|
-
for (const element of elements) {
|
|
34
|
-
if (!IGNORE_TAGS.has(element.nodeName)) {
|
|
35
|
-
const lexicalNode = $createNodesFromDOM(element, editor, allArtificialNodes, false);
|
|
36
|
-
if (lexicalNode !== null) {
|
|
37
|
-
lexicalNodes = lexicalNodes.concat(lexicalNode);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
$unwrapArtificialNodes(allArtificialNodes);
|
|
42
|
-
return lexicalNodes;
|
|
43
|
-
}
|
|
44
|
-
function $generateHtmlFromNodes(editor, selection) {
|
|
45
|
-
if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') {
|
|
46
|
-
throw new Error('To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.');
|
|
47
|
-
}
|
|
48
|
-
const container = document.createElement('div');
|
|
49
|
-
const root = lexical.$getRoot();
|
|
50
|
-
const topLevelChildren = root.getChildren();
|
|
51
|
-
for (let i = 0; i < topLevelChildren.length; i++) {
|
|
52
|
-
const topLevelNode = topLevelChildren[i];
|
|
53
|
-
$appendNodesToHTML(editor, topLevelNode, container, selection);
|
|
54
|
-
}
|
|
55
|
-
return container.innerHTML;
|
|
56
|
-
}
|
|
57
|
-
function $appendNodesToHTML(editor, currentNode, parentElement, selection$1 = null) {
|
|
58
|
-
let shouldInclude = selection$1 !== null ? currentNode.isSelected(selection$1) : true;
|
|
59
|
-
const shouldExclude = lexical.$isElementNode(currentNode) && currentNode.excludeFromCopy('html');
|
|
60
|
-
let target = currentNode;
|
|
61
|
-
if (selection$1 !== null && lexical.$isTextNode(currentNode)) {
|
|
62
|
-
target = selection.$sliceSelectedTextNodeContent(selection$1, currentNode, 'clone');
|
|
63
|
-
}
|
|
64
|
-
const children = lexical.$isElementNode(target) ? target.getChildren() : [];
|
|
65
|
-
const registeredNode = lexical.getRegisteredNode(editor, target.getType());
|
|
66
|
-
let exportOutput;
|
|
67
|
-
|
|
68
|
-
// Use HTMLConfig overrides, if available.
|
|
69
|
-
if (registeredNode && registeredNode.exportDOM !== undefined) {
|
|
70
|
-
exportOutput = registeredNode.exportDOM(editor, target);
|
|
71
|
-
} else {
|
|
72
|
-
exportOutput = target.exportDOM(editor);
|
|
73
|
-
}
|
|
74
|
-
const {
|
|
75
|
-
element,
|
|
76
|
-
after
|
|
77
|
-
} = exportOutput;
|
|
78
|
-
if (!element) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
const fragment = document.createDocumentFragment();
|
|
82
|
-
for (let i = 0; i < children.length; i++) {
|
|
83
|
-
const childNode = children[i];
|
|
84
|
-
const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragment, selection$1);
|
|
85
|
-
if (!shouldInclude && lexical.$isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection$1, 'html')) {
|
|
86
|
-
shouldInclude = true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (shouldInclude && !shouldExclude) {
|
|
90
|
-
if (utils.isHTMLElement(element) || lexical.isDocumentFragment(element)) {
|
|
91
|
-
element.append(fragment);
|
|
92
|
-
}
|
|
93
|
-
parentElement.append(element);
|
|
94
|
-
if (after) {
|
|
95
|
-
const newElement = after.call(target, element);
|
|
96
|
-
if (newElement) {
|
|
97
|
-
if (lexical.isDocumentFragment(element)) {
|
|
98
|
-
element.replaceChildren(newElement);
|
|
99
|
-
} else {
|
|
100
|
-
element.replaceWith(newElement);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
parentElement.append(fragment);
|
|
106
|
-
}
|
|
107
|
-
return shouldInclude;
|
|
108
|
-
}
|
|
109
|
-
function getConversionFunction(domNode, editor) {
|
|
110
|
-
const {
|
|
111
|
-
nodeName
|
|
112
|
-
} = domNode;
|
|
113
|
-
const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase());
|
|
114
|
-
let currentConversion = null;
|
|
115
|
-
if (cachedConversions !== undefined) {
|
|
116
|
-
for (const cachedConversion of cachedConversions) {
|
|
117
|
-
const domConversion = cachedConversion(domNode);
|
|
118
|
-
if (domConversion !== null && (currentConversion === null ||
|
|
119
|
-
// Given equal priority, prefer the last registered importer
|
|
120
|
-
// which is typically an application custom node or HTMLConfig['import']
|
|
121
|
-
(currentConversion.priority || 0) <= (domConversion.priority || 0))) {
|
|
122
|
-
currentConversion = domConversion;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return currentConversion !== null ? currentConversion.conversion : null;
|
|
127
|
-
}
|
|
128
|
-
const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
|
|
129
|
-
function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorLexicalNode, forChildMap = new Map(), parentLexicalNode) {
|
|
130
|
-
let lexicalNodes = [];
|
|
131
|
-
if (IGNORE_TAGS.has(node.nodeName)) {
|
|
132
|
-
return lexicalNodes;
|
|
133
|
-
}
|
|
134
|
-
let currentLexicalNode = null;
|
|
135
|
-
const transformFunction = getConversionFunction(node, editor);
|
|
136
|
-
const transformOutput = transformFunction ? transformFunction(node) : null;
|
|
137
|
-
let postTransform = null;
|
|
138
|
-
if (transformOutput !== null) {
|
|
139
|
-
postTransform = transformOutput.after;
|
|
140
|
-
const transformNodes = transformOutput.node;
|
|
141
|
-
currentLexicalNode = Array.isArray(transformNodes) ? transformNodes[transformNodes.length - 1] : transformNodes;
|
|
142
|
-
if (currentLexicalNode !== null) {
|
|
143
|
-
for (const [, forChildFunction] of forChildMap) {
|
|
144
|
-
currentLexicalNode = forChildFunction(currentLexicalNode, parentLexicalNode);
|
|
145
|
-
if (!currentLexicalNode) {
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (currentLexicalNode) {
|
|
150
|
-
lexicalNodes.push(...(Array.isArray(transformNodes) ? transformNodes : [currentLexicalNode]));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (transformOutput.forChild != null) {
|
|
154
|
-
forChildMap.set(node.nodeName, transformOutput.forChild);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// If the DOM node doesn't have a transformer, we don't know what
|
|
159
|
-
// to do with it but we still need to process any childNodes.
|
|
160
|
-
const children = node.childNodes;
|
|
161
|
-
let childLexicalNodes = [];
|
|
162
|
-
const hasBlockAncestorLexicalNodeForChildren = currentLexicalNode != null && lexical.$isRootOrShadowRoot(currentLexicalNode) ? false : currentLexicalNode != null && lexical.$isBlockElementNode(currentLexicalNode) || hasBlockAncestorLexicalNode;
|
|
163
|
-
for (let i = 0; i < children.length; i++) {
|
|
164
|
-
childLexicalNodes.push(...$createNodesFromDOM(children[i], editor, allArtificialNodes, hasBlockAncestorLexicalNodeForChildren, new Map(forChildMap), currentLexicalNode));
|
|
165
|
-
}
|
|
166
|
-
if (postTransform != null) {
|
|
167
|
-
childLexicalNodes = postTransform(childLexicalNodes);
|
|
168
|
-
}
|
|
169
|
-
if (utils.isBlockDomNode(node)) {
|
|
170
|
-
if (!hasBlockAncestorLexicalNodeForChildren) {
|
|
171
|
-
childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, lexical.$createParagraphNode);
|
|
172
|
-
} else {
|
|
173
|
-
childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => {
|
|
174
|
-
const artificialNode = new lexical.ArtificialNode__DO_NOT_USE();
|
|
175
|
-
allArtificialNodes.push(artificialNode);
|
|
176
|
-
return artificialNode;
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (currentLexicalNode == null) {
|
|
181
|
-
if (childLexicalNodes.length > 0) {
|
|
182
|
-
// If it hasn't been converted to a LexicalNode, we hoist its children
|
|
183
|
-
// up to the same level as it.
|
|
184
|
-
lexicalNodes = lexicalNodes.concat(childLexicalNodes);
|
|
185
|
-
} else {
|
|
186
|
-
if (utils.isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) {
|
|
187
|
-
// Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes
|
|
188
|
-
lexicalNodes = lexicalNodes.concat(lexical.$createLineBreakNode());
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
if (lexical.$isElementNode(currentLexicalNode)) {
|
|
193
|
-
// If the current node is a ElementNode after conversion,
|
|
194
|
-
// we can append all the children to it.
|
|
195
|
-
currentLexicalNode.append(...childLexicalNodes);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return lexicalNodes;
|
|
199
|
-
}
|
|
200
|
-
function wrapContinuousInlines(domNode, nodes, createWrapperFn) {
|
|
201
|
-
const textAlign = domNode.style.textAlign;
|
|
202
|
-
const out = [];
|
|
203
|
-
let continuousInlines = [];
|
|
204
|
-
// wrap contiguous inline child nodes in para
|
|
205
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
206
|
-
const node = nodes[i];
|
|
207
|
-
if (lexical.$isBlockElementNode(node)) {
|
|
208
|
-
if (textAlign && !node.getFormat()) {
|
|
209
|
-
node.setFormat(textAlign);
|
|
210
|
-
}
|
|
211
|
-
out.push(node);
|
|
212
|
-
} else {
|
|
213
|
-
continuousInlines.push(node);
|
|
214
|
-
if (i === nodes.length - 1 || i < nodes.length - 1 && lexical.$isBlockElementNode(nodes[i + 1])) {
|
|
215
|
-
const wrapper = createWrapperFn();
|
|
216
|
-
wrapper.setFormat(textAlign);
|
|
217
|
-
wrapper.append(...continuousInlines);
|
|
218
|
-
out.push(wrapper);
|
|
219
|
-
continuousInlines = [];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return out;
|
|
224
|
-
}
|
|
225
|
-
function $unwrapArtificialNodes(allArtificialNodes) {
|
|
226
|
-
for (const node of allArtificialNodes) {
|
|
227
|
-
if (node.getNextSibling() instanceof lexical.ArtificialNode__DO_NOT_USE) {
|
|
228
|
-
node.insertAfter(lexical.$createLineBreakNode());
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Replace artificial node with it's children
|
|
232
|
-
for (const node of allArtificialNodes) {
|
|
233
|
-
const children = node.getChildren();
|
|
234
|
-
for (const child of children) {
|
|
235
|
-
node.insertBefore(child);
|
|
236
|
-
}
|
|
237
|
-
node.remove();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
function isDomNodeBetweenTwoInlineNodes(node) {
|
|
241
|
-
if (node.nextSibling == null || node.previousSibling == null) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
return lexical.isInlineDomNode(node.nextSibling) && lexical.isInlineDomNode(node.previousSibling);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
exports.$generateHtmlFromNodes = $generateHtmlFromNodes;
|
|
248
|
-
exports.$generateNodesFromDOM = $generateNodesFromDOM;
|
package/LexicalHtml.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
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 * as modDev from './LexicalHtml.dev.mjs';
|
|
10
|
-
import * as modProd from './LexicalHtml.prod.mjs';
|
|
11
|
-
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
|
|
12
|
-
export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes;
|
|
13
|
-
export const $generateNodesFromDOM = mod.$generateNodesFromDOM;
|
package/LexicalHtml.node.mjs
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalHtml.dev.mjs') : import('./LexicalHtml.prod.mjs'));
|
|
10
|
-
export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes;
|
|
11
|
-
export const $generateNodesFromDOM = mod.$generateNodesFromDOM;
|
package/LexicalHtml.prod.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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
|
-
"use strict";var e=require("@lexical/selection"),n=require("@lexical/utils"),t=require("lexical");function o(l,i,r,s=null){let c=null===s||i.isSelected(s);const d=t.$isElementNode(i)&&i.excludeFromCopy("html");let u=i;null!==s&&t.$isTextNode(i)&&(u=e.$sliceSelectedTextNodeContent(s,i,"clone"));const a=t.$isElementNode(u)?u.getChildren():[],f=t.getRegisteredNode(l,u.getType());let m;m=f&&void 0!==f.exportDOM?f.exportDOM(l,u):u.exportDOM(l);const{element:h,after:p}=m;if(!h)return!1;const g=document.createDocumentFragment();for(let e=0;e<a.length;e++){const n=a[e],r=o(l,n,g,s);!c&&t.$isElementNode(i)&&r&&i.extractWithChild(n,s,"html")&&(c=!0)}if(c&&!d){if((n.isHTMLElement(h)||t.isDocumentFragment(h))&&h.append(g),r.append(h),p){const e=p.call(u,h);e&&(t.isDocumentFragment(h)?h.replaceChildren(e):h.replaceWith(e))}}else r.append(g);return c}const l=new Set(["STYLE","SCRIPT"]);function i(e,o,s,c,d=new Map,u){let a=[];if(l.has(e.nodeName))return a;let f=null;const m=function(e,n){const{nodeName:t}=e,o=n._htmlConversions.get(t.toLowerCase());let l=null;if(void 0!==o)for(const n of o){const t=n(e);null!==t&&(null===l||(l.priority||0)<=(t.priority||0))&&(l=t)}return null!==l?l.conversion:null}(e,o),h=m?m(e):null;let p=null;if(null!==h){p=h.after;const n=h.node;if(f=Array.isArray(n)?n[n.length-1]:n,null!==f){for(const[,e]of d)if(f=e(f,u),!f)break;f&&a.push(...Array.isArray(n)?n:[f])}null!=h.forChild&&d.set(e.nodeName,h.forChild)}const g=e.childNodes;let N=[];const $=(null==f||!t.$isRootOrShadowRoot(f))&&(null!=f&&t.$isBlockElementNode(f)||c);for(let e=0;e<g.length;e++)N.push(...i(g[e],o,s,$,new Map(d),f));return null!=p&&(N=p(N)),n.isBlockDomNode(e)&&(N=r(e,N,$?()=>{const e=new t.ArtificialNode__DO_NOT_USE;return s.push(e),e}:t.$createParagraphNode)),null==f?N.length>0?a=a.concat(N):n.isBlockDomNode(e)&&function(e){if(null==e.nextSibling||null==e.previousSibling)return!1;return t.isInlineDomNode(e.nextSibling)&&t.isInlineDomNode(e.previousSibling)}(e)&&(a=a.concat(t.$createLineBreakNode())):t.$isElementNode(f)&&f.append(...N),a}function r(e,n,o){const l=e.style.textAlign,i=[];let r=[];for(let e=0;e<n.length;e++){const s=n[e];if(t.$isBlockElementNode(s))l&&!s.getFormat()&&s.setFormat(l),i.push(s);else if(r.push(s),e===n.length-1||e<n.length-1&&t.$isBlockElementNode(n[e+1])){const e=o();e.setFormat(l),e.append(...r),i.push(e),r=[]}}return i}exports.$generateHtmlFromNodes=function(e,n){if("undefined"==typeof document||"undefined"==typeof window&&void 0===global.window)throw new Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");const l=document.createElement("div"),i=t.$getRoot().getChildren();for(let t=0;t<i.length;t++){o(e,i[t],l,n)}return l.innerHTML},exports.$generateNodesFromDOM=function(e,n){const o=t.isDOMDocumentNode(n)?n.body.childNodes:n.childNodes;let r=[];const s=[];for(const n of o)if(!l.has(n.nodeName)){const t=i(n,e,s,!1);null!==t&&(r=r.concat(t))}return function(e){for(const n of e)n.getNextSibling()instanceof t.ArtificialNode__DO_NOT_USE&&n.insertAfter(t.$createLineBreakNode());for(const n of e){const e=n.getChildren();for(const t of e)n.insertBefore(t);n.remove()}}(s),r};
|
package/LexicalHtml.prod.mjs
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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{$sliceSelectedTextNodeContent as e}from"@lexical/selection";import{isHTMLElement as n,isBlockDomNode as t}from"@lexical/utils";import{isDOMDocumentNode as o,$getRoot as l,$isElementNode as r,$isTextNode as i,getRegisteredNode as s,isDocumentFragment as c,$isRootOrShadowRoot as u,$isBlockElementNode as f,$createLineBreakNode as a,ArtificialNode__DO_NOT_USE as d,isInlineDomNode as p,$createParagraphNode as h}from"lexical";function m(e,n){const t=o(n)?n.body.childNodes:n.childNodes;let l=[];const r=[];for(const n of t)if(!w.has(n.nodeName)){const t=y(n,e,r,!1);null!==t&&(l=l.concat(t))}return function(e){for(const n of e)n.getNextSibling()instanceof d&&n.insertAfter(a());for(const n of e){const e=n.getChildren();for(const t of e)n.insertBefore(t);n.remove()}}(r),l}function g(e,n){if("undefined"==typeof document||"undefined"==typeof window&&void 0===global.window)throw new Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");const t=document.createElement("div"),o=l().getChildren();for(let l=0;l<o.length;l++){x(e,o[l],t,n)}return t.innerHTML}function x(t,o,l,u=null){let f=null===u||o.isSelected(u);const a=r(o)&&o.excludeFromCopy("html");let d=o;null!==u&&i(o)&&(d=e(u,o,"clone"));const p=r(d)?d.getChildren():[],h=s(t,d.getType());let m;m=h&&void 0!==h.exportDOM?h.exportDOM(t,d):d.exportDOM(t);const{element:g,after:w}=m;if(!g)return!1;const y=document.createDocumentFragment();for(let e=0;e<p.length;e++){const n=p[e],l=x(t,n,y,u);!f&&r(o)&&l&&o.extractWithChild(n,u,"html")&&(f=!0)}if(f&&!a){if((n(g)||c(g))&&g.append(y),l.append(g),w){const e=w.call(d,g);e&&(c(g)?g.replaceChildren(e):g.replaceWith(e))}}else l.append(y);return f}const w=new Set(["STYLE","SCRIPT"]);function y(e,n,o,l,i=new Map,s){let c=[];if(w.has(e.nodeName))return c;let m=null;const g=function(e,n){const{nodeName:t}=e,o=n._htmlConversions.get(t.toLowerCase());let l=null;if(void 0!==o)for(const n of o){const t=n(e);null!==t&&(null===l||(l.priority||0)<=(t.priority||0))&&(l=t)}return null!==l?l.conversion:null}(e,n),x=g?g(e):null;let b=null;if(null!==x){b=x.after;const n=x.node;if(m=Array.isArray(n)?n[n.length-1]:n,null!==m){for(const[,e]of i)if(m=e(m,s),!m)break;m&&c.push(...Array.isArray(n)?n:[m])}null!=x.forChild&&i.set(e.nodeName,x.forChild)}const S=e.childNodes;let v=[];const N=(null==m||!u(m))&&(null!=m&&f(m)||l);for(let e=0;e<S.length;e++)v.push(...y(S[e],n,o,N,new Map(i),m));return null!=b&&(v=b(v)),t(e)&&(v=C(e,v,N?()=>{const e=new d;return o.push(e),e}:h)),null==m?v.length>0?c=c.concat(v):t(e)&&function(e){if(null==e.nextSibling||null==e.previousSibling)return!1;return p(e.nextSibling)&&p(e.previousSibling)}(e)&&(c=c.concat(a())):r(m)&&m.append(...v),c}function C(e,n,t){const o=e.style.textAlign,l=[];let r=[];for(let e=0;e<n.length;e++){const i=n[e];if(f(i))o&&!i.getFormat()&&i.setFormat(o),l.push(i);else if(r.push(i),e===n.length-1||e<n.length-1&&f(n[e+1])){const e=t();e.setFormat(o),e.append(...r),l.push(e),r=[]}}return l}export{g as $generateHtmlFromNodes,m as $generateNodesFromDOM};
|
package/index.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
import type { BaseSelection, LexicalEditor, LexicalNode } from 'lexical';
|
|
9
|
-
/**
|
|
10
|
-
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
11
|
-
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
12
|
-
* or an equivalent library and pass in the document here.
|
|
13
|
-
*/
|
|
14
|
-
export declare function $generateNodesFromDOM(editor: LexicalEditor, dom: Document | ParentNode): Array<LexicalNode>;
|
|
15
|
-
export declare function $generateHtmlFromNodes(editor: LexicalEditor, selection?: BaseSelection | null): string;
|
|
File without changes
|