Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lexical-website] Documentation Update: Add Documentation for html Property in Lexical Editor Configuration #6770

Merged
merged 23 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f381e10
docs: Add Documentation for html Property in Lexical Editor Configur…
Kingscliq Oct 26, 2024
5b2df82
docs: updated docs on the html property of the editor configuarion
Kingscliq Oct 27, 2024
036af2e
Merge branch 'main' into docs-html-property
Kingscliq Oct 27, 2024
a70028a
Merge branch 'main' into docs-html-property
Kingscliq Oct 27, 2024
5323a95
Merge branch 'main' into docs-html-property
Kingscliq Oct 28, 2024
6f7378e
chore: add example implementation of the html property of the HTMLCon…
Kingscliq Oct 30, 2024
ef06f04
chore: added comments for the *
Kingscliq Oct 30, 2024
c68f9bf
Merge branch 'main' into docs-html-property
Kingscliq Oct 30, 2024
605cf78
Merge branch 'docs-html-property' of https://github.com/Kingscliq/lex…
Kingscliq Oct 31, 2024
0e27d68
Update packages/lexical-utils/src/index.ts
Kingscliq Nov 1, 2024
a410c85
Update packages/lexical-website/docs/concepts/serialization.md
Kingscliq Nov 1, 2024
5fe53e3
Merge branch 'main' into docs-html-property
Kingscliq Nov 4, 2024
83677da
chore: deleing yarn lock file since package is geenrated using npm
Kingscliq Nov 4, 2024
2fe866a
Merge branch 'docs-html-property' of https://github.com/Kingscliq/lex…
Kingscliq Nov 4, 2024
42daae7
Merge branch 'main' into docs-html-property
Kingscliq Nov 5, 2024
0e11e8e
Merge branch 'main' of https://github.com/Kingscliq/lexical into docs…
Kingscliq Nov 5, 2024
205db92
chore: add the import property configuration
Kingscliq Nov 5, 2024
06599ce
Merge branch 'docs-html-property' of https://github.com/Kingscliq/lex…
Kingscliq Nov 5, 2024
ed613e5
Update examples/react-rich/src/App.tsx
Kingscliq Nov 5, 2024
0fd6257
Merge branch 'main' into docs-html-property
Kingscliq Nov 5, 2024
1a84724
chore: updates addition of examples to the serialisation docs
Kingscliq Nov 5, 2024
74d3a14
Update packages/lexical-website/docs/concepts/serialization.md
etrepum Nov 5, 2024
c0e1237
Merge branch 'main' into docs-html-property
etrepum Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 113 additions & 3 deletions examples/react-rich/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,137 @@
* LICENSE file in the root directory of this source tree.
*
*/

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 {
$isTextNode,
DOMConversionMap,
DOMExportOutput,
Klass,
LexicalEditor,
LexicalNode,
ParagraphNode,
TextNode,
} from 'lexical';

import ExampleTheme from './ExampleTheme';
import ToolbarPlugin from './plugins/ToolbarPlugin';
import TreeViewPlugin from './plugins/TreeViewPlugin';
import {parseAllowedColor, parseAllowedFontSize} from './styleConfig';

const placeholder = 'Enter some rich text...';

const removeStylesExportDOM = (
editor: LexicalEditor,
target: LexicalNode,
): DOMExportOutput => {
const output = target.exportDOM(editor);
if (output && output.element instanceof HTMLElement) {
// Remove all inline styles and classes if the element is an HTMLElement
// Children are checked as well since TextNode can be nested
// in i, b, and strong tags.
for (const el of [
output.element,
...output.element.querySelectorAll('[style],[class],[dir="ltr"]'),
]) {
el.removeAttribute('class');
el.removeAttribute('style');
if (el.getAttribute('dir') === 'ltr') {
el.removeAttribute('dir');
}
}
}
return output;
};

