b2049209ff
There are some style differences compared to prettier, and not all are necessarily an improvement, but it's much faster now.
117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
// 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<T>(
|
|
element: Element,
|
|
format: BuildFormat<T>,
|
|
matchAncestors: Match<T>[],
|
|
): 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<T>(
|
|
text: Text,
|
|
format: BuildFormat<T>,
|
|
matchAncestors: Match<T>[],
|
|
): FormattingNode<T> | 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<T>(
|
|
node: Node,
|
|
format: BuildFormat<T>,
|
|
matchAncestors: Match<T>[],
|
|
): TreeNode[] {
|
|
if (nodeIsText(node) && !textIsNegligible(node)) {
|
|
return [buildFromText(node, format, matchAncestors)];
|
|
} else if (nodeIsElement(node) && !elementIsNegligible(node)) {
|
|
return buildFromElement(node, format, matchAncestors);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|