@lexical/html 0.13.1 → 0.14.1

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.
@@ -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 };
@@ -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 equivilant library and pass in the document here.
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 : [];
@@ -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) element.replaceWith(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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # `@lexical/html`
2
2
 
3
+ [![See API Documentation](https://lexical.dev/img/see-api-documentation.svg)](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 equivilant library and pass in the document here.
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.13.1",
11
+ "version": "0.14.1",
12
12
  "main": "LexicalHtml.js",
13
13
  "peerDependencies": {
14
- "lexical": "0.13.1"
14
+ "lexical": "0.14.1"
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.13.1",
23
- "@lexical/utils": "0.13.1"
24
- }
22
+ "@lexical/selection": "0.14.1",
23
+ "@lexical/utils": "0.14.1"
24
+ },
25
+ "module": "LexicalHtml.esm.js",
26
+ "sideEffects": false
25
27
  }