const exportMap = new Map<
Klass<LexicalNode>,
(editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
>([
[ParagraphNode, removeStylesExportDOM],
[TextNode, removeStylesExportDOM],
]);

const getExtraStyles = (element: HTMLElement): string => {
// Parse styles from pasted input, but only if they match exactly the
// sort of styles that would be produced by exportDOM
let extraStyles = '';
const fontSize = parseAllowedFontSize(element.style.fontSize);
const backgroundColor = parseAllowedColor(element.style.backgroundColor);
const color = parseAllowedColor(element.style.color);
if (fontSize !== '' && fontSize !== '15px') {
extraStyles += `font-size: ${fontSize};`;
}
if (backgroundColor !== '' && backgroundColor !== 'rgb(255, 255, 255)') {
extraStyles += `background-color: ${backgroundColor};`;
}
if (color !== '' && color !== 'rgb(0, 0, 0)') {
extraStyles += `color: ${color};`;
}
return extraStyles;
};

const constructImportMap = (): DOMConversionMap => {
const importMap: DOMConversionMap = {};

// Wrap all TextNode importers with a function that also imports
// the custom styles implemented by the playground
for (const [tag, fn] of Object.entries(TextNode.importDOM() || {})) {
importMap[tag] = (importNode) => {
const importer = fn(importNode);
if (!importer) {
return null;
}
return {
...importer,
conversion: (element) => {
const output = importer.conversion(element);
if (
output === null ||
output.forChild === undefined ||
output.after !== undefined ||
output.node !== null
) {
return output;
}
const extraStyles = getExtraStyles(element);
if (extraStyles) {
const {forChild} = output;
return {
...output,
forChild: (child, parent) => {
const textNode = forChild(child, parent);
if ($isTextNode(textNode)) {
textNode.setStyle(textNode.getStyle() + extraStyles);
}
return textNode;
},
};
}
return output;
},
};
};
}

return importMap;
};

const editorConfig = {
html: {
export: exportMap,
import: constructImportMap(),
},
namespace: 'React.js Demo',
nodes: [],
// Handling of errors during update
nodes: [ParagraphNode, TextNode],
onError(error: Error) {
throw error;
},
// The editor theme
theme: ExampleTheme,
};

Expand Down
1 change: 1 addition & 0 deletions examples/react-rich/src/ExampleTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
*/

export default {
code: 'editor-code',
heading: {
Expand Down
25 changes: 25 additions & 0 deletions examples/react-rich/src/styleConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

const MIN_ALLOWED_FONT_SIZE = 8;
const MAX_ALLOWED_FONT_SIZE = 72;

export const parseAllowedFontSize = (input: string): string => {
const match = input.match(/^(\d+(?:\.\d+)?)px$/);
if (match) {
const n = Number(match[1]);
if (n >= MIN_ALLOWED_FONT_SIZE && n <= MAX_ALLOWED_FONT_SIZE) {
return input;
}
}
return '';
};

export function parseAllowedColor(input: string) {
return /^rgb\(\d+, \d+, \d+\)$/.test(input) ? input : '';
}
29 changes: 29 additions & 0 deletions packages/lexical-website/docs/concepts/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,32 @@ function patchStyleConversion(
};
}
```

### `html` Property for Import and Export Configuration

The `html` property in `CreateEditorArgs` provides an alternate way to configure HTML import and export behavior in Lexical without subclassing or node replacement. It includes two properties:

- `import` - Similar to `importDOM`, it controls how HTML elements are transformed into `LexicalNodes`. However, instead of defining conversions directly on each `LexicalNode`, `html.import` provides a configuration that can be overridden easily in the editor setup.

- `export` - Similar to `exportDOM`, this property customizes how `LexicalNodes` are serialized into HTML. With `html.export`, users can specify transformations for various nodes collectively, offering a flexible override mechanism that can adapt without needing to extend or replace specific `LexicalNodes`.

#### Key Differences from `importDOM` and `exportDOM`

While `importDOM` and `exportDOM` allow for highly customized, node-specific conversions by defining them directly within the `LexicalNode` class, the `html` property enables broader, editor-wide configurations. This setup benefits situations where:

- **Consistent Transformations**: You want uniform import/export behavior across different nodes without adjusting each node individually.
- **No Subclassing Required**: Overrides to import and export logic are applied at the editor configuration level, simplifying customization and reducing the need for extensive subclassing.

#### Type Definitions

```typescript
type HTMLConfig = {
export?: DOMExportOutputMap; // Optional map defining how nodes are exported to HTML.
import?: DOMConversionMap; // Optional record defining how HTML is converted into nodes.
};
```

Kingscliq marked this conversation as resolved.
Show resolved Hide resolved
#### Example of a use case for the `html` Property for Import and Export Configuration:

[Rich text sandbox](https://stackblitz.com/github/facebook/lexical/tree/main/examples/react-rich?embed=1&file=src%2FApp.tsx&terminalHeight=0&ctl=1&showSidebar=0&devtoolsheight=0&view=preview)

Loading