@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
124 lines (113 loc) • 3.12 kB
text/typescript
function getChildIndex(node: Element) {
return Array.prototype.indexOf.call(node.parentElement!.childNodes, node);
}
function isWhitespaceNode(node: Node) {
return node.nodeType === 3 && !/\S/.test(node.nodeValue || "");
}
/**
* Step 1, Turns:
*
* <ul>
* <li>item</li>
* <li>
* <ul>
* <li>...</li>
* <li>...</li>
* </ul>
* </li>
*
* Into:
* <ul>
* <li>item</li>
* <ul>
* <li>...</li>
* <li>...</li>
* </ul>
* </ul>
*
*/
function liftNestedListsToParent(element: HTMLElement) {
element.querySelectorAll("li > ul, li > ol").forEach((list) => {
const index = getChildIndex(list);
const parentListItem = list.parentElement!;
const siblingsAfter = Array.from(parentListItem.childNodes).slice(
index + 1,
);
list.remove();
siblingsAfter.forEach((sibling) => {
sibling.remove();
});
parentListItem.insertAdjacentElement("afterend", list);
siblingsAfter.reverse().forEach((sibling) => {
if (isWhitespaceNode(sibling)) {
return;
}
const siblingContainer = document.createElement("li");
siblingContainer.append(sibling);
list.insertAdjacentElement("afterend", siblingContainer);
});
if (parentListItem.childNodes.length === 0) {
parentListItem.remove();
}
});
}
/**
* Step 2, Turns (output of liftNestedListsToParent):
*
* <li>item</li>
* <ul>
* <li>...</li>
* <li>...</li>
* </ul>
*
* Into:
* <div>
* <li>item</li>
* <div data-node-type="blockGroup">
* <ul>
* <li>...</li>
* <li>...</li>
* </ul>
* </div>
* </div>
*
* This resulting format is parsed
*/
function createGroups(element: HTMLElement) {
element.querySelectorAll("li + ul, li + ol").forEach((list) => {
const listItem = list.previousElementSibling as HTMLElement;
const blockContainer = document.createElement("div");
listItem.insertAdjacentElement("afterend", blockContainer);
blockContainer.append(listItem);
const blockGroup = document.createElement("div");
blockGroup.setAttribute("data-node-type", "blockGroup");
blockContainer.append(blockGroup);
while (
blockContainer.nextElementSibling?.nodeName === "UL" ||
blockContainer.nextElementSibling?.nodeName === "OL"
) {
blockGroup.append(blockContainer.nextElementSibling);
}
});
}
// prevent XSS, similar to https://github.com/ProseMirror/prosemirror-view/blob/1251b2b412656a2a06263e4187574beb43651273/src/clipboard.ts#L204
// https://github.com/TypeCellOS/BlockNote/issues/601
let _detachedDoc: Document | null = null;
function detachedDoc() {
return (
_detachedDoc ||
(_detachedDoc = document.implementation.createHTMLDocument("title"))
);
}
export function nestedListsToBlockNoteStructure(
elementOrHTML: HTMLElement | string,
) {
if (typeof elementOrHTML === "string") {
const element = detachedDoc().createElement("div");
element.innerHTML = elementOrHTML;
elementOrHTML = element;
}
liftNestedListsToParent(elementOrHTML);
createGroups(elementOrHTML);
return elementOrHTML;
}