Anki/ts/lib/domlib/surround/build/build-tree.ts
Damien Elmes 9f55cf26fc
Switch to SvelteKit (#3077)
* Update to latest Node LTS

* Add sveltekit

* Split tslib into separate @generated and @tslib components

SvelteKit's path aliases don't support multiple locations, so our old
approach of using @tslib to refer to both ts/lib and out/ts/lib will no
longer work. Instead, all generated sources and their includes are
placed in a separate out/ts/generated folder, and imported via @generated
instead. This also allows us to generate .ts files, instead of needing
to output separate .d.ts and .js files.

* Switch package.json to module type

* Avoid usage of baseUrl

Incompatible with SvelteKit

* Move sass into ts; use relative links

SvelteKit's default sass support doesn't allow overriding loadPaths

* jest->vitest, graphs example working with yarn dev

* most pages working in dev mode

* Some fixes after rebasing

* Fix/silence some svelte-check errors

* Get image-occlusion working with Fabric types

* Post-rebase lock changes

* Editor is now checked

* SvelteKit build integrated into ninja

* Use the new SvelteKit entrypoint for pages like congrats/deck options/etc

* Run eslint once for ts/**; fix some tests

* Fix a bunch of issues introduced when rebasing over latest main

* Run eslint fix

* Fix remaining eslint+pylint issues; tests now all pass

* Fix some issues with a clean build

* Latest bufbuild no longer requires @__PURE__ hack

* Add a few missed dependencies

* Add yarn.bat to fix Windows build

* Fix pages failing to show when ANKI_API_PORT not defined

* Fix svelte-check and vitest on Windows

* Set node path in ./yarn

* Move svelte-kit output to ts/.svelte-kit

Sadly, I couldn't figure out a way to store it in out/ if out/ is
a symlink, as it breaks module resolution when SvelteKit is run.

* Allow HMR inside Anki

* Skip SvelteKit build when HMR is defined

* Fix some post-rebase issues

I should have done a normal merge instead.
2024-03-31 09:16:31 +01:00

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 "@tslib/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 [];
}
}