Last active
July 20, 2024 14:33
-
-
Save azzgo/e06d18e6a9c6ac71794dfac15d1518e5 to your computer and use it in GitHub Desktop.
一个单文件实现标记和重定位,核心逻辑和思路,完全 copy from alienzhou/web-highlighter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @description minimal mark and relocation utils files | |
* all ideas extract from alienzhou/web-highlighter | |
**/ | |
const ROOT_IDX = -2; | |
const UNKNOWN_IDX = -1; | |
function genDomMeta($node, offset, $root) { | |
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, | |
}; | |
} | |
const formatDomNode = (n) => { | |
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, | |
}; | |
}; | |
const getOriginParent = ($node) => { | |
if ($node instanceof HTMLElement) { | |
return $node; | |
} | |
let $originParent = $node.parentNode; | |
return $originParent; | |
}; | |
const countGlobalNodeIndex = ($node, $root) => { | |
const tagName = $node.tagName; | |
const $list = $root.getElementsByTagName(tagName); | |
for (let i = 0; i < $list.length; i++) { | |
if ($node === $list[i]) { | |
return i; | |
} | |
} | |
return UNKNOWN_IDX; | |
}; | |
const getTextPreOffset = ($root, $text) => { | |
const nodeStack = [$root]; | |
let $curNode = 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; | |
}; | |
// from source | |
const queryElementNode = (startMeta, endMeta, $root) => { | |
const start = | |
startMeta.parentIndex === ROOT_IDX | |
? $root | |
: $root.getElementsByTagName(startMeta.parentTagName)[ | |
startMeta.parentIndex | |
]; | |
const end = | |
endMeta.parentIndex === ROOT_IDX | |
? $root | |
: $root.getElementsByTagName(endMeta.parentTagName)[endMeta.parentIndex]; | |
return { start, end }; | |
}; | |
const getTextChildByOffset = ($parent, offset) => { | |
const nodeStack = [$parent]; | |
let $curNode = null; | |
let curOffset = 0; | |
let startOffset = 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) { | |
startOffset = offset - curOffset; | |
curOffset += $curNode.textContent.length; | |
if (curOffset >= offset) { | |
break; | |
} | |
} | |
} | |
if (!$curNode) { | |
$curNode = $parent; | |
} | |
return { | |
$node: $curNode, | |
offset: startOffset, | |
}; | |
}; | |
// export functions | |
export const getDomMetaFromRange = (range, $root) => { | |
const start = { | |
$node: range.startContainer, | |
offset: range.startOffset, | |
}; | |
const end = { | |
$node: range.endContainer, | |
offset: range.endOffset, | |
}; | |
const startDomNode = formatDomNode(start); | |
const endDomNode = formatDomNode(end); | |
return { | |
startMeta: genDomMeta(startDomNode.$node, startDomNode.offset, $root), | |
endMeta: genDomMeta(endDomNode.$node, endDomNode.offset, $root), | |
}; | |
}; | |
export const getDomNodeFromMeta = (startMeta, endMeta, $root) => { | |
const { start, end } = queryElementNode(startMeta, endMeta, $root); | |
let startInfo = getTextChildByOffset(start, startMeta.textOffset); | |
let endInfo = getTextChildByOffset(end, endMeta.textOffset); | |
return { | |
start: startInfo, | |
end: endInfo, | |
}; | |
}; | |
export const selectAndScrollToView = (start, end) => { | |
const range = new Range(); | |
range.setStart(start.$node, start.offset); | |
range.setEnd(end.$node, end.offset); | |
const span = document.createElement("span"); | |
range.insertNode(span); | |
span.scrollIntoView(); | |
span.remove(); | |
const selection = window.getSelection(); | |
selection.removeAllRanges(); | |
selection.addRange(range); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment