@lexical/html 0.13.0 → 0.14.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.dev.esm.js +165 -0
- package/LexicalHtml.dev.js +5 -3
- package/LexicalHtml.esm.js +11 -0
- package/LexicalHtml.js +1 -1
- package/LexicalHtml.prod.esm.js +7 -0
- package/LexicalHtml.prod.js +1 -1
- package/README.md +10 -1
- package/index.d.ts +1 -1
- package/package.json +7 -5
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
import { $cloneWithProperties, $sliceSelectedTextNodeContent } from '@lexical/selection';
|
|
8
|
+
import { isHTMLElement } from '@lexical/utils';
|
|
9
|
+
import { $getRoot, $isElementNode, $isTextNode } from 'lexical';
|
|
10
|
+
|
|
11
|
+
/** @module @lexical/html */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
15
|
+
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
16
|
+
* or an equivalent library and pass in the document here.
|
|
17
|
+
*/
|
|
18
|
+
function $generateNodesFromDOM(editor, dom) {
|
|
19
|
+
const elements = dom.body ? dom.body.childNodes : [];
|
|
20
|
+
let lexicalNodes = [];
|
|
21
|
+
for (let i = 0; i < elements.length; i++) {
|
|
22
|
+
const element = elements[i];
|
|
23
|
+
if (!IGNORE_TAGS.has(element.nodeName)) {
|
|
24
|
+
const lexicalNode = $createNodesFromDOM(element, editor);
|
|
25
|
+
if (lexicalNode !== null) {
|
|
26
|
+
lexicalNodes = lexicalNodes.concat(lexicalNode);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return lexicalNodes;
|
|
31
|
+
}
|
|
32
|
+
function $generateHtmlFromNodes(editor, selection) {
|
|
33
|
+
if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') {
|
|
34
|
+
throw new Error('To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.');
|
|
35
|
+
}
|
|
36
|
+
const container = document.createElement('div');
|
|
37
|
+
const root = $getRoot();
|
|
38
|
+
const topLevelChildren = root.getChildren();
|
|
39
|
+
for (let i = 0; i < topLevelChildren.length; i++) {
|
|
40
|
+
const topLevelNode = topLevelChildren[i];
|
|
41
|
+
$appendNodesToHTML(editor, topLevelNode, container, selection);
|
|
42
|
+
}
|
|
43
|
+
return container.innerHTML;
|
|
44
|
+
}
|
|
45
|
+
function $appendNodesToHTML(editor, currentNode, parentElement, selection = null) {
|
|
46
|
+
let shouldInclude = selection !== null ? currentNode.isSelected(selection) : true;
|
|
47
|
+
const shouldExclude = $isElementNode(currentNode) && currentNode.excludeFromCopy('html');
|
|
48
|
+
let target = currentNode;
|
|
49
|
+
if (selection !== null) {
|
|
50
|
+
let clone = $cloneWithProperties(currentNode);
|
|
51
|
+
clone = $isTextNode(clone) && selection !== null ? $sliceSelectedTextNodeContent(selection, clone) : clone;
|
|
52
|
+
target = clone;
|
|
53
|
+
}
|
|
54
|
+
const children = $isElementNode(target) ? target.getChildren() : [];
|
|
55
|
+
const registeredNode = editor._nodes.get(target.getType());
|
|
56
|
+
let exportOutput;
|
|
57
|
+
|
|
58
|
+
// Use HTMLConfig overrides, if available.
|
|
59
|
+
if (registeredNode && registeredNode.exportDOM !== undefined) {
|
|
60
|
+
exportOutput = registeredNode.exportDOM(editor, target);
|
|
61
|
+
} else {
|
|
62
|
+
exportOutput = target.exportDOM(editor);
|
|
63
|
+
}
|
|
64
|
+
const {
|
|
65
|
+
element,
|
|
66
|
+
after
|
|
67
|
+
} = exportOutput;
|
|
68
|
+
if (!element) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const fragment = document.createDocumentFragment();
|
|
72
|
+
for (let i = 0; i < children.length; i++) {
|
|
73
|
+
const childNode = children[i];
|
|
74
|
+
const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragment, selection);
|
|
75
|
+
if (!shouldInclude && $isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection, 'html')) {
|
|
76
|
+
shouldInclude = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (shouldInclude && !shouldExclude) {
|
|
80
|
+
if (isHTMLElement(element)) {
|
|
81
|
+
element.append(fragment);
|
|
82
|
+
}
|
|
83
|
+
parentElement.append(element);
|
|
84
|
+
if (after) {
|
|
85
|
+
const newElement = after.call(target, element);
|
|
86
|
+
if (newElement) {
|
|
87
|
+
element.replaceWith(newElement);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
parentElement.append(fragment);
|
|
92
|
+
}
|
|
93
|
+
return shouldInclude;
|
|
94
|
+
}
|
|
95
|
+
function getConversionFunction(domNode, editor) {
|
|
96
|
+
const {
|
|
97
|
+
nodeName
|
|
98
|
+
} = domNode;
|
|
99
|
+
const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase());
|
|
100
|
+
let currentConversion = null;
|
|
101
|
+
if (cachedConversions !== undefined) {
|
|
102
|
+
for (const cachedConversion of cachedConversions) {
|
|
103
|
+
const domConversion = cachedConversion(domNode);
|
|
104
|
+
if (domConversion !== null && (currentConversion === null || (currentConversion.priority || 0) < (domConversion.priority || 0))) {
|
|
105
|
+
currentConversion = domConversion;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return currentConversion !== null ? currentConversion.conversion : null;
|
|
110
|
+
}
|
|
111
|
+
const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
|
|
112
|
+
function $createNodesFromDOM(node, editor, forChildMap = new Map(), parentLexicalNode) {
|
|
113
|
+
let lexicalNodes = [];
|
|
114
|
+
if (IGNORE_TAGS.has(node.nodeName)) {
|
|
115
|
+
return lexicalNodes;
|
|
116
|
+
}
|
|
117
|
+
let currentLexicalNode = null;
|
|
118
|
+
const transformFunction = getConversionFunction(node, editor);
|
|
119
|
+
const transformOutput = transformFunction ? transformFunction(node) : null;
|
|
120
|
+
let postTransform = null;
|
|
121
|
+
if (transformOutput !== null) {
|
|
122
|
+
postTransform = transformOutput.after;
|
|
123
|
+
const transformNodes = transformOutput.node;
|
|
124
|
+
currentLexicalNode = Array.isArray(transformNodes) ? transformNodes[transformNodes.length - 1] : transformNodes;
|
|
125
|
+
if (currentLexicalNode !== null) {
|
|
126
|
+
for (const [, forChildFunction] of forChildMap) {
|
|
127
|
+
currentLexicalNode = forChildFunction(currentLexicalNode, parentLexicalNode);
|
|
128
|
+
if (!currentLexicalNode) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (currentLexicalNode) {
|
|
133
|
+
lexicalNodes.push(...(Array.isArray(transformNodes) ? transformNodes : [currentLexicalNode]));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (transformOutput.forChild != null) {
|
|
137
|
+
forChildMap.set(node.nodeName, transformOutput.forChild);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If the DOM node doesn't have a transformer, we don't know what
|
|
142
|
+
// to do with it but we still need to process any childNodes.
|
|
143
|
+
const children = node.childNodes;
|
|
144
|
+
let childLexicalNodes = [];
|
|
145
|
+
for (let i = 0; i < children.length; i++) {
|
|
146
|
+
childLexicalNodes.push(...$createNodesFromDOM(children[i], editor, new Map(forChildMap), currentLexicalNode));
|
|
147
|
+
}
|
|
148
|
+
if (postTransform != null) {
|
|
149
|
+
childLexicalNodes = postTransform(childLexicalNodes);
|
|
150
|
+
}
|
|
151
|
+
if (currentLexicalNode == null) {
|
|
152
|
+
// If it hasn't been converted to a LexicalNode, we hoist its children
|
|
153
|
+
// up to the same level as it.
|
|
154
|
+
lexicalNodes = lexicalNodes.concat(childLexicalNodes);
|
|
155
|
+
} else {
|
|
156
|
+
if ($isElementNode(currentLexicalNode)) {
|
|
157
|
+
// If the current node is a ElementNode after conversion,
|
|
158
|
+
// we can append all the children to it.
|
|
159
|
+
currentLexicalNode.append(...childLexicalNodes);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return lexicalNodes;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export { $generateHtmlFromNodes, $generateNodesFromDOM };
|
package/LexicalHtml.dev.js
CHANGED
|
@@ -15,7 +15,7 @@ var lexical = require('lexical');
|
|
|
15
15
|
/**
|
|
16
16
|
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
17
17
|
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
18
|
-
* or an
|
|
18
|
+
* or an equivalent library and pass in the document here.
|
|
19
19
|
*/
|
|
20
20
|
function $generateNodesFromDOM(editor, dom) {
|
|
21
21
|
const elements = dom.body ? dom.body.childNodes : [];
|
|
@@ -32,7 +32,7 @@ function $generateNodesFromDOM(editor, dom) {
|
|
|
32
32
|
return lexicalNodes;
|
|
33
33
|
}
|
|
34
34
|
function $generateHtmlFromNodes(editor, selection) {
|
|
35
|
-
if (typeof document === 'undefined' || typeof window === 'undefined') {
|
|
35
|
+
if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') {
|
|
36
36
|
throw new Error('To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.');
|
|
37
37
|
}
|
|
38
38
|
const container = document.createElement('div');
|
|
@@ -85,7 +85,9 @@ function $appendNodesToHTML(editor, currentNode, parentElement, selection$1 = nu
|
|
|
85
85
|
parentElement.append(element);
|
|
86
86
|
if (after) {
|
|
87
87
|
const newElement = after.call(target, element);
|
|
88
|
-
if (newElement)
|
|
88
|
+
if (newElement) {
|
|
89
|
+
element.replaceWith(newElement);
|
|
90
|
+
}
|
|
89
91
|
}
|
|
90
92
|
} else {
|
|
91
93
|
parentElement.append(fragment);
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
import * as modDev from './LexicalHtml.dev.esm.js';
|
|
8
|
+
import * as modProd from './LexicalHtml.prod.esm.js';
|
|
9
|
+
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
|
10
|
+
export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes;
|
|
11
|
+
export const $generateNodesFromDOM = mod.$generateNodesFromDOM;
|
package/LexicalHtml.js
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
'use strict'
|
|
8
|
-
const LexicalHtml = process.env.NODE_ENV === 'development' ? require('./LexicalHtml.dev.js') : require('./LexicalHtml.prod.js')
|
|
8
|
+
const LexicalHtml = process.env.NODE_ENV === 'development' ? require('./LexicalHtml.dev.js') : require('./LexicalHtml.prod.js');
|
|
9
9
|
module.exports = LexicalHtml;
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
import{$cloneWithProperties as e,$sliceSelectedTextNodeContent as n}from"@lexical/selection";import{isHTMLElement as t}from"@lexical/utils";import{$getRoot as l,$isElementNode as o,$isTextNode as r}from"lexical";function i(e,n){const t=n.body?n.body.childNodes:[];let l=[];for(let n=0;n<t.length;n++){const o=t[n];if(!u.has(o.nodeName)){const n=a(o,e);null!==n&&(l=l.concat(n))}}return l}function c(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++){s(e,o[l],t,n)}return t.innerHTML}function s(l,i,c,u=null){let a=null===u||i.isSelected(u);const d=o(i)&&i.excludeFromCopy("html");let f=i;if(null!==u){let t=e(i);t=r(t)&&null!==u?n(u,t):t,f=t}const p=o(f)?f.getChildren():[],m=l._nodes.get(f.getType());let h;h=m&&void 0!==m.exportDOM?m.exportDOM(l,f):f.exportDOM(l);const{element:g,after:y}=h;if(!g)return!1;const w=document.createDocumentFragment();for(let e=0;e<p.length;e++){const n=p[e],t=s(l,n,w,u);!a&&o(i)&&t&&i.extractWithChild(n,u,"html")&&(a=!0)}if(a&&!d){if(t(g)&&g.append(w),c.append(g),y){const e=y.call(f,g);e&&g.replaceWith(e)}}else c.append(w);return a}const u=new Set(["STYLE","SCRIPT"]);function a(e,n,t=new Map,l){let r=[];if(u.has(e.nodeName))return r;let i=null;const c=function(e,n){const{nodeName:t}=e,l=n._htmlConversions.get(t.toLowerCase());let o=null;if(void 0!==l)for(const n of l){const t=n(e);null!==t&&(null===o||(o.priority||0)<(t.priority||0))&&(o=t)}return null!==o?o.conversion:null}(e,n),s=c?c(e):null;let d=null;if(null!==s){d=s.after;const n=s.node;if(i=Array.isArray(n)?n[n.length-1]:n,null!==i){for(const[,e]of t)if(i=e(i,l),!i)break;i&&r.push(...Array.isArray(n)?n:[i])}null!=s.forChild&&t.set(e.nodeName,s.forChild)}const f=e.childNodes;let p=[];for(let e=0;e<f.length;e++)p.push(...a(f[e],n,new Map(t),i));return null!=d&&(p=d(p)),null==i?r=r.concat(p):o(i)&&i.append(...p),r}export{c as $generateHtmlFromNodes,i as $generateNodesFromDOM};
|
package/LexicalHtml.prod.js
CHANGED
|
@@ -9,5 +9,5 @@ function u(c,e,h,a=null){let f=null!==a?e.isSelected(a):!0,k=q.$isElementNode(e)
|
|
|
9
9
|
x&&e.extractWithChild(t,a,"html")&&(f=!0)}f&&!k?(p.isHTMLElement(l)&&l.append(b),h.append(l),r&&(c=r.call(d,l))&&l.replaceWith(c)):h.append(b);return f}let v=new Set(["STYLE","SCRIPT"]);
|
|
10
10
|
function w(c,e,h=new Map,a){let f=[];if(v.has(c.nodeName))return f;let k=null;var d,{nodeName:g}=c,b=e._htmlConversions.get(g.toLowerCase());g=null;if(void 0!==b)for(d of b)b=d(c),null!==b&&(null===g||(g.priority||0)<(b.priority||0))&&(g=b);g=(d=null!==g?g.conversion:null)?d(c):null;d=null;if(null!==g){d=g.after;b=g.node;k=Array.isArray(b)?b[b.length-1]:b;if(null!==k){for(var [,l]of h)if(k=l(k,a),!k)break;k&&f.push(...(Array.isArray(b)?b:[k]))}null!=g.forChild&&h.set(c.nodeName,g.forChild)}c=c.childNodes;
|
|
11
11
|
a=[];for(l=0;l<c.length;l++)a.push(...w(c[l],e,new Map(h),k));null!=d&&(a=d(a));null==k?f=f.concat(a):q.$isElementNode(k)&&k.append(...a);return f}
|
|
12
|
-
exports.$generateHtmlFromNodes=function(c,e){if("undefined"===typeof document||"undefined"===typeof window)throw Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");let h=document.createElement("div"),a=q.$getRoot().getChildren();for(let f=0;f<a.length;f++)u(c,a[f],h,e);return h.innerHTML};
|
|
12
|
+
exports.$generateHtmlFromNodes=function(c,e){if("undefined"===typeof document||"undefined"===typeof window&&"undefined"===typeof global.window)throw Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");let h=document.createElement("div"),a=q.$getRoot().getChildren();for(let f=0;f<a.length;f++)u(c,a[f],h,e);return h.innerHTML};
|
|
13
13
|
exports.$generateNodesFromDOM=function(c,e){e=e.body?e.body.childNodes:[];let h=[];for(let f=0;f<e.length;f++){var a=e[f];v.has(a.nodeName)||(a=w(a,c),null!==a&&(h=h.concat(a)))}return h}
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# `@lexical/html`
|
|
2
2
|
|
|
3
|
+
[](https://lexical.dev/docs/api/modules/lexical_html)
|
|
4
|
+
|
|
3
5
|
# HTML
|
|
4
6
|
This package exports utility functions for converting `Lexical` -> `HTML` and `HTML` -> `Lexical`. These same functions are also used in the `lexical-clipboard` package for copy and paste.
|
|
5
7
|
|
|
@@ -7,6 +9,13 @@ This package exports utility functions for converting `Lexical` -> `HTML` and `H
|
|
|
7
9
|
|
|
8
10
|
### Exporting
|
|
9
11
|
```js
|
|
12
|
+
// In a headless mode, you need to initialize a headless browser implementation such as JSDom.
|
|
13
|
+
const dom = new JSDOM();
|
|
14
|
+
// @ts-expect-error
|
|
15
|
+
global.window = dom.window;
|
|
16
|
+
global.document = dom.window.document;
|
|
17
|
+
// You may also need to polyfill DocumentFragment or navigator in certain cases.
|
|
18
|
+
|
|
10
19
|
// When converting to HTML you can pass in a selection object to narrow it
|
|
11
20
|
// down to a certain part of the editor's contents.
|
|
12
21
|
const htmlString = $generateHtmlFromNodes(editor, selection | null);
|
|
@@ -31,4 +40,4 @@ const editor = createEditor({ ...config, nodes });
|
|
|
31
40
|
|
|
32
41
|
// Or insert them at a selection.
|
|
33
42
|
$insertNodes(nodes);
|
|
34
|
-
```
|
|
43
|
+
```
|
package/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { BaseSelection, LexicalEditor, LexicalNode } from 'lexical';
|
|
|
10
10
|
/**
|
|
11
11
|
* How you parse your html string to get a document is left up to you. In the browser you can use the native
|
|
12
12
|
* DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
|
|
13
|
-
* or an
|
|
13
|
+
* or an equivalent library and pass in the document here.
|
|
14
14
|
*/
|
|
15
15
|
export declare function $generateNodesFromDOM(editor: LexicalEditor, dom: Document): Array<LexicalNode>;
|
|
16
16
|
export declare function $generateHtmlFromNodes(editor: LexicalEditor, selection?: BaseSelection | null): string;
|
package/package.json
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"html"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.14.0",
|
|
12
12
|
"main": "LexicalHtml.js",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"lexical": "0.
|
|
14
|
+
"lexical": "0.14.0"
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"directory": "packages/lexical-html"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@lexical/selection": "0.
|
|
23
|
-
"@lexical/utils": "0.
|
|
24
|
-
}
|
|
22
|
+
"@lexical/selection": "0.14.0",
|
|
23
|
+
"@lexical/utils": "0.14.0"
|
|
24
|
+
},
|
|
25
|
+
"module": "LexicalHtml.esm.js",
|
|
26
|
+
"sideEffects": false
|
|
25
27
|
}
|