From f00644737c0ac1500c12f8e22c650fde6e39524f Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Sat, 17 Dec 2022 12:39:45 -0700 Subject: [PATCH 1/2] add HTML paste support for checklists --- packages/lexical-list/src/LexicalListItemNode.ts | 4 +++- packages/lexical-list/src/LexicalListNode.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index a3cd49ab662..cc683799362 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -518,7 +518,9 @@ function updateListItemChecked( } function convertListItemElement(domNode: Node): DOMConversionOutput { - return {node: $createListItemNode()}; + // @ts-ignore-next-line + const checked = domNode['aria-checked'] === 'true'; + return {node: $createListItemNode(checked)}; } export function $createListItemNode(checked?: boolean): ListItemNode { diff --git a/packages/lexical-list/src/LexicalListNode.ts b/packages/lexical-list/src/LexicalListNode.ts index acd55132304..9b17f8f8fe6 100644 --- a/packages/lexical-list/src/LexicalListNode.ts +++ b/packages/lexical-list/src/LexicalListNode.ts @@ -268,6 +268,10 @@ function convertListNode(domNode: Node): DOMConversionOutput { if (nodeName === 'ol') { node = $createListNode('number'); } else if (nodeName === 'ul') { + // @ts-ignore-next-line this prop might exist + if (domNode.__lexicalListType === 'check') { + node = $createListNode('check'); + } node = $createListNode('bullet'); } From 31cb4e83b34d26ba2882afdf923ae603c9a83cd0 Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Mon, 19 Dec 2022 15:33:33 -0700 Subject: [PATCH 2/2] fix --- .../lexical-list/src/LexicalListItemNode.ts | 5 +- packages/lexical-list/src/LexicalListNode.ts | 24 +++++- .../__tests__/e2e/CopyAndPaste.spec.mjs | 74 +++++++++++++++++++ 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index cc683799362..ac6728bba73 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -518,8 +518,9 @@ function updateListItemChecked( } function convertListItemElement(domNode: Node): DOMConversionOutput { - // @ts-ignore-next-line - const checked = domNode['aria-checked'] === 'true'; + const checked = + domNode instanceof HTMLElement && + domNode.getAttribute('aria-checked') === 'true'; return {node: $createListItemNode(checked)}; } diff --git a/packages/lexical-list/src/LexicalListNode.ts b/packages/lexical-list/src/LexicalListNode.ts index 9b17f8f8fe6..623c9a84c8d 100644 --- a/packages/lexical-list/src/LexicalListNode.ts +++ b/packages/lexical-list/src/LexicalListNode.ts @@ -16,6 +16,7 @@ import { $isElementNode, DOMConversionMap, DOMConversionOutput, + DOMExportOutput, EditorConfig, EditorThemeClasses, ElementNode, @@ -134,6 +135,19 @@ export class ListNode extends ElementNode { return node; } + exportDOM(editor: LexicalEditor): DOMExportOutput { + const element = document.createElement(this.__tag); + if (this.__start !== 1) { + element.setAttribute('start', String(this.__start)); + } + if (this.__listType === 'check') { + element.setAttribute('__lexicalListType', 'check'); + } + return { + element, + }; + } + exportJSON(): SerializedListNode { return { ...super.exportJSON(), @@ -264,15 +278,17 @@ function normalizeChildren(nodes: Array): Array { function convertListNode(domNode: Node): DOMConversionOutput { const nodeName = domNode.nodeName.toLowerCase(); let node = null; - if (nodeName === 'ol') { node = $createListNode('number'); } else if (nodeName === 'ul') { - // @ts-ignore-next-line this prop might exist - if (domNode.__lexicalListType === 'check') { + if ( + domNode instanceof HTMLElement && + domNode.getAttribute('__lexicallisttype') === 'check' + ) { node = $createListNode('check'); + } else { + node = $createListNode('bullet'); } - node = $createListNode('bullet'); } return { diff --git a/packages/lexical-playground/__tests__/e2e/CopyAndPaste.spec.mjs b/packages/lexical-playground/__tests__/e2e/CopyAndPaste.spec.mjs index ee839d49cc8..1d89af768a3 100644 --- a/packages/lexical-playground/__tests__/e2e/CopyAndPaste.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CopyAndPaste.spec.mjs @@ -2577,6 +2577,80 @@ test.describe('CopyAndPaste', () => { ); }); + test('HTML Copy + paste a checklist', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': `
`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    + + +
+ `, + ); + + await clearEditor(page); + await focusEditor(page); + + // Ensure we preserve checked status. + clipboard[ + 'text/html' + ] = `
`; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    + + +
+ `, + ); + }); + test('HTML Copy + paste a code block with BR', async ({ page, isPlainText,