Closed Kingscliq closed 2 weeks ago
The latest updates on your projects. Learn more about Vercel for Git ↗︎
Name | Status | Preview | Comments | Updated (UTC) |
---|---|---|---|---|
lexical | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | Nov 5, 2024 7:39pm |
lexical-playground | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | Nov 5, 2024 7:39pm |
Path | Size |
---|---|
lexical - cjs | 29.92 KB (0%) |
lexical - esm | 29.78 KB (0%) |
@lexical/rich-text - cjs | 38.57 KB (0%) |
@lexical/rich-text - esm | 31.63 KB (0%) |
@lexical/plain-text - cjs | 37.22 KB (0%) |
@lexical/plain-text - esm | 28.94 KB (0%) |
@lexical/react - cjs | 40.33 KB (0%) |
@lexical/react - esm | 33.06 KB (0%) |
Was this written with ChatGPT or something like that? The code example doesn't really seem close to correct. I would recommend writing an example of this functionality in one of the example packages or the playground so you can verify that the types are correct and it behaves as expected. The theme usage isn't relevant here, I'd remove that.
Was this written with ChatGPT or something like that? The code example doesn't really seem close to correct. I would recommend writing an example of this functionality in one of the example packages or the playground so you can verify that the types are correct and it behaves as expected. The theme usage isn't relevant here, I'd remove that.
Thanks for the feedback @etrepum , I will create an example I think that's probably the best way to verify the functionality.
Quick question though, can I use the pattern for the exportDom feature on the playground, like a button that calls the html.export()
assuming that's how it is called to behave exactly like the exportDom feature, I highlighted earlier
If you were using the playground, or another example that has the debug tree view, then the "Export DOM" button is sufficient. You don't need to add any additional UI. Only the configuration passed to createEditor or LexicalComposer would change to add the html property. The whole purpose is that this configuration can be used to alter the behavior of existing export functionality.
What the example would do is provide some configuration that makes it so some node (or perhaps multiple nodes) have a different output than their default exportDOM implementation. If this is configured correctly, then the change would be apparent in the tree view after clicking Export DOM. The export HTML output can also be observed by copying text from the editor and then pasting it somewhere to see the text/html content such as https://evercoder.github.io/clipboard-inspector/
Possibly a simple and useful example would be some configuration that removes style from the default output. <span style="white-space: pre-wrap;">Welcome to the playground</span>
is how a TextNode in the playground is exported, but maybe the example could change that to <span>Welcome to the playground</span>
>
If you were using the playground, or another example that has the debug tree view, then the "Export DOM" button is sufficient. You don't need to add any additional UI. Only the configuration passed to createEditor or LexicalComposer would change to add the html property. The whole purpose is that this configuration can be used to alter the behavior of existing export functionality.
What the example would do is provide some configuration that makes it so some node (or perhaps multiple nodes) have a different output than their default exportDOM implementation. If this is configured correctly, then the change would be apparent in the tree view after clicking Export DOM. The export HTML output can also be observed by copying text from the editor and then pasting it somewhere to see the text/html content such as https://evercoder.github.io/clipboard-inspector/
Possibly a simple and useful example would be some configuration that removes style from the default output.
<span style="white-space: pre-wrap;">Welcome to the playground</span>
is how a TextNode in the playground is exported, but maybe the example could change that to<span>Welcome to the playground</span>
Got it! Just scanning through the examples
dir, so do you suggest I create a new installation or use an already existing installation, from what I am seeing I think the react-rich dir is quite okay for what I am trying to achieve, what do you think?
I noticed that the TreeViewPlugin
isn’t directly editable from the examples, as it’s part of an installed package. To modify it, I’d need to make changes within the lexical-react
package itself if I want my examples to work with the React package. The same approach would apply to the lexical-playground
. Could you confirm if the examples package and similar dependencies pull changes directly from the monorepo files, or if publishing is required before these changes are accessible?
You don't need to touch the TreeViewPlugin. Its "Export DOM" functionality does exactly the right thing to demonstrate this without any modification. The only thing you will need to change is the configuration passed to createEditor or LexicalComposer.
Any of the examples is fine, or the playground.
You don't need to touch the TreeViewPlugin. Its "Export DOM" functionality does exactly the right thing to demonstrate this without any modification. The only thing you will need to change is the configuration passed to createEditor or LexicalComposer.
Any of the examples is fine, or the playground.
Okay, got it!, I will go through the LexicalComposer API and see how I can update the configuration
Hi @etrepum I was going through the source and realized that LexicalComposer was used in a lot of places, which means I can create a new react application with a new LexicalComposer Instance and ensure that I add the html attribute to the to the configuration and pass it as initialConfig on the LexicalComposer do you think it will overwrite the exportDOM called on the TreeView plugin?
Just like we have in
import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import ExampleTheme from './ExampleTheme';
import ToolbarPlugin from './plugins/ToolbarPlugin';
import TreeViewPlugin from './plugins/TreeViewPlugin';
const placeholder = 'Enter some rich text...';
const editorConfig = {
namespace: 'React.js Demo',
nodes: [],
// Handling of errors during update
onError(error: Error) {
throw error;
},
// The editor theme
theme: ExampleTheme,
};
export default function App() {
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
<ToolbarPlugin />
<div className="editor-inner">
<RichTextPlugin
contentEditable={
<ContentEditable
className="editor-input"
aria-placeholder={placeholder}
placeholder={
<div className="editor-placeholder">{placeholder}</div>
}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
<AutoFocusPlugin />
<TreeViewPlugin />
</div>
</div>
</LexicalComposer>
);
}
I got this from examples/react-rich
Yes, if the html property is set on that initialConfig then any effect its export property has would be observable from the TreeViewPlugin or the clipboard.
Yes, if the html property is set on that initialConfig then any effect its export property has would be observable from the TreeViewPlugin or the clipboard.
okay! thanks, will do that then.
Can I create a new react-app on the examples folder?, what name do I give it(is there a convention for naming)?, or do I link a stackblitz URL to the .md file that have the field documented?
There is not a documented convention for the naming of example packages. It would be a lot less work to add this configuration example to an existing example or the playground, I would recommend that approach.
There is not a documented convention for the naming of example packages. It would be a lot less work to add this configuration example to an existing example or the playground, I would recommend that approach.
I wanted to talk about the html property for a bit, I am not sure how and what configuration it needs if I am trying to pass it to the editorConfiguration
for example
import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import ExampleTheme from './ExampleTheme';
import ToolbarPlugin from './plugins/ToolbarPlugin';
import TreeViewPlugin from './plugins/TreeViewPlugin';
const placeholder = 'Enter some rich text...';
const editorConfig = {
namespace: 'React.js Demo',
nodes: [],
// Handling of errors during update
onError(error: Error) {
throw error;
},
// The editor theme
theme: ExampleTheme,
html: {
import: {} // with some values or configs,
export: {
// with some values or config
}
} ,
};
export default function App() {
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
<ToolbarPlugin />
<div className="editor-inner">
<RichTextPlugin
contentEditable={
<ContentEditable
className="editor-input"
aria-placeholder={placeholder}
placeholder={
<div className="editor-placeholder">{placeholder}</div>
}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
<AutoFocusPlugin />
<TreeViewPlugin />
</div>
</div>
</LexicalComposer>
);
}
in this snippet the editorConfig has a bunch of values, the nameSpace allows you to pass a string, and the theme allows you pass a default theme configuration, for the HTML its expects the import and export properties which have their own key-value pairs, how does one know what exactly to pass to those key values to maintain the exact behavior we want, more like the overwrite of the exportDOM and importDOM behaviors
The way you would figure it out is to read through the type definitions to see what types are expected. In above messages you can see that the type of the html property is as follows:
Then you can look to see what the types of DOMExportOutputMap
and DOMConversionMap
are
You can also see how importDOM and exportDOM use parts of those types
If you look closely you can see that DOMExportOutputMap and DOMConversionMap have almost exactly the same signature as exportDOM and importDOM. In the exportDOM case it's a Map
of Klass<LexicalNode>
(this is just the awkward way that we have to refer to the class of a node rather than an instance of that class in typescript) to functions instead of a method of those classes. In the importDOM case it's just exactly the same signature as the static importDOM function
In essence the html property would look something like this:
/* this is the same signature as an importDOM
function removeStylesExportDOM(editor: LexicalEditor, target: LexicalNode): DOMExportOutput {
// Call the original node's implementation of exportDOM
// and remove any exported style from the element
const output = target.exportDOM(editor);
output.element.style = '';
return output;
}
const export: DOMExportOutputMap = new Map([
[ParagraphNode, removeStylesExportDOM],
[TextNode, removeStylesExportDOM],
]);
const import: DOMConversionMap = /* this would be essentially the same as the return value of an importDOM */;
const editorConfig = {
html: { import, export },
/* other values here */
};
The way you would figure it out is to read through the type definitions to see what types are expected. In above messages you can see that the type of the html property is as follows:
Then you can look to see what the types of
DOMExportOutputMap
andDOMConversionMap
areYou can also see how importDOM and exportDOM use parts of those types
If you look closely you can see that DOMExportOutputMap and DOMConversionMap have almost exactly the same signature as exportDOM and importDOM. In the exportDOM case it's a
Map
ofKlass<LexicalNode>
(this is just the awkward way that we have to refer to the class of a node rather than an instance of that class in typescript) to functions instead of a method of those classes. In the importDOM case it's just exactly the same signature as the static importDOM functionIn essence the html property would look something like this:
/* this is the same signature as an importDOM function removeStylesExportDOM(editor: LexicalEditor, target: LexicalNode): DOMExportOutput { // Call the original node's implementation of exportDOM // and remove any exported style from the element const output = target.exportDOM(editor); output.element.style = ''; return output; } const export: DOMExportOutputMap = new Map([ [ParagraphNode, removeStylesExportDOM], [TextNode, removeStylesExportDOM], ]); const import: DOMConversionMap = /* this would be essentially the same as the return value of an importDOM */; const editorConfig = { html: { import, export }, /* other values here */ };
Got it! thanks @etrepum
Hi @etrepum thanks for clarifying things for me on this issue
I noticed that
export type HTMLConfig = { export?: DOMExportOutputMap; import?: DOMConversionMap; };
HTMLConfig's import and export has two types imported from the lexical. DOMConversionMap
works for import but I couldn't import DOMExportMap from lexical, since I am using react-rich
example to add the configurations
How do you think I can import DOMExportOutputMap
. to the react project I am using?
We should export that too, if you wanted to fix that, but you could use something like this:
type DOMExportOutputMap = NonNullable<HTMLConfig['export']>;
We should export that too, if you wanted to fix that, but you could use something like this:
type DOMExportOutputMap = NonNullable<HTMLConfig['export']>;
Oh great hack, I tried something like this
const removeStylesExportDOM = (
editor: LexicalEditor,
target: LexicalNode,
): DOMExportOutput => {
// Call the original node's implementation of exportDOM
const output = target.exportDOM(editor);
if (output && output.element instanceof HTMLElement) {
// Remove all inline styles if the element is an HTMLElement
output.element.removeAttribute('style');
}
return output;
};
type DOMExportOutputMap = NonNullable<HTMLConfig['export']>;
const exportMap: DOMExportOutputMap = new Map([
[ParagraphNode, removeStylesExportDOM],
[TextNode, removeStylesExportDOM],
]);
The Map is Javascript Map right? I used it and getting some typescript overload matches errors
No overload matches this call.
Overload 1 of 4, '(iterable?: Iterable<readonly [typeof ParagraphNode, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput]> | null | undefined): Map<...>', gave the following error.
Argument of type '([typeof ParagraphNode, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput] | [typeof TextNode, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput])[]' is not assignable to parameter of type 'Iterable<readonly [typeof ParagraphNode, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput]>'.
It looks like TypeScript is unable to infer the type of the Map correctly with the initializer, either you can expand the type yourself or you can use the setter form so that TypeScript performs the inference correctly.
const export = new Map<
Klass<LexicalNode>,
(editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
>([
[ParagraphNode, removeStylesExportDOM],
[TextNode, removeStylesExportDOM],
]);
const export: DOMExportOutputMap = new Map();
export.set(ParagraphNode, removeStylesExportDOM);
export.set(TextNode, removeStylesExportDOM);
It looks like TypeScript is unable to infer the type of the Map correctly with the initializer, either you can expand the type yourself or you can use the setter form so that TypeScript performs the inference correctly.
const export = new Map< Klass<LexicalNode>, (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput >([ [ParagraphNode, removeStylesExportDOM], [TextNode, removeStylesExportDOM], ]);
const export: DOMExportOutputMap = new Map(); export.set(ParagraphNode, removeStylesExportDOM); export.set(TextNode, removeStylesExportDOM);
Okay I will try this
https://github.com/facebook/lexical/pull/6780 has been merged so it should be fairly straightforward for you to refer to its implementation in the example for the html import config
6780 has been merged so it should be fairly straightforward for you to refer to its implementation in the example for the html import config
Great!! hoping on it right now. In terms of linking this example to the markdown files, do I need to refer to the react-rich version or do you think I could copy the code over to Stackblitz and link it?
I realized there was no color palette or fontSize on the toolbar so I will have that implemented to test the behaviour of the configuration
I think this is very close! For the example to be helpful we need to link to it from the documentation, otherwise people won't know to look here
got it!
Updated with a link to the example @etrepum
Description
Closes #6762
Test plan
Before
The html property in CreateEditorArgs is undocumented, lacking examples for its import and export properties. These functions have similar functionality to importDOM and exportDOM but are undocumented, creating ambiguity for users looking to extend or customize HTML handling.
After
The html property is now fully documented, with examples illustrating the import and export properties. The import property demonstrates mappings for transforming HTML elements into Lexical nodes, while export shows how to convert nodes back to HTML. These examples provide clarity and guide users in extending or customizing their editor’s HTML handling, building on the concepts of importDOM and exportDOM.