pangu
Version:
Opinionated paranoid text spacing: automatically inserts whitespace between CJK (Chinese, Japanese, Korean) and ANS (alphabetical letters, numerical digits and symbols)
64 lines (63 loc) • 2.77 kB
JavaScript
//#region src/browser/dom/dom-walker.ts
var DomWalker = class {
static ignoredClass = "no-pangu-spacing";
static ignoredTags = /^(code|pre|script|style|textarea|iframe|input)$/i;
static blockTags = /^(div|p|h1|h2|h3|h4|h5|h6)$/i;
static spaceLikeTags = /^(br|hr|i|img|pangu)$/i;
static spaceSensitiveTags = /^(a|del|i|pre|s|strike|u)$/i;
static collectTextNodes(root, reverse = false) {
const nodes = [];
if (!root || root instanceof DocumentFragment) return nodes;
const walker = this.createTextWalker(root);
while (walker.nextNode()) nodes.push(walker.currentNode);
return reverse ? nodes.reverse() : nodes;
}
static findAdjacentTextNode(textNode, direction) {
let root = textNode;
while (root.parentNode && root !== document.body && !this.blockTags.test(root.nodeName)) root = root.parentNode;
const walker = this.createTextWalker(root);
walker.currentNode = textNode;
return direction === "previous" ? walker.previousNode() : walker.nextNode();
}
static findBoundaryNode(textNode, edge) {
let node = textNode;
while (node.parentNode && !this.spaceSensitiveTags.test(node.nodeName) && (edge === "first" ? this.isFirstTextChild(node.parentNode, node) : this.isLastTextChild(node.parentNode, node))) node = node.parentNode;
return node;
}
static isFirstTextChild(parentNode, targetNode) {
const { childNodes } = parentNode;
for (const childNode of childNodes) if (childNode.nodeType !== Node.COMMENT_NODE && childNode.textContent) return childNode === targetNode;
return false;
}
static isLastTextChild(parentNode, targetNode) {
const { childNodes } = parentNode;
for (let i = childNodes.length - 1; i > -1; i--) {
const childNode = childNodes[i];
if (childNode.nodeType !== Node.COMMENT_NODE && childNode.textContent) return childNode === targetNode;
}
return false;
}
static createTextWalker(root) {
return document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
if (!node.nodeValue || !/\S/.test(node.nodeValue)) return NodeFilter.FILTER_REJECT;
return this.isIgnoredNode(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
} });
}
static isIgnoredNode(node) {
let currentNode = node;
while (currentNode) {
if (currentNode instanceof Element && this.isIgnoredElement(currentNode)) return true;
currentNode = currentNode.parentNode;
}
return false;
}
static isIgnoredElement(element) {
return this.ignoredTags.test(element.nodeName) || this.isContentEditable(element) || element.classList.contains(this.ignoredClass);
}
static isContentEditable(node) {
return node instanceof HTMLElement && (node.isContentEditable || node.getAttribute("g_editable") === "true");
}
};
//#endregion
export { DomWalker };
//# sourceMappingURL=dom-walker.js.map