Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 70 additions & 0 deletions packages/lexical-conformance/src/html.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import type {JSDOM} from 'jsdom';

import {HorizontalRuleNode} from '@lexical/extension';
import {createHeadlessEditor} from '@lexical/headless';
import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html';
import {
Expand All @@ -25,6 +26,7 @@ import {readFileSync} from 'node:fs';
import {test} from 'vitest';

import {browserWindow} from './browserWindow';
import {DOCUMENT_NODES} from './property';
import {emitVectors} from './vectors';

type Case = {
Expand Down Expand Up @@ -265,3 +267,71 @@ test('html/import', () => {
cases,
);
});

/** HTML for the importers of the node packages (lists, rich text, links, code, the horizontal rule). */
const NODES_IMPORT_CORPUS = [
'<ul><li>one</li><li>two<ul><li>nested</li></ul></li></ul>',
'<ol start="3"><li>three</li><li value="7">seven</li></ol>',
'<ol start=" -2x"><li>a</li></ol><ol start="99999999999"><li>b</li></ol>',
'<ul __lexicallisttype="check"><li aria-checked="true">done</li><li aria-checked="false">todo</li></ul>',
'<ul class="contains-task-list"><li class="task-list-item"><input type="checkbox" checked> gh</li><li class="task-list-item"><input type="checkbox"> gh2</li></ul>',
'<ul data-is-checklist="1"><li class="joplin-checkbox"><div class="checkbox-wrapper"><input type="checkbox" checked></div>j</li></ul>',
'<ul><li><p style="text-align: center">docs</p></li><li dir="rtl" style="text-align: right">r</li></ul>',
'<ul><li>a</li>loose<li>b</li></ul>',
'<h1>one</h1><h2 style="text-align: center">two</h2><h3 dir="rtl">three</h3><h4 data-lexical-indent="2">four</h4><h5>five</h5><h6>six</h6>',
'<p><span style="font-size: 26pt">Docs title</span></p>',
'<blockquote style="text-align: right" dir="ltr">quote<br>more</blockquote>',
'<blockquote><p>a</p><p>b</p></blockquote>',
'<p><a href="https://example.com" target="_blank" rel="noopener" title="t">link</a> <a href="/x"></a><a href="#y"><b>bold</b></a></p>',
'<pre data-language="js" data-theme="dark">const a = 1;\n\tb();</pre>',
'<code>one line</code><code>two\nlines</code><code>a<br>b</code>',
'<div style="font-family: monospace">mono<br>code</div><div style="font-family: Menlo, monospace"><div>child</div></div>',
'<table class="js-file-line-container"><tr><td class="blob-num">1</td><td class="js-file-line">line one</td></tr><tr><td>2</td><td class="js-file-line">line two</td></tr></table>',
'<p>before</p><hr><p>after</p>',
'<p>a<hr>b</p>',
];

test('html/import-nodes', () => {
const htmls = [
...NODES_IMPORT_CORPUS,
// Everything property/documents' HTML export writes must import too.
// That file comes from property.test.ts, so a `generate` that changes it
// needs a second run to bring this one up to date.
...JSON.parse(
readFileSync(
new URL('../vectors/property/documents.json', import.meta.url),
'utf8',
),
)
.cases.map((c: {html?: string | null}) => c.html)
.filter((html: unknown): html is string => typeof html === 'string'),
];
const cases: ImportCase[] = htmls.map(html => {
const editor = createHeadlessEditor({
namespace: '',
nodes: [...DOCUMENT_NODES, HorizontalRuleNode],
onError: e => {
throw e;
},
});
let nodes = '';
withJSDOM(win => {
const dom = new win.DOMParser().parseFromString(html, 'text/html');
editor.update(
() => {
nodes = JSON.stringify(
$generateNodesFromDOM(editor, dom).map($nodeToJSON),
);
},
{discrete: true},
);
});
return {html, nodes};
});
emitVectors(
'html/import-nodes',
'html-import',
'HTML for the node packages (curated, plus every property/documents HTML export) and the nodes $generateNodesFromDOM makes from it in jsdom with the property/documents nodes and HorizontalRuleNode, each as exportJSON with its children.',
cases,
);
});
142 changes: 142 additions & 0 deletions packages/lexical-conformance/vectors/html/import-nodes.json

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions rust/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ depends on as the upstream package depends on them:
| `utils` | `@lexical/utils` | `selection` |
| `rich-text` | `@lexical/rich-text` | `selection`, `utils` |
| `plain-text`| `@lexical/plain-text` | (upstream lists `clipboard`, `dragon`, `extension`, `selection` and `utils`; the model handlers use none of them) |
| `list` | `@lexical/list` | `utils` (upstream also lists `extension` and `html`, for `ListExtension` and the DOM import, which aren't ported) |
| `link` | `@lexical/link` | `utils` (upstream also lists `extension` and `html`, for `LinkExtension` and the DOM import, which aren't ported) |
| `list` | `@lexical/list` | `utils` (upstream also lists `extension`, for `ListExtension`, which isn't ported, and `html`, whose parts build with that feature) |
| `link` | `@lexical/link` | `utils` (upstream also lists `extension`, for `LinkExtension`, which isn't ported, and `html`, whose parts build with that feature) |
| `autolink` | `@lexical/link`'s `registerAutoLink` | `link`, `regex` |
| `code` | `@lexical/code-core` | (upstream lists `extension`, `html` and `utils`, for the extensions, the DOM import and the arrow-key handlers, which aren't ported) |
| `code` | `@lexical/code-core` | (upstream lists `extension` and `utils`, for the extensions and the arrow-key handlers, which aren't ported, and `html`, whose parts build with that feature) |
| `overflow` | `@lexical/overflow` | |
| `text` | `@lexical/text` | |
| `hashtag` | `@lexical/hashtag` | `text` (upstream also lists `extension` and `utils`, for `HashtagExtension` and `mergeRegister`, which aren't ported) |
Expand Down Expand Up @@ -173,7 +173,9 @@ model of jsdom's"), since the vectors record what jsdom does:
in the editor state (the text a selection cuts short). The base classes'
behavior is the hooks' defaults (`TextNode` renders its text,
`ElementNode` adds the indent and direction), as for
`get_text_content`. The theme is the JSON of `EditorThemeClasses`
`get_text_content`. A node package's DOM parts build when both its
feature and `html` are on, so the package doesn't pull in the DOM. The
theme is the JSON of `EditorThemeClasses`
(`CreateEditorArgs.theme`), and a `DOMConversionMap` is a list of
name and importer pairs in the order JS's object keys would be.
- **`DOMParser` is a WHATWG tokenizer and tree builder**, checked against
Expand Down Expand Up @@ -226,7 +228,7 @@ Each milestone ends with green CI and a report to the Space.
| P | Desktop playgrounds (`apps/`): `lexical-playground-rune` on Rune, then `lexical-playground-gpui` on GPUI, sharing `lexical-playground-core`; they grow with each milestone below | the vectors, replayed in the app; a session exported from the app replays in JS (`replay.test.ts`); in CI, a GPUI session typed in a real window replays in JS | both apps run on macOS with typing, Return, deletion, formatting, input methods and the Parity panel; they grow with each milestone |
| M2 | `rich-text` (headings, quotes, and the command handlers a text surface dispatches: typing, Enter, Backspace, formatting) and `history` (undo), which the playgrounds need first; then the node packages `list`, `link`, `code`, `overflow`, `hashtag`, with `selection` and `utils` | `serialization/*`, `property/documents` (JSON and text), `entities/hashtag-*`, `scripts/history` | done (every vector it names passes): `history` is done (`scripts/history` passes); `rich-text`'s nodes and `registerRichText`'s model command handlers are done (`scripts/rich-text` and `scripts/rich-text-commands` pass, with the heading and quote cases of `property/documents`); `plain-text`'s model command handlers are done (`scripts/plain-text` passes); `list`'s nodes, `registerList` and the model part of `registerCheckList` are done (`scripts/list` and `serialization/list` pass, with the list cases of `property/documents`); `link`'s nodes, `$toggleLink` and `registerLink`'s model handlers are done (`scripts/link` and `serialization/link` pass, with the link cases of `property/documents` that need no other package), `registerAutoLink` and its matchers are done behind `autolink` (`scripts/autolink` and `entities/autolink-match` pass); `selection`'s `$setBlocksType` is done (`scripts/set-blocks-type` passes); `code`'s nodes, `FlatStructureUtils.ts`, `CodeExtension`'s transform and `registerCodeIndentation`'s model handlers are done (`scripts/code` and `serialization/code` pass, with the code cases of `property/documents` that need no other package); `overflow`'s `OverflowNode` is done (`scripts/overflow` and `serialization/overflow` pass, with the overflow cases of `property/documents` that need no other package); `hashtag`'s `HashtagNode`, its matcher (generated character tables, no `regex`) and `registerLexicalHashtag`, with `text`'s `registerLexicalTextEntity`, are done (`entities/hashtag-match`, `entities/hashtag-documents` and `entities/hashtag-typing` pass, with the last waiting cases of `property/documents`), so M2's node packages are done |
| M3 | The commands `property/sequences` dispatches | `property/sequences` | |
| M4 | `html`: a DOM abstraction over an HTML parser, CSS declarations, export and import | `html/*`, `css/*`, `property/documents` (HTML) | in progress: the DOM (`dom`: nodes, `DOMParser`, the serializer, the inline style declaration and its tables), `getStyleObjectFromCSS`, the core nodes' DOM hooks and `@lexical/html`'s export and import are done (every `html/*` and `css/*` file passes); next, the node packages' DOM parts (list, link, code, overflow, hashtag, rich text, the decorators) and `property/documents`' HTML |
| M4 | `html`: a DOM abstraction over an HTML parser, CSS declarations, export and import | `html/*`, `css/*`, `property/documents` (HTML) | in progress: the DOM (`dom`: nodes, `DOMParser`, the serializer, the inline style declaration and its tables), `getStyleObjectFromCSS`, the core nodes' and the node packages' DOM hooks, and `@lexical/html`'s export and import, are done (every `html/*` and `css/*` file and `property/documents`' HTML pass); not ported: the `DOMImportExtension` and `DOMRenderExtension` pipelines and `$inlineStylesFromStyleSheets` |
| M5 | `markdown` | `markdown/import-export`, `property/documents` (Markdown) | |
| M6 | `yjs`: a port of `@lexical/yjs` on `yrs` (behind the feature), with new JS vectors for the binding (sevki/lexical-rs's `lexical-yjs` and jpcamara/yrby's `lexical-yjs-html` only as cross-checks) | the new collab vectors | |
| M7 | Releases: the mirror, changesets, a name tmm settles | | |
Expand Down
Loading
Loading