You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@annotator.apache.org by ge...@apache.org on 2020/02/13 15:47:23 UTC
[incubator-annotator] 01/01: Add highlightRange to dom package
This is an automated email from the ASF dual-hosted git repository.
gerben pushed a commit to branch add-highlight-range
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit 18eb1d5043787aa62e91217cc5664e3f7b4029a1
Author: Gerben <ge...@treora.com>
AuthorDate: Thu Feb 13 16:42:32 2020 +0100
Add highlightRange to dom package
Code is copied from https://www.npmjs.com/package/dom-highlight-range v3.0.1
---
packages/dom/src/highlight-range.js | 106 ++++++++++++++++++++++++++++++++++++
packages/dom/src/index.js | 1 +
2 files changed, 107 insertions(+)
diff --git a/packages/dom/src/highlight-range.js b/packages/dom/src/highlight-range.js
new file mode 100644
index 0000000..09e22d5
--- /dev/null
+++ b/packages/dom/src/highlight-range.js
@@ -0,0 +1,106 @@
+// Wrap each text node in a given DOM Range with a <mark> or other element.
+// Breaks start and/or end node if needed.
+// Returns a function that cleans up the created highlight (not a perfect undo: split text nodes are
+// not merged again).
+//
+// Parameters:
+// - range: a DOM Range object. Note that as highlighting modifies the DOM, the range may be
+// unusable afterwards
+// - tagName: the element used to wrap text nodes. Defaults to 'mark'.
+// - attributes: an Object defining any attributes to be set on the wrapper elements.
+export function highlightRange(range, tagName = 'mark', attributes = {}) {
+ if (range.collapsed) return;
+
+ // First put all nodes in an array (splits start and end nodes if needed)
+ const nodes = textNodesInRange(range);
+
+ // Highlight each node
+ const highlightElements = [];
+ for (const node of nodes) {
+ const highlightElement = wrapNodeInHighlight(node, tagName, attributes);
+ highlightElements.push(highlightElement);
+ }
+
+ // Return a function that cleans up the highlightElements.
+ function removeHighlights() {
+ // Remove each of the created highlightElements.
+ for (const highlightIdx in highlightElements) {
+ removeHighlight(highlightElements[highlightIdx]);
+ }
+ }
+ return removeHighlights;
+}
+
+// Return an array of the text nodes in the range. Split the start and end nodes if required.
+function textNodesInRange(range) {
+ // If the start or end node is a text node and only partly in the range, split it.
+ if (range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset > 0) {
+ const endOffset = range.endOffset; // (this may get lost when the splitting the node)
+ const createdNode = range.startContainer.splitText(range.startOffset);
+ if (range.endContainer === range.startContainer) {
+ // If the end was in the same container, it will now be in the newly created node.
+ range.setEnd(createdNode, endOffset - range.startOffset);
+ }
+ range.setStart(createdNode, 0);
+ }
+ if (
+ range.endContainer.nodeType === Node.TEXT_NODE
+ && range.endOffset < range.endContainer.length
+ ) {
+ range.endContainer.splitText(range.endOffset);
+ }
+
+ // Collect the text nodes.
+ const walker = range.startContainer.ownerDocument.createTreeWalker(
+ range.commonAncestorContainer,
+ NodeFilter.SHOW_TEXT,
+ node => range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT,
+ );
+ walker.currentNode = range.startContainer;
+
+ // // Optimise by skipping nodes that are explicitly outside the range.
+ // const NodeTypesWithCharacterOffset = [
+ // Node.TEXT_NODE,
+ // Node.PROCESSING_INSTRUCTION_NODE,
+ // Node.COMMENT_NODE,
+ // ];
+ // if (!NodeTypesWithCharacterOffset.includes(range.startContainer.nodeType)) {
+ // if (range.startOffset < range.startContainer.childNodes.length) {
+ // walker.currentNode = range.startContainer.childNodes[range.startOffset];
+ // } else {
+ // walker.nextSibling(); // TODO verify this is correct.
+ // }
+ // }
+
+ const nodes = [];
+ if (walker.currentNode.nodeType === Node.TEXT_NODE)
+ nodes.push(walker.currentNode);
+ while (walker.nextNode() && range.comparePoint(walker.currentNode, 0) !== 1)
+ nodes.push(walker.currentNode);
+ return nodes;
+}
+
+// Replace [node] with <tagName ...attributes>[node]</tagName>
+function wrapNodeInHighlight(node, tagName, attributes) {
+ const highlightElement = node.ownerDocument.createElement(tagName);
+ Object.keys(attributes).forEach(key => {
+ highlightElement.setAttribute(key, attributes[key]);
+ });
+ const tempRange = node.ownerDocument.createRange();
+ tempRange.selectNode(node);
+ tempRange.surroundContents(highlightElement);
+ return highlightElement;
+}
+
+// Remove a highlight element created with wrapNodeInHighlight.
+function removeHighlight(highlightElement) {
+ if (highlightElement.childNodes.length === 1) {
+ highlightElement.parentNode.replaceChild(highlightElement.firstChild, highlightElement);
+ } else {
+ // If the highlight somehow contains multiple nodes now, move them all.
+ while (highlightElement.firstChild) {
+ highlightElement.parentNode.insertBefore(highlightElement.firstChild, highlightElement);
+ }
+ highlightElement.remove();
+ }
+}
diff --git a/packages/dom/src/index.js b/packages/dom/src/index.js
index e54a806..3d7ca58 100644
--- a/packages/dom/src/index.js
+++ b/packages/dom/src/index.js
@@ -21,3 +21,4 @@
export * from './css';
export * from './range';
export * from './text-quote';
+export * from './highlight-range';