// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { elementIsEmpty, nodeIsElement, nodeIsText } from "../../../lib/dom"; import type { Match } from "../match-type"; import type { TreeNode } from "../tree"; import { BlockNode, ElementNode, FormattingNode } from "../tree"; import { appendNode } from "./add-merge"; import type { BuildFormat } from "./format"; function buildFromElement( element: Element, format: BuildFormat, matchAncestors: Match[], ): TreeNode[] { const match = format.createMatch(element); if (match.matches) { matchAncestors = [...matchAncestors, match]; } let children: TreeNode[] = []; for (const child of [...element.childNodes]) { const nodes = buildFromNode(child, format, matchAncestors); for (const node of nodes) { children = appendNode(children, node, format); } } if (match.shouldRemove()) { const parent = element.parentElement!; const childIndex = Array.prototype.indexOf.call(parent.childNodes, element); for (const child of children) { if (child instanceof FormattingNode) { if (child.hasMatchHoles) { child.matchLeaves.push(match); child.hasMatchHoles = false; } child.range.parent = parent; child.range.startIndex += childIndex; child.range.endIndex += childIndex; } } element.replaceWith(...element.childNodes); return children; } const matchNode = ElementNode.make( element, children.every((node: TreeNode): boolean => node.insideRange), ); if (children.length === 0) { // This means there are no non-negligible children return []; } else if (children.length === 1) { const [only] = children; if ( // blocking only instanceof BlockNode // ascension || (only instanceof FormattingNode && format.tryAscend(only, matchNode)) ) { return [only]; } } matchNode.replaceChildren(...children); return [matchNode]; } function buildFromText( text: Text, format: BuildFormat, matchAncestors: Match[], ): FormattingNode | BlockNode { const insideRange = format.isInsideRange(text); if (!insideRange && matchAncestors.length === 0) { return BlockNode.make(); } return FormattingNode.fromText(text, insideRange, matchAncestors); } function elementIsNegligible(element: Element): boolean { return elementIsEmpty(element); } function textIsNegligible(text: Text): boolean { return text.length === 0; } /** * Builds a formatting tree starting at node. * * @returns root of the formatting tree */ export function buildFromNode( node: Node, format: BuildFormat, matchAncestors: Match[], ): TreeNode[] { if (nodeIsText(node) && !textIsNegligible(node)) { return [buildFromText(node, format, matchAncestors)]; } else if (nodeIsElement(node) && !elementIsNegligible(node)) { return buildFromElement(node, format, matchAncestors); } else { return []; } }