web-highlighter
Version:
✨A no-runtime dependency lib for text highlighting & persistence on any website ✨🖍️
95 lines (76 loc) • 2.56 kB
text/typescript
/**
* some dom operations about HighlightRange
*/
import type { DomMeta, DomNode } from '@src/types';
import { CAMEL_DATASET_IDENTIFIER, ROOT_IDX, UNKNOWN_IDX } from '@src/util/const';
const countGlobalNodeIndex = ($node: Node, $root: Document | HTMLElement): number => {
const tagName = ($node as HTMLElement).tagName;
const $list = $root.getElementsByTagName(tagName);
for (let i = 0; i < $list.length; i++) {
if ($node === $list[i]) {
return i;
}
}
return UNKNOWN_IDX;
};
/**
* text total length in all predecessors (text nodes) in the root node
* (without offset in current node)
*/
const getTextPreOffset = ($root: Node, $text: Node): number => {
const nodeStack: Node[] = [$root];
let $curNode: Node = null;
let offset = 0;
while (($curNode = nodeStack.pop())) {
const children = $curNode.childNodes;
for (let i = children.length - 1; i >= 0; i--) {
nodeStack.push(children[i]);
}
if ($curNode.nodeType === 3 && $curNode !== $text) {
offset += $curNode.textContent.length;
} else if ($curNode.nodeType === 3) {
break;
}
}
return offset;
};
/**
* find the original dom parent node (none highlight dom)
*/
const getOriginParent = ($node: HTMLElement | Text): HTMLElement => {
if ($node instanceof HTMLElement && (!$node.dataset || !$node.dataset[CAMEL_DATASET_IDENTIFIER])) {
return $node;
}
let $originParent = $node.parentNode as HTMLElement;
while ($originParent?.dataset[CAMEL_DATASET_IDENTIFIER]) {
$originParent = $originParent.parentNode as HTMLElement;
}
return $originParent;
};
export const getDomMeta = ($node: HTMLElement | Text, offset: number, $root: Document | HTMLElement): DomMeta => {
const $originParent = getOriginParent($node);
const index = $originParent === $root ? ROOT_IDX : countGlobalNodeIndex($originParent, $root);
const preNodeOffset = getTextPreOffset($originParent, $node);
const tagName = $originParent.tagName;
return {
parentTagName: tagName,
parentIndex: index,
textOffset: preNodeOffset + offset,
};
};
export const formatDomNode = (n: DomNode): DomNode => {
if (
// Text
n.$node.nodeType === 3 ||
// CDATASection
n.$node.nodeType === 4 ||
// Comment
n.$node.nodeType === 8
) {
return n;
}
return {
$node: n.$node.childNodes[n.offset],
offset: 0,
};
};