@quanta-intellect/vessel-browser 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/out/main/index.js +1273 -458
- package/out/preload/content-script.js +76 -8
- package/out/renderer/assets/{index-CCVxW0YM.js → index-BUYEjb3N.js} +245 -101
- package/out/renderer/assets/{index-Cud0VqFQ.css → index-DOCQcMR5.css} +172 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
|
@@ -2126,7 +2126,34 @@ function generateStableSelector(el) {
|
|
|
2126
2126
|
let elementIndex = 0;
|
|
2127
2127
|
const elementSelectors = {};
|
|
2128
2128
|
let indexedElements = /* @__PURE__ */ new WeakMap();
|
|
2129
|
+
const indexedElementRefs = {};
|
|
2129
2130
|
let activeOverlays = [];
|
|
2131
|
+
function deepQuerySelectorAll(selector, root = document) {
|
|
2132
|
+
const results = [];
|
|
2133
|
+
root.querySelectorAll(selector).forEach((el) => results.push(el));
|
|
2134
|
+
const MAX_SHADOW_HOSTS = 50;
|
|
2135
|
+
const MAX_DEPTH = 3;
|
|
2136
|
+
const shadowRoots = [];
|
|
2137
|
+
const findShadowRoots = (node, depth) => {
|
|
2138
|
+
if (depth > MAX_DEPTH || shadowRoots.length >= MAX_SHADOW_HOSTS) return;
|
|
2139
|
+
const children = node.children;
|
|
2140
|
+
for (let i = 0; i < children.length && shadowRoots.length < MAX_SHADOW_HOSTS; i++) {
|
|
2141
|
+
const el = children[i];
|
|
2142
|
+
if (el.shadowRoot) {
|
|
2143
|
+
shadowRoots.push(el.shadowRoot);
|
|
2144
|
+
findShadowRoots(el.shadowRoot, depth + 1);
|
|
2145
|
+
}
|
|
2146
|
+
if (el.children.length > 0 && el.children.length < 200) {
|
|
2147
|
+
findShadowRoots(el, depth);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
};
|
|
2151
|
+
findShadowRoots(root, 0);
|
|
2152
|
+
for (const sr of shadowRoots) {
|
|
2153
|
+
sr.querySelectorAll(selector).forEach((el) => results.push(el));
|
|
2154
|
+
}
|
|
2155
|
+
return results;
|
|
2156
|
+
}
|
|
2130
2157
|
function generateSelector(el) {
|
|
2131
2158
|
return generateStableSelector(el);
|
|
2132
2159
|
}
|
|
@@ -2135,6 +2162,7 @@ function assignIndex(el) {
|
|
|
2135
2162
|
if (existing != null) return existing;
|
|
2136
2163
|
elementIndex += 1;
|
|
2137
2164
|
elementSelectors[elementIndex] = generateSelector(el);
|
|
2165
|
+
indexedElementRefs[elementIndex] = el;
|
|
2138
2166
|
indexedElements.set(el, elementIndex);
|
|
2139
2167
|
return elementIndex;
|
|
2140
2168
|
}
|
|
@@ -2475,7 +2503,7 @@ function buildBaseMetadata(el) {
|
|
|
2475
2503
|
};
|
|
2476
2504
|
}
|
|
2477
2505
|
function extractHeadings() {
|
|
2478
|
-
return
|
|
2506
|
+
return deepQuerySelectorAll("h1, h2, h3, h4, h5, h6").map((el) => {
|
|
2479
2507
|
const text = el.textContent?.trim() || "";
|
|
2480
2508
|
if (!text) return null;
|
|
2481
2509
|
return {
|
|
@@ -2486,10 +2514,10 @@ function extractHeadings() {
|
|
|
2486
2514
|
}
|
|
2487
2515
|
function extractNavigation() {
|
|
2488
2516
|
const navigation = [];
|
|
2489
|
-
|
|
2517
|
+
deepQuerySelectorAll(
|
|
2490
2518
|
'nav, [role="navigation"], header nav, [role="banner"] nav'
|
|
2491
2519
|
).forEach((nav) => {
|
|
2492
|
-
|
|
2520
|
+
deepQuerySelectorAll("a[href]", nav).forEach((link) => {
|
|
2493
2521
|
const anchor = link;
|
|
2494
2522
|
const text = anchor.textContent?.trim();
|
|
2495
2523
|
if (!text || anchor.getAttribute("href")?.startsWith("#")) return;
|
|
@@ -2534,7 +2562,7 @@ function getFieldMetadata(el) {
|
|
|
2534
2562
|
}
|
|
2535
2563
|
function extractInteractiveElements() {
|
|
2536
2564
|
const elements = [];
|
|
2537
|
-
|
|
2565
|
+
deepQuerySelectorAll(
|
|
2538
2566
|
'button, [role="button"], input[type="submit"], input[type="button"]'
|
|
2539
2567
|
).forEach((btn) => {
|
|
2540
2568
|
const input = btn;
|
|
@@ -2545,7 +2573,7 @@ function extractInteractiveElements() {
|
|
|
2545
2573
|
...buildBaseMetadata(btn)
|
|
2546
2574
|
});
|
|
2547
2575
|
});
|
|
2548
|
-
|
|
2576
|
+
deepQuerySelectorAll("a[href]").forEach((link) => {
|
|
2549
2577
|
const anchor = link;
|
|
2550
2578
|
const text = anchor.textContent?.trim();
|
|
2551
2579
|
if (!text || anchor.getAttribute("href")?.startsWith("#")) return;
|
|
@@ -2559,7 +2587,7 @@ function extractInteractiveElements() {
|
|
|
2559
2587
|
context
|
|
2560
2588
|
});
|
|
2561
2589
|
});
|
|
2562
|
-
|
|
2590
|
+
deepQuerySelectorAll(
|
|
2563
2591
|
'input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea'
|
|
2564
2592
|
).forEach((input) => {
|
|
2565
2593
|
const element = input;
|
|
@@ -2587,7 +2615,8 @@ function extractForms() {
|
|
|
2587
2615
|
}
|
|
2588
2616
|
return el instanceof HTMLInputElement && (el.type === "submit" || el.type === "image") && el.form === form;
|
|
2589
2617
|
}
|
|
2590
|
-
|
|
2618
|
+
deepQuerySelectorAll("form").forEach((formEl) => {
|
|
2619
|
+
const form = formEl;
|
|
2591
2620
|
const fields = [];
|
|
2592
2621
|
form.querySelectorAll(
|
|
2593
2622
|
"input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='image']), select, textarea"
|
|
@@ -2784,9 +2813,12 @@ function getVisiblePageText() {
|
|
|
2784
2813
|
function vesselExtractContent() {
|
|
2785
2814
|
const extractStructuredContent = (article) => {
|
|
2786
2815
|
activeOverlays = detectOverlays();
|
|
2816
|
+
const readabilityText = article?.textContent || "";
|
|
2817
|
+
const visibleText = getVisiblePageText();
|
|
2818
|
+
const content = readabilityText.length > visibleText.length * 0.3 ? readabilityText : visibleText;
|
|
2787
2819
|
return {
|
|
2788
2820
|
title: article?.title || document.title,
|
|
2789
|
-
content
|
|
2821
|
+
content,
|
|
2790
2822
|
htmlContent: article?.content || "",
|
|
2791
2823
|
byline: article?.byline || "",
|
|
2792
2824
|
excerpt: article?.excerpt || "",
|
|
@@ -2813,6 +2845,9 @@ function vesselExtractContent() {
|
|
|
2813
2845
|
Object.keys(elementSelectors).forEach(
|
|
2814
2846
|
(key) => delete elementSelectors[key]
|
|
2815
2847
|
);
|
|
2848
|
+
Object.keys(indexedElementRefs).forEach(
|
|
2849
|
+
(key) => delete indexedElementRefs[key]
|
|
2850
|
+
);
|
|
2816
2851
|
const documentClone = document.cloneNode(true);
|
|
2817
2852
|
const reader = new readabilityExports.Readability(documentClone);
|
|
2818
2853
|
const article = reader.parse();
|
|
@@ -2825,9 +2860,42 @@ function vesselExtractContent() {
|
|
|
2825
2860
|
function resolveElementSelector(index) {
|
|
2826
2861
|
return elementSelectors[index] || null;
|
|
2827
2862
|
}
|
|
2863
|
+
function interactByIndex(index, action, value) {
|
|
2864
|
+
const el = indexedElementRefs[index];
|
|
2865
|
+
if (!el || !(el instanceof HTMLElement)) {
|
|
2866
|
+
return "Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.";
|
|
2867
|
+
}
|
|
2868
|
+
if (action === "click") {
|
|
2869
|
+
el.focus();
|
|
2870
|
+
el.click();
|
|
2871
|
+
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
2872
|
+
}
|
|
2873
|
+
if (action === "focus") {
|
|
2874
|
+
el.focus();
|
|
2875
|
+
return "Focused: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
2876
|
+
}
|
|
2877
|
+
if (action === "value" && value != null) {
|
|
2878
|
+
if (!(el instanceof HTMLInputElement) && !(el instanceof HTMLTextAreaElement) && !(el instanceof HTMLSelectElement)) {
|
|
2879
|
+
return "Error[not-input]: Element is not a text input";
|
|
2880
|
+
}
|
|
2881
|
+
const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;
|
|
2882
|
+
const desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
2883
|
+
if (desc?.set) {
|
|
2884
|
+
desc.set.call(el, value);
|
|
2885
|
+
} else {
|
|
2886
|
+
el.value = value;
|
|
2887
|
+
}
|
|
2888
|
+
el.focus();
|
|
2889
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2890
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2891
|
+
return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
|
|
2892
|
+
}
|
|
2893
|
+
return "Error: Unknown action";
|
|
2894
|
+
}
|
|
2828
2895
|
electron.contextBridge.exposeInMainWorld("__vessel", {
|
|
2829
2896
|
extractContent: vesselExtractContent,
|
|
2830
2897
|
getElementSelector: resolveElementSelector,
|
|
2898
|
+
interactByIndex,
|
|
2831
2899
|
notifyHighlightSelection: (text) => {
|
|
2832
2900
|
if (typeof text === "string" && text.trim()) {
|
|
2833
2901
|
electron.ipcRenderer.send("vessel:highlight-selection", text.trim());
|