anki/ts/domlib/surround/normalize-insertion-ranges.ts
Henrik Giesel 30bbbaf00b
Use eslint for sorting our imports (#1637)
* Make eslint sort our imports

* fix missing deps in eslint rule (dae)

Caught on Linux due to the stricter sandboxing

* Remove exports-last eslint rule (for now?)

* Adjust browserslist settings

- We use ResizeObserver which is not supported in browsers like KaiOS,
  Baidu or Android UC

* Raise minimum iOS version 13.4

- It's the first version that supports ResizeObserver

* Apply new eslint rules to sort imports
2022-02-04 18:36:34 +10:00

183 lines
5.4 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { ChildNodeRange } from "./child-node-range";
import { findAfter, findBefore } from "./find-adjacent";
import { findWithin, findWithinNode } from "./find-within";
import type {
ElementClearer,
ElementMatcher,
FoundAdjacent,
FoundMatch,
} from "./matcher";
import { MatchResult } from "./matcher";
function countChildNodesRespectiveToParent(parent: Node, element: Element): number {
return element.parentNode === parent ? element.childNodes.length : 1;
}
interface NormalizationResult {
normalizedRanges: ChildNodeRange[];
removedNodes: Element[];
}
function normalizeWithinInner(
node: Element,
parent: Node,
removedNodes: Element[],
matcher: ElementMatcher,
clearer: ElementClearer,
) {
const matches = findWithinNode(node, matcher);
const processFoundMatches = ({ element, matchType }: FoundMatch) =>
matchType === MatchResult.MATCH ?? clearer(element);
for (const { element: found } of matches.filter(processFoundMatches)) {
removedNodes.push(found);
found.replaceWith(...found.childNodes);
}
/**
* Normalization here is vital so that the
* original range can selected afterwards
*/
node.normalize();
return countChildNodesRespectiveToParent(parent, node);
}
function normalizeAdjacent(
matches: FoundAdjacent[],
parent: Node,
removedNodes: Element[],
matcher: ElementMatcher,
clearer: ElementClearer,
): number {
let childCount = 0;
let keepChildCount = 0;
for (const { element, matchType } of matches) {
switch (matchType) {
case MatchResult.MATCH:
childCount += normalizeWithinInner(
element as Element,
parent,
removedNodes,
matcher,
clearer,
);
removedNodes.push(element as Element);
element.replaceWith(...element.childNodes);
break;
case MatchResult.KEEP:
keepChildCount = normalizeWithinInner(
element as Element,
parent,
removedNodes,
matcher,
clearer,
);
if (clearer(element as Element)) {
removedNodes.push(element as Element);
element.replaceWith(...element.childNodes);
childCount += keepChildCount;
} else {
childCount++;
}
break;
case MatchResult.ALONG:
childCount++;
break;
}
}
return childCount;
}
function normalizeWithin(
matches: FoundMatch[],
parent: Node,
removedNodes: Element[],
clearer: ElementClearer,
): number {
let childCount = 0;
for (const { matchType, element } of matches) {
if (matchType === MatchResult.MATCH) {
removedNodes.push(element);
childCount += countChildNodesRespectiveToParent(parent, element);
element.replaceWith(...element.childNodes);
} /* matchType === MatchResult.KEEP */ else {
if (clearer(element)) {
removedNodes.push(element);
childCount += countChildNodesRespectiveToParent(parent, element);
element.replaceWith(...element.childNodes);
} else {
childCount += 1;
}
}
}
const shift = childCount - matches.length;
return shift;
}
export function normalizeInsertionRanges(
insertionRanges: ChildNodeRange[],
matcher: ElementMatcher,
clearer: ElementClearer,
): NormalizationResult {
const removedNodes: Element[] = [];
const normalizedRanges: ChildNodeRange[] = [];
for (const [index, range] of insertionRanges.entries()) {
const normalizedRange = { ...range };
const parent = normalizedRange.parent;
/**
* This deals with the unnormalized state that would exist
* after surrounding and finds conflicting elements, for example cases like:
* `<b>single<b>double</b>single</b>` or `<i><b>before</b></i><b>after</b>`
*/
if (index === 0) {
const matches = findBefore(normalizedRange, matcher);
const count = normalizeAdjacent(
matches,
parent,
removedNodes,
matcher,
clearer,
);
normalizedRange.startIndex -= matches.length;
normalizedRange.endIndex += count - matches.length;
}
const matches = findWithin(normalizedRange, matcher);
const withinShift = normalizeWithin(matches, parent, removedNodes, clearer);
normalizedRange.endIndex += withinShift;
if (index === insertionRanges.length - 1) {
const matches = findAfter(normalizedRange, matcher);
const count = normalizeAdjacent(
matches,
parent,
removedNodes,
matcher,
clearer,
);
normalizedRange.endIndex += count;
}
normalizedRanges.push(normalizedRange);
}
return {
normalizedRanges,
removedNodes,
};
}