From c67d2aa75d4398e599a85f8bef0c59b9c58ac72b Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Tue, 20 Dec 2022 19:13:10 -0700 Subject: [PATCH] Add HTML paste support for checklists (#3579) --- .../lexical-list/src/LexicalListItemNode.ts | 5 +- packages/lexical-list/src/LexicalListNode.ts | 24 +++++- .../__tests__/e2e/CopyAndPaste.spec.mjs | 74 +++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index a3cd49ab662..ac6728bba73 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -518,7 +518,10 @@ function updateListItemChecked( } function convertListItemElement(domNode: Node): DOMConversionOutput { - return {node: $createListItemNode()}; + const checked = + domNode instanceof HTMLElement && + domNode.getAttribute('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..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,11 +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') { - node = $createListNode('bullet'); + if ( + domNode instanceof HTMLElement && + domNode.getAttribute('__lexicallisttype') === 'check' + ) { + node = $createListNode('check'); + } else { + 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,