2024-08-02 06:16:54 -04:00
|
|
|
import {$isListNode, ListItemNode, SerializedListItemNode} from "@lexical/list";
|
2024-07-30 09:42:19 -04:00
|
|
|
import {EditorConfig} from "lexical/LexicalEditor";
|
|
|
|
import {DOMExportOutput, LexicalEditor, LexicalNode} from "lexical";
|
2024-08-03 13:14:01 -04:00
|
|
|
|
|
|
|
import {el} from "../utils/dom";
|
2024-09-13 10:50:42 -04:00
|
|
|
import {$isCustomListNode} from "./custom-list";
|
2024-07-30 09:42:19 -04:00
|
|
|
|
|
|
|
function updateListItemChecked(
|
|
|
|
dom: HTMLElement,
|
|
|
|
listItemNode: ListItemNode,
|
|
|
|
): void {
|
|
|
|
// Only set task list attrs for leaf list items
|
|
|
|
const shouldBeTaskItem = !$isListNode(listItemNode.getFirstChild());
|
|
|
|
dom.classList.toggle('task-list-item', shouldBeTaskItem);
|
|
|
|
if (listItemNode.__checked) {
|
|
|
|
dom.setAttribute('checked', 'checked');
|
|
|
|
} else {
|
|
|
|
dom.removeAttribute('checked');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class CustomListItemNode extends ListItemNode {
|
|
|
|
static getType(): string {
|
|
|
|
return 'custom-list-item';
|
|
|
|
}
|
|
|
|
|
|
|
|
static clone(node: CustomListItemNode): CustomListItemNode {
|
|
|
|
return new CustomListItemNode(node.__value, node.__checked, node.__key);
|
|
|
|
}
|
|
|
|
|
|
|
|
createDOM(config: EditorConfig): HTMLElement {
|
|
|
|
const element = document.createElement('li');
|
|
|
|
const parent = this.getParent();
|
|
|
|
|
|
|
|
if ($isListNode(parent) && parent.getListType() === 'check') {
|
|
|
|
updateListItemChecked(element, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
element.value = this.__value;
|
|
|
|
|
2024-09-13 10:50:42 -04:00
|
|
|
if ($hasNestedListWithoutLabel(this)) {
|
|
|
|
element.style.listStyle = 'none';
|
|
|
|
}
|
|
|
|
|
2024-07-30 09:42:19 -04:00
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateDOM(
|
|
|
|
prevNode: ListItemNode,
|
|
|
|
dom: HTMLElement,
|
|
|
|
config: EditorConfig,
|
|
|
|
): boolean {
|
|
|
|
const parent = this.getParent();
|
|
|
|
if ($isListNode(parent) && parent.getListType() === 'check') {
|
|
|
|
updateListItemChecked(dom, this);
|
|
|
|
}
|
|
|
|
// @ts-expect-error - this is always HTMLListItemElement
|
|
|
|
dom.value = this.__value;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
|
|
|
const element = this.createDOM(editor._config);
|
|
|
|
element.style.textAlign = this.getFormatType();
|
|
|
|
|
|
|
|
if (element.classList.contains('task-list-item')) {
|
|
|
|
const input = el('input', {
|
|
|
|
type: 'checkbox',
|
|
|
|
disabled: 'disabled',
|
|
|
|
});
|
|
|
|
if (element.hasAttribute('checked')) {
|
|
|
|
input.setAttribute('checked', 'checked');
|
|
|
|
element.removeAttribute('checked');
|
|
|
|
}
|
|
|
|
|
|
|
|
element.prepend(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
element,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
exportJSON(): SerializedListItemNode {
|
|
|
|
return {
|
|
|
|
...super.exportJSON(),
|
|
|
|
type: 'custom-list-item',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-13 10:50:42 -04:00
|
|
|
function $hasNestedListWithoutLabel(node: CustomListItemNode): boolean {
|
|
|
|
const children = node.getChildren();
|
|
|
|
let hasLabel = false;
|
|
|
|
let hasNestedList = false;
|
|
|
|
|
|
|
|
for (const child of children) {
|
|
|
|
if ($isCustomListNode(child)) {
|
|
|
|
hasNestedList = true;
|
|
|
|
} else if (child.getTextContent().trim().length > 0) {
|
|
|
|
hasLabel = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasNestedList && !hasLabel;
|
|
|
|
}
|
|
|
|
|
2024-07-30 09:42:19 -04:00
|
|
|
export function $isCustomListItemNode(
|
|
|
|
node: LexicalNode | null | undefined,
|
|
|
|
): node is CustomListItemNode {
|
|
|
|
return node instanceof CustomListItemNode;
|
2024-09-13 10:50:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function $createCustomListItemNode(): CustomListItemNode {
|
|
|
|
return new CustomListItemNode();
|
2024-07-30 09:42:19 -04:00
|
|
|
}
|