]> BookStack Code Mirror - bookstack/blobdiff - resources/js/services/dom.ts
ZIP Imports: Added API examples, finished testing
[bookstack] / resources / js / services / dom.ts
index 537af816a907ad6e7a98ed4c1643fdc91ce40f8f..8696fe81639c84aea40294dc9b3c1db376ca129f 100644 (file)
@@ -1,3 +1,5 @@
+import {cyrb53} from "./util";
+
 /**
  * Check if the given param is a HTMLElement
  */
@@ -181,6 +183,9 @@ export function htmlToDom(html: string): HTMLElement {
     return firstChild;
 }
 
+/**
+ * For the given node and offset, return an adjusted offset that's relative to the given parent element.
+ */
 export function normalizeNodeTextOffsetToParent(node: Node, offset: number, parentElement: HTMLElement): number {
     if (!parentElement.contains(node)) {
         throw new Error('ParentElement must be a prent of element');
@@ -201,3 +206,72 @@ export function normalizeNodeTextOffsetToParent(node: Node, offset: number, pare
 
     return normalizedOffset;
 }
+
+/**
+ * Find the target child node and adjusted offset based on a parent node and text offset.
+ * Returns null if offset not found within the given parent node.
+ */
+export function findTargetNodeAndOffset(parentNode: HTMLElement, offset: number): ({node: Node, offset: number}|null) {
+    if (offset === 0) {
+        return { node: parentNode, offset: 0 };
+    }
+
+    let currentOffset = 0;
+    let currentNode = null;
+
+    for (let i = 0; i < parentNode.childNodes.length; i++) {
+        currentNode = parentNode.childNodes[i];
+
+        if (currentNode.nodeType === Node.TEXT_NODE) {
+            // For text nodes, count the length of their content
+            // Returns if within range
+            const textLength = (currentNode.textContent || '').length;
+            if (currentOffset + textLength >= offset) {
+                return {
+                    node: currentNode,
+                    offset: offset - currentOffset
+                };
+            }
+
+            currentOffset += textLength;
+        } else if (currentNode.nodeType === Node.ELEMENT_NODE) {
+            // Otherwise, if an element, track the text length and search within
+            // if in range for the target offset
+            const elementTextLength = (currentNode.textContent || '').length;
+            if (currentOffset + elementTextLength >= offset) {
+                return findTargetNodeAndOffset(currentNode as HTMLElement, offset - currentOffset);
+            }
+
+            currentOffset += elementTextLength;
+        }
+    }
+
+    // Return null if not found within range
+    return null;
+}
+
+/**
+ * Create a hash for the given HTML element content.
+ */
+export function hashElement(element: HTMLElement): string {
+    const normalisedElemText = (element.textContent || '').replace(/\s{2,}/g, '');
+    return cyrb53(normalisedElemText);
+}
+
+/**
+ * Find the closest scroll container parent for the given element
+ * otherwise will default to the body element.
+ */
+export function findClosestScrollContainer(start: HTMLElement): HTMLElement {
+    let el: HTMLElement|null = start;
+    do {
+        const computed = window.getComputedStyle(el);
+        if (computed.overflowY === 'scroll') {
+            return el;
+        }
+
+        el = el.parentElement;
+    } while (el);
+
+    return document.body;
+}
\ No newline at end of file