Skip to content

Commit

Permalink
feat: parse dom queryPath
Browse files Browse the repository at this point in the history
  • Loading branch information
betterRunner committed Sep 20, 2021
1 parent cb64535 commit d04a6f4
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 23 deletions.
41 changes: 32 additions & 9 deletions src/content-scripts/parser/selection-meta.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isObject } from "@/utils/utils";
import { getNodeText } from "@/utils/dom";
import { filterAncestorNodes, getDomQueryPath, getNodeText } from "@/utils/dom";
import { Rect } from "@/types/common";

/**
Expand Down Expand Up @@ -98,14 +98,37 @@ function filterInvalidCoorRects(rects: Rect[]) {
return rects.filter((ele) => !excludeRects.includes(ele));
}

function parseSelectionRects(range: Range) {
let rects = Array.from(range.getClientRects()).map((r) => ({
x: r.x,
y: r.y + window.scrollY,
width: r.width,
height: r.height,
}));
rects = filterDuplicateRects(rects);
rects = filterInvalidCoorRects(rects);
return rects;
}

function parseSelectedQueryPaths(selection: Selection, range: Range) {
const allWithinRangeParentNodes = (range.commonAncestorContainer as HTMLElement)?.getElementsByTagName(
"*"
);
const allSelectedNodes = Array.from(allWithinRangeParentNodes).filter(n => selection.containsNode(n, true));
const textNodes = filterAncestorNodes(allSelectedNodes);
return textNodes.map(n => getDomQueryPath(n as HTMLElement));
}

/**
* Get the `SelectionMeta` from current mouse selection object.
*/
export interface SelectionMeta {
queryPaths: string[];
rects: Rect[];
texts: string[];
}
export function parseRectsAndTextFromSelection(): SelectionMeta {
let queryPaths: string[] = [];
let rects: Rect[] = [];
let texts: string[] = [];
try {
Expand All @@ -114,22 +137,22 @@ export function parseRectsAndTextFromSelection(): SelectionMeta {
if (selection) {
const range = selection.getRangeAt(0);
if (range) {
// 1. texts
const cloneFragment = range.cloneContents();
texts = getNodeTextList(cloneFragment);
rects = Array.from(range.getClientRects()).map((r) => ({
x: r.x,
y: r.y + window.scrollY,
width: r.width,
height: r.height,
}));
rects = filterDuplicateRects(rects);
rects = filterInvalidCoorRects(rects);

// 2. rects
rects = parseSelectionRects(range);

// 3. queryPaths
queryPaths = parseSelectedQueryPaths(selection, range);
}
}
} catch (err) {
console.log(err);
}
return {
queryPaths,
rects,
texts,
};
Expand Down
72 changes: 58 additions & 14 deletions src/utils/dom.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
import { getObjectType } from '../utils/utils'
import { getObjectType } from "../utils/utils";

export const getNodeText = (node: any): string => {
let text = ''
const objType = getObjectType(node)
let text = "";
const objType = getObjectType(node);
switch (objType) {
case '[object HTMLTimeElement]': {
text = node.dateTime || ''
break
case "[object HTMLTimeElement]": {
text = node.dateTime || "";
break;
}
case '[object Text]': {
text = node.textContent || ''
break
case "[object Text]": {
text = node.textContent || "";
break;
}
case '[object HTMLSpanElement]': {
text = node.innerText || ''
break
case "[object HTMLSpanElement]": {
text = node.innerText || "";
break;
}
// TODO: more options
default: {
break
break;
}
}
return text
return text;
};

export function getDomQueryPath(el: HTMLElement) {
if (!el) return '';

const stack = [];
while (el.parentNode != null) {
let sibCount = 0;
let sibIndex = 0;
for (let i = 0; i < el.parentNode.childNodes.length; i++) {
const sib = el.parentNode.childNodes[i];
if (sib.nodeName == el.nodeName) {
if (sib === el) {
sibIndex = sibCount;
}
sibCount++;
}
}
const name = el.nodeName.toLowerCase();
if (el.hasAttribute("id") && el.id != "") {
stack.unshift(`${name}#${el.id}`);
} else if (sibCount > 1) {
stack.unshift(`${name}:n-th-of-type(${sibIndex + 1})`);
} else {
stack.unshift(name);
}
el = el.parentNode as HTMLElement;
}

return stack.slice(1).join(' '); // removes the html element
}

export function filterAncestorNodes(nodes: Node[]) {
const ancestors = new Set();
for (const node of nodes) {
let parent = node.parentNode;
while(parent) {
if (nodes.includes(parent) && node.textContent) {
ancestors.add(parent);
}
parent = parent.parentNode;
}
}
return nodes.filter(n => !ancestors.has(n));
}

0 comments on commit d04a6f4

Please sign in to comment.