Skip to content

Commit

Permalink
Add HTML paste support for checklists (#3579)
Browse files Browse the repository at this point in the history
  • Loading branch information
acywatson authored Dec 21, 2022
1 parent 6db97e7 commit c67d2aa
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 3 deletions.
5 changes: 4 additions & 1 deletion packages/lexical-list/src/LexicalListItemNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 22 additions & 2 deletions packages/lexical-list/src/LexicalListNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
$isElementNode,
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
EditorThemeClasses,
ElementNode,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -264,11 +278,17 @@ function normalizeChildren(nodes: Array<LexicalNode>): Array<ListItemNode> {
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 {
Expand Down
74 changes: 74 additions & 0 deletions packages/lexical-playground/__tests__/e2e/CopyAndPaste.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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': `<meta charset='utf-8'><ul __lexicallisttype="check"><li role="checkbox" tabindex="-1" aria-checked="false" value="1" class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemUnchecked"><span>Hello</span></li><li role="checkbox" tabindex="-1" aria-checked="false" value="2" class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemUnchecked"><span>world</span></li></ul>`,
};

await pasteFromClipboard(page, clipboard);

await assertHTML(
page,
html`
<ul class="PlaygroundEditorTheme__ul">
<li
role="checkbox"
tabindex="-1"
aria-checked="false"
value="1"
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemUnchecked PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Hello</span>
</li>
<li
role="checkbox"
tabindex="-1"
aria-checked="false"
value="2"
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemUnchecked PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">world</span>
</li>
</ul>
`,
);

await clearEditor(page);
await focusEditor(page);

// Ensure we preserve checked status.
clipboard[
'text/html'
] = `<meta charset='utf-8'><ul __lexicallisttype="check"><li role="checkbox" tabindex="-1" aria-checked="true" value="1" class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemChecked"><span>Hello</span></li><li role="checkbox" tabindex="-1" aria-checked="false" value="2" class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemUnchecked"><span>world</span></li></ul>`;

await pasteFromClipboard(page, clipboard);

await assertHTML(
page,
html`
<ul class="PlaygroundEditorTheme__ul">
<li
role="checkbox"
tabindex="-1"
aria-checked="true"
value="1"
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemChecked PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Hello</span>
</li>
<li
role="checkbox"
tabindex="-1"
aria-checked="false"
value="2"
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__listItemUnchecked PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">world</span>
</li>
</ul>
`,
);
});

test('HTML Copy + paste a code block with BR', async ({
page,
isPlainText,
Expand Down

2 comments on commit c67d2aa

@vercel
Copy link

@vercel vercel bot commented on c67d2aa Dec 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical – ./packages/lexical-website

lexical-fbopensource.vercel.app
www.lexical.dev
lexicaljs.com
lexical-git-main-fbopensource.vercel.app
lexical.dev
lexicaljs.org

@vercel
Copy link

@vercel vercel bot commented on c67d2aa Dec 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical-playground – ./packages/lexical-playground

lexical-playground-fbopensource.vercel.app
lexical-playground-git-main-fbopensource.vercel.app
lexical-playground.vercel.app
playground.lexical.dev

Please sign in to comment.