@quanta-intellect/vessel-browser 0.1.71 → 0.1.73
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 +14 -2
- package/out/main/index.js +1376 -60
- package/out/preload/index.js +33 -0
- package/out/renderer/assets/{index-DbnD-fG3.js → index-C2iAShPa.js} +943 -633
- package/out/renderer/assets/{index-wmYomTVM.css → index-MB-_tN9U.css} +142 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -339,6 +339,7 @@ class Tab {
|
|
|
339
339
|
onHighlightSelection;
|
|
340
340
|
onHighlightRemove;
|
|
341
341
|
onHighlightRecolor;
|
|
342
|
+
onSavePage;
|
|
342
343
|
_highlightModeActive = false;
|
|
343
344
|
_readerOriginalUrl = null;
|
|
344
345
|
// Fully custom URL history — we never rely on Chromium's native back/forward
|
|
@@ -383,11 +384,13 @@ class Tab {
|
|
|
383
384
|
this.onHighlightSelection = options?.onHighlightSelection;
|
|
384
385
|
this.onHighlightRemove = options?.onHighlightRemove;
|
|
385
386
|
this.onHighlightRecolor = options?.onHighlightRecolor;
|
|
387
|
+
this.onSavePage = options?.onSavePage;
|
|
386
388
|
const webPreferences = {
|
|
387
389
|
preload: path.join(__dirname, "../preload/content-script.js"),
|
|
388
390
|
sandbox: true,
|
|
389
391
|
contextIsolation: true,
|
|
390
|
-
nodeIntegration: false
|
|
392
|
+
nodeIntegration: false,
|
|
393
|
+
spellcheck: false
|
|
391
394
|
};
|
|
392
395
|
if (options?.sessionPartition) {
|
|
393
396
|
webPreferences.session = electron.session.fromPartition(options.sessionPartition);
|
|
@@ -404,6 +407,9 @@ class Tab {
|
|
|
404
407
|
canGoForward: false,
|
|
405
408
|
isReaderMode: false,
|
|
406
409
|
adBlockingEnabled: options?.adBlockingEnabled ?? true,
|
|
410
|
+
isPinned: false,
|
|
411
|
+
isAudible: false,
|
|
412
|
+
isMuted: false,
|
|
407
413
|
role: options?.role
|
|
408
414
|
};
|
|
409
415
|
this.view.webContents.on("before-input-event", (event, input) => {
|
|
@@ -530,6 +536,19 @@ class Tab {
|
|
|
530
536
|
this._state.favicon = favicons[0] || "";
|
|
531
537
|
this.onChange();
|
|
532
538
|
});
|
|
539
|
+
wc.on("media-started-playing", () => {
|
|
540
|
+
this._state.isAudible = true;
|
|
541
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
542
|
+
this.onChange();
|
|
543
|
+
});
|
|
544
|
+
wc.on("media-paused", () => {
|
|
545
|
+
setTimeout(() => {
|
|
546
|
+
if (wc.isDestroyed()) return;
|
|
547
|
+
this._state.isAudible = wc.isCurrentlyAudible();
|
|
548
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
549
|
+
this.onChange();
|
|
550
|
+
}, 250);
|
|
551
|
+
});
|
|
533
552
|
wc.on("context-menu", (_event, params) => {
|
|
534
553
|
const x = params.x;
|
|
535
554
|
const y = params.y;
|
|
@@ -621,6 +640,19 @@ class Tab {
|
|
|
621
640
|
})
|
|
622
641
|
);
|
|
623
642
|
}
|
|
643
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
644
|
+
menu.append(
|
|
645
|
+
new electron.MenuItem({
|
|
646
|
+
label: "Save Page As...",
|
|
647
|
+
click: () => this.onSavePage?.()
|
|
648
|
+
})
|
|
649
|
+
);
|
|
650
|
+
menu.append(
|
|
651
|
+
new electron.MenuItem({
|
|
652
|
+
label: "View Page Source",
|
|
653
|
+
click: () => void this.viewSource()
|
|
654
|
+
})
|
|
655
|
+
);
|
|
624
656
|
menu.popup({ window: this.parentWindow });
|
|
625
657
|
}
|
|
626
658
|
get state() {
|
|
@@ -706,12 +738,61 @@ class Tab {
|
|
|
706
738
|
zoomReset() {
|
|
707
739
|
this.view.webContents.setZoomLevel(0);
|
|
708
740
|
}
|
|
741
|
+
async viewSource() {
|
|
742
|
+
const url = this.view.webContents.getURL();
|
|
743
|
+
let html;
|
|
744
|
+
try {
|
|
745
|
+
html = await this.view.webContents.executeJavaScript(
|
|
746
|
+
"document.documentElement.outerHTML"
|
|
747
|
+
);
|
|
748
|
+
} catch (err) {
|
|
749
|
+
logger$j.warn("Failed to retrieve page source:", err);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const escaped = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
753
|
+
const win = new electron.BrowserWindow({
|
|
754
|
+
width: 960,
|
|
755
|
+
height: 700,
|
|
756
|
+
title: `view-source:${url}`,
|
|
757
|
+
backgroundColor: "#1a1a1e",
|
|
758
|
+
webPreferences: {
|
|
759
|
+
sandbox: true,
|
|
760
|
+
contextIsolation: true,
|
|
761
|
+
nodeIntegration: false,
|
|
762
|
+
spellcheck: false
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
const style = "background:#1a1a1e;color:#e0e0e0;font-family:monospace;font-size:12px;line-height:1.5;padding:16px;margin:0;white-space:pre-wrap;word-break:break-all;";
|
|
766
|
+
const dataUrl = `data:text/html;charset=utf-8,<!DOCTYPE html><html><head><title>view-source:${url}</title></head><body style="${style}"><pre>${escaped}</pre></body></html>`;
|
|
767
|
+
win.loadURL(dataUrl);
|
|
768
|
+
}
|
|
709
769
|
setAdBlockingEnabled(enabled) {
|
|
710
770
|
if (this._state.adBlockingEnabled === enabled) return false;
|
|
711
771
|
this._state.adBlockingEnabled = enabled;
|
|
712
772
|
this.onChange();
|
|
713
773
|
return true;
|
|
714
774
|
}
|
|
775
|
+
setPinned(pinned) {
|
|
776
|
+
if (this._state.isPinned === pinned) return;
|
|
777
|
+
this._state.isPinned = pinned;
|
|
778
|
+
this.onChange();
|
|
779
|
+
}
|
|
780
|
+
setGroup(groupId) {
|
|
781
|
+
if (this._state.groupId === groupId) return;
|
|
782
|
+
this._state.groupId = groupId;
|
|
783
|
+
this.onChange();
|
|
784
|
+
}
|
|
785
|
+
setMuted(muted) {
|
|
786
|
+
const wc = this.view.webContents;
|
|
787
|
+
wc.setAudioMuted(muted);
|
|
788
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
789
|
+
this._state.isAudible = wc.isCurrentlyAudible();
|
|
790
|
+
this.onChange();
|
|
791
|
+
}
|
|
792
|
+
toggleMuted() {
|
|
793
|
+
this.setMuted(!this._state.isMuted);
|
|
794
|
+
return this._state.isMuted;
|
|
795
|
+
}
|
|
715
796
|
get highlightModeActive() {
|
|
716
797
|
return this._highlightModeActive;
|
|
717
798
|
}
|
|
@@ -813,6 +894,32 @@ class Tab {
|
|
|
813
894
|
this.view.webContents.close();
|
|
814
895
|
}
|
|
815
896
|
}
|
|
897
|
+
const TAB_GROUP_COLORS = [
|
|
898
|
+
"blue",
|
|
899
|
+
"green",
|
|
900
|
+
"yellow",
|
|
901
|
+
"orange",
|
|
902
|
+
"red",
|
|
903
|
+
"purple",
|
|
904
|
+
"gray"
|
|
905
|
+
];
|
|
906
|
+
const TAB_GROUP_COLOR_LABELS = {
|
|
907
|
+
blue: "Blue",
|
|
908
|
+
green: "Green",
|
|
909
|
+
yellow: "Yellow",
|
|
910
|
+
orange: "Orange",
|
|
911
|
+
red: "Red",
|
|
912
|
+
purple: "Purple",
|
|
913
|
+
gray: "Gray"
|
|
914
|
+
};
|
|
915
|
+
const SEARCH_ENGINE_PRESETS = {
|
|
916
|
+
duckduckgo: { label: "DuckDuckGo", url: "https://duckduckgo.com/?q=" },
|
|
917
|
+
google: { label: "Google", url: "https://www.google.com/search?q=" },
|
|
918
|
+
bing: { label: "Bing", url: "https://www.bing.com/search?q=" },
|
|
919
|
+
brave: { label: "Brave Search", url: "https://search.brave.com/search?q=" },
|
|
920
|
+
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
921
|
+
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
922
|
+
};
|
|
816
923
|
const logger$i = createLogger("JsonPersistence");
|
|
817
924
|
function canUseSafeStorage() {
|
|
818
925
|
try {
|
|
@@ -906,6 +1013,20 @@ const SAVE_DEBOUNCE_MS$5 = 250;
|
|
|
906
1013
|
function getHighlightsPath() {
|
|
907
1014
|
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
908
1015
|
}
|
|
1016
|
+
function createPersistence$1() {
|
|
1017
|
+
return createDebouncedJsonPersistence({
|
|
1018
|
+
debounceMs: SAVE_DEBOUNCE_MS$5,
|
|
1019
|
+
filePath: getHighlightsPath(),
|
|
1020
|
+
getValue: () => state$4,
|
|
1021
|
+
logLabel: "highlights",
|
|
1022
|
+
resetOnSchedule: true
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
let persistence$5 = null;
|
|
1026
|
+
function getPersistence$1() {
|
|
1027
|
+
persistence$5 ??= createPersistence$1();
|
|
1028
|
+
return persistence$5;
|
|
1029
|
+
}
|
|
909
1030
|
function load$4() {
|
|
910
1031
|
if (state$4) return state$4;
|
|
911
1032
|
state$4 = loadJsonFile({
|
|
@@ -920,15 +1041,8 @@ function load$4() {
|
|
|
920
1041
|
});
|
|
921
1042
|
return state$4;
|
|
922
1043
|
}
|
|
923
|
-
const persistence$5 = createDebouncedJsonPersistence({
|
|
924
|
-
debounceMs: SAVE_DEBOUNCE_MS$5,
|
|
925
|
-
filePath: getHighlightsPath(),
|
|
926
|
-
getValue: () => state$4,
|
|
927
|
-
logLabel: "highlights",
|
|
928
|
-
resetOnSchedule: true
|
|
929
|
-
});
|
|
930
1044
|
function save$2() {
|
|
931
|
-
|
|
1045
|
+
getPersistence$1().schedule();
|
|
932
1046
|
}
|
|
933
1047
|
function emit$2() {
|
|
934
1048
|
if (!state$4) return;
|
|
@@ -1010,7 +1124,7 @@ function clearHighlightsForUrl(url) {
|
|
|
1010
1124
|
return removed;
|
|
1011
1125
|
}
|
|
1012
1126
|
function flushPersist$4() {
|
|
1013
|
-
return
|
|
1127
|
+
return getPersistence$1().flush();
|
|
1014
1128
|
}
|
|
1015
1129
|
const SKIP_TAGS_JS = "var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};";
|
|
1016
1130
|
const CONTENT_ROOTS_JS = `
|
|
@@ -2367,9 +2481,22 @@ function destroySession(tabId) {
|
|
|
2367
2481
|
}
|
|
2368
2482
|
}
|
|
2369
2483
|
const logger$h = createLogger("TabManager");
|
|
2484
|
+
function sanitizePdfFilename(title) {
|
|
2485
|
+
const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2486
|
+
const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
|
|
2487
|
+
return `${base}.pdf`;
|
|
2488
|
+
}
|
|
2489
|
+
function sanitizePageFilename(title, ext) {
|
|
2490
|
+
const clean = title.replace(/[<>:\"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2491
|
+
const escapedExt = ext.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2492
|
+
const regex = new RegExp(`\\.${escapedExt}$`, "i");
|
|
2493
|
+
const base = (clean || "Vessel Page").replace(regex, "");
|
|
2494
|
+
return `${base}.${ext}`;
|
|
2495
|
+
}
|
|
2370
2496
|
class TabManager {
|
|
2371
2497
|
tabs = /* @__PURE__ */ new Map();
|
|
2372
2498
|
order = [];
|
|
2499
|
+
tabGroups = /* @__PURE__ */ new Map();
|
|
2373
2500
|
activeTabId = null;
|
|
2374
2501
|
window;
|
|
2375
2502
|
onStateChange;
|
|
@@ -2407,7 +2534,10 @@ class TabManager {
|
|
|
2407
2534
|
},
|
|
2408
2535
|
onHighlightSelection: (wc) => this.captureHighlightFromPage(wc),
|
|
2409
2536
|
onHighlightRemove: (url2, text) => this.removeHighlightByText(url2, text),
|
|
2410
|
-
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color)
|
|
2537
|
+
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color),
|
|
2538
|
+
onSavePage: () => {
|
|
2539
|
+
void this.savePage(id);
|
|
2540
|
+
}
|
|
2411
2541
|
});
|
|
2412
2542
|
this.tabs.set(id, tab);
|
|
2413
2543
|
this.order.push(id);
|
|
@@ -2434,6 +2564,8 @@ class TabManager {
|
|
|
2434
2564
|
closeTab(id) {
|
|
2435
2565
|
const tab = this.tabs.get(id);
|
|
2436
2566
|
if (!tab) return;
|
|
2567
|
+
if (tab.state.isPinned) return;
|
|
2568
|
+
const groupId = tab.state.groupId;
|
|
2437
2569
|
this.closedTabs.push({
|
|
2438
2570
|
url: tab.state.url,
|
|
2439
2571
|
title: tab.state.title,
|
|
@@ -2451,6 +2583,7 @@ class TabManager {
|
|
|
2451
2583
|
tab.destroy();
|
|
2452
2584
|
this.tabs.delete(id);
|
|
2453
2585
|
this.order = this.order.filter((tid) => tid !== id);
|
|
2586
|
+
this.removeGroupIfEmpty(groupId);
|
|
2454
2587
|
if (this.activeTabId === id) {
|
|
2455
2588
|
if (this.order.length > 0) {
|
|
2456
2589
|
this.switchTab(this.order[this.order.length - 1]);
|
|
@@ -2494,6 +2627,118 @@ class TabManager {
|
|
|
2494
2627
|
if (!tab) return null;
|
|
2495
2628
|
return this.createTab(tab.state.url, { adBlockingEnabled: tab.state.adBlockingEnabled });
|
|
2496
2629
|
}
|
|
2630
|
+
pinTab(id) {
|
|
2631
|
+
const tab = this.tabs.get(id);
|
|
2632
|
+
if (!tab) return;
|
|
2633
|
+
tab.setPinned(true);
|
|
2634
|
+
this.order = this.order.filter((tid) => tid !== id);
|
|
2635
|
+
const firstNonPinned = this.order.findIndex((tid) => !this.tabs.get(tid)?.state.isPinned);
|
|
2636
|
+
if (firstNonPinned === -1) {
|
|
2637
|
+
this.order.push(id);
|
|
2638
|
+
} else {
|
|
2639
|
+
this.order.splice(firstNonPinned, 0, id);
|
|
2640
|
+
}
|
|
2641
|
+
this.broadcastState();
|
|
2642
|
+
}
|
|
2643
|
+
unpinTab(id) {
|
|
2644
|
+
const tab = this.tabs.get(id);
|
|
2645
|
+
if (!tab) return;
|
|
2646
|
+
tab.setPinned(false);
|
|
2647
|
+
this.order = this.order.filter((tid) => tid !== id);
|
|
2648
|
+
const firstNonPinned = this.order.findIndex((tid) => !this.tabs.get(tid)?.state.isPinned);
|
|
2649
|
+
if (firstNonPinned === -1) {
|
|
2650
|
+
this.order.push(id);
|
|
2651
|
+
} else {
|
|
2652
|
+
this.order.splice(firstNonPinned, 0, id);
|
|
2653
|
+
}
|
|
2654
|
+
this.broadcastState();
|
|
2655
|
+
}
|
|
2656
|
+
createGroupFromTab(id, options) {
|
|
2657
|
+
const tab = this.tabs.get(id);
|
|
2658
|
+
if (!tab) return null;
|
|
2659
|
+
const previousGroupId = tab.state.groupId;
|
|
2660
|
+
const groupId = crypto$1.randomUUID();
|
|
2661
|
+
const color = options?.color && TAB_GROUP_COLORS.includes(options.color) ? options.color : TAB_GROUP_COLORS[this.tabGroups.size % TAB_GROUP_COLORS.length];
|
|
2662
|
+
this.tabGroups.set(groupId, {
|
|
2663
|
+
id: groupId,
|
|
2664
|
+
name: options?.name?.trim() || `Group ${this.tabGroups.size + 1}`,
|
|
2665
|
+
color,
|
|
2666
|
+
collapsed: false
|
|
2667
|
+
});
|
|
2668
|
+
this.assignTabToGroup(id, groupId);
|
|
2669
|
+
this.removeGroupIfEmpty(previousGroupId);
|
|
2670
|
+
return groupId;
|
|
2671
|
+
}
|
|
2672
|
+
assignTabToGroup(id, groupId) {
|
|
2673
|
+
const tab = this.tabs.get(id);
|
|
2674
|
+
if (!tab || !this.tabGroups.has(groupId)) return;
|
|
2675
|
+
const previousGroupId = tab.state.groupId;
|
|
2676
|
+
tab.setGroup(groupId);
|
|
2677
|
+
this.removeGroupIfEmpty(previousGroupId);
|
|
2678
|
+
this.broadcastState();
|
|
2679
|
+
}
|
|
2680
|
+
removeTabFromGroup(id) {
|
|
2681
|
+
const tab = this.tabs.get(id);
|
|
2682
|
+
if (!tab) return;
|
|
2683
|
+
const groupId = tab.state.groupId;
|
|
2684
|
+
tab.setGroup(void 0);
|
|
2685
|
+
this.removeGroupIfEmpty(groupId);
|
|
2686
|
+
this.broadcastState();
|
|
2687
|
+
}
|
|
2688
|
+
toggleGroupCollapsed(groupId) {
|
|
2689
|
+
const group = this.tabGroups.get(groupId);
|
|
2690
|
+
if (!group) return null;
|
|
2691
|
+
group.collapsed = !group.collapsed;
|
|
2692
|
+
this.broadcastState();
|
|
2693
|
+
return group.collapsed;
|
|
2694
|
+
}
|
|
2695
|
+
setGroupColor(groupId, color) {
|
|
2696
|
+
const group = this.tabGroups.get(groupId);
|
|
2697
|
+
if (!group || !TAB_GROUP_COLORS.includes(color)) return;
|
|
2698
|
+
group.color = color;
|
|
2699
|
+
this.broadcastState();
|
|
2700
|
+
}
|
|
2701
|
+
toggleMuted(id) {
|
|
2702
|
+
return this.tabs.get(id)?.toggleMuted() ?? null;
|
|
2703
|
+
}
|
|
2704
|
+
printTab(id) {
|
|
2705
|
+
const tab = this.tabs.get(id);
|
|
2706
|
+
if (!tab) return;
|
|
2707
|
+
tab.view.webContents.print();
|
|
2708
|
+
}
|
|
2709
|
+
async saveTabAsPdf(id) {
|
|
2710
|
+
const tab = this.tabs.get(id);
|
|
2711
|
+
if (!tab) return null;
|
|
2712
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
2713
|
+
title: "Save Page as PDF",
|
|
2714
|
+
defaultPath: sanitizePdfFilename(tab.state.title || "Vessel Page"),
|
|
2715
|
+
filters: [{ name: "PDF", extensions: ["pdf"] }]
|
|
2716
|
+
});
|
|
2717
|
+
if (canceled || !filePath) return null;
|
|
2718
|
+
const data = await tab.view.webContents.printToPDF({
|
|
2719
|
+
printBackground: true
|
|
2720
|
+
});
|
|
2721
|
+
await fs.promises.writeFile(filePath, data);
|
|
2722
|
+
return filePath;
|
|
2723
|
+
}
|
|
2724
|
+
async savePage(id, format = "MHTML") {
|
|
2725
|
+
const tab = this.tabs.get(id);
|
|
2726
|
+
if (!tab) return null;
|
|
2727
|
+
const ext = format === "MHTML" ? "mhtml" : "html";
|
|
2728
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
2729
|
+
title: "Save Page As",
|
|
2730
|
+
defaultPath: sanitizePageFilename(
|
|
2731
|
+
tab.state.title || "Vessel Page",
|
|
2732
|
+
ext
|
|
2733
|
+
),
|
|
2734
|
+
filters: [
|
|
2735
|
+
{ name: format === "MHTML" ? "MHTML" : "HTML", extensions: [ext] }
|
|
2736
|
+
]
|
|
2737
|
+
});
|
|
2738
|
+
if (canceled || !filePath) return null;
|
|
2739
|
+
await tab.view.webContents.savePage(filePath, format);
|
|
2740
|
+
return filePath;
|
|
2741
|
+
}
|
|
2497
2742
|
getActiveTab() {
|
|
2498
2743
|
return this.activeTabId ? this.tabs.get(this.activeTabId) : void 0;
|
|
2499
2744
|
}
|
|
@@ -2504,7 +2749,10 @@ class TabManager {
|
|
|
2504
2749
|
return this.activeTabId;
|
|
2505
2750
|
}
|
|
2506
2751
|
getAllStates() {
|
|
2507
|
-
return this.order.map((id) => this.tabs.get(id).state);
|
|
2752
|
+
return this.order.map((id) => this.withGroupState(this.tabs.get(id).state));
|
|
2753
|
+
}
|
|
2754
|
+
getGroups() {
|
|
2755
|
+
return Array.from(this.tabGroups.values());
|
|
2508
2756
|
}
|
|
2509
2757
|
findTabByWebContentsId(webContentsId) {
|
|
2510
2758
|
for (const id of this.order) {
|
|
@@ -2537,7 +2785,10 @@ class TabManager {
|
|
|
2537
2785
|
id: state2.id,
|
|
2538
2786
|
url: state2.url || "about:blank",
|
|
2539
2787
|
title: state2.title,
|
|
2540
|
-
adBlockingEnabled: state2.adBlockingEnabled
|
|
2788
|
+
adBlockingEnabled: state2.adBlockingEnabled,
|
|
2789
|
+
isPinned: state2.isPinned,
|
|
2790
|
+
groupName: state2.groupName,
|
|
2791
|
+
groupColor: state2.groupColor
|
|
2541
2792
|
})),
|
|
2542
2793
|
activeIndex: activeIndex >= 0 ? activeIndex : 0,
|
|
2543
2794
|
activeTabId: activeId || void 0,
|
|
@@ -2552,12 +2803,33 @@ class TabManager {
|
|
|
2552
2803
|
Math.min(snapshot.activeIndex, tabs.length - 1)
|
|
2553
2804
|
);
|
|
2554
2805
|
this.destroyAllTabs();
|
|
2806
|
+
const restoredGroups = /* @__PURE__ */ new Map();
|
|
2555
2807
|
const ids = tabs.map(
|
|
2556
2808
|
(tab, index) => this.createTab(tab.url || "about:blank", {
|
|
2557
2809
|
background: index !== activeIndex,
|
|
2558
2810
|
adBlockingEnabled: tab.adBlockingEnabled ?? true
|
|
2559
2811
|
})
|
|
2560
2812
|
);
|
|
2813
|
+
tabs.forEach((tab, index) => {
|
|
2814
|
+
if (tab.isPinned && ids[index]) {
|
|
2815
|
+
this.pinTab(ids[index]);
|
|
2816
|
+
}
|
|
2817
|
+
if (tab.groupName && ids[index]) {
|
|
2818
|
+
const key = `${tab.groupName}|${tab.groupColor ?? "blue"}`;
|
|
2819
|
+
let groupId = restoredGroups.get(key);
|
|
2820
|
+
if (!groupId) {
|
|
2821
|
+
groupId = crypto$1.randomUUID();
|
|
2822
|
+
restoredGroups.set(key, groupId);
|
|
2823
|
+
this.tabGroups.set(groupId, {
|
|
2824
|
+
id: groupId,
|
|
2825
|
+
name: tab.groupName,
|
|
2826
|
+
color: tab.groupColor ?? "blue",
|
|
2827
|
+
collapsed: false
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
this.assignTabToGroup(ids[index], groupId);
|
|
2831
|
+
}
|
|
2832
|
+
});
|
|
2561
2833
|
const activeId = ids[activeIndex];
|
|
2562
2834
|
if (activeId) {
|
|
2563
2835
|
this.switchTab(activeId);
|
|
@@ -2576,6 +2848,7 @@ class TabManager {
|
|
|
2576
2848
|
}
|
|
2577
2849
|
this.tabs.clear();
|
|
2578
2850
|
this.order = [];
|
|
2851
|
+
this.tabGroups.clear();
|
|
2579
2852
|
this.activeTabId = null;
|
|
2580
2853
|
this.broadcastState();
|
|
2581
2854
|
}
|
|
@@ -2692,6 +2965,24 @@ class TabManager {
|
|
|
2692
2965
|
message: `Color changed to ${color}`
|
|
2693
2966
|
});
|
|
2694
2967
|
}
|
|
2968
|
+
withGroupState(state2) {
|
|
2969
|
+
if (!state2.groupId) return state2;
|
|
2970
|
+
const group = this.tabGroups.get(state2.groupId);
|
|
2971
|
+
if (!group) return { ...state2, groupId: void 0 };
|
|
2972
|
+
return {
|
|
2973
|
+
...state2,
|
|
2974
|
+
groupName: group.name,
|
|
2975
|
+
groupColor: group.color,
|
|
2976
|
+
groupCollapsed: group.collapsed
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
removeGroupIfEmpty(groupId) {
|
|
2980
|
+
if (!groupId) return;
|
|
2981
|
+
for (const tab of this.tabs.values()) {
|
|
2982
|
+
if (tab.state.groupId === groupId) return;
|
|
2983
|
+
}
|
|
2984
|
+
this.tabGroups.delete(groupId);
|
|
2985
|
+
}
|
|
2695
2986
|
async removeHighlightMarksForText(wc, text) {
|
|
2696
2987
|
await wc.executeJavaScript(
|
|
2697
2988
|
`(function() {
|
|
@@ -2773,6 +3064,8 @@ const Channels = {
|
|
|
2773
3064
|
BOOKMARK_SAVE: "bookmarks:save",
|
|
2774
3065
|
BOOKMARK_UPDATE: "bookmarks:update-item",
|
|
2775
3066
|
BOOKMARK_REMOVE: "bookmarks:remove",
|
|
3067
|
+
BOOKMARKS_EXPORT_HTML: "bookmarks:export-html",
|
|
3068
|
+
BOOKMARKS_EXPORT_JSON: "bookmarks:export-json",
|
|
2776
3069
|
BOOKMARK_ADD_CONTEXT_TO_CHAT: "bookmarks:add-context-to-chat",
|
|
2777
3070
|
FOLDER_CREATE: "bookmarks:folder-create",
|
|
2778
3071
|
FOLDER_REMOVE: "bookmarks:folder-remove",
|
|
@@ -2801,6 +3094,23 @@ const Channels = {
|
|
|
2801
3094
|
TAB_REOPEN_CLOSED: "tab:reopen-closed",
|
|
2802
3095
|
TAB_DUPLICATE: "tab:duplicate",
|
|
2803
3096
|
TAB_CONTEXT_MENU: "tab:context-menu",
|
|
3097
|
+
// Pin tabs
|
|
3098
|
+
TAB_PIN: "tab:pin",
|
|
3099
|
+
TAB_UNPIN: "tab:unpin",
|
|
3100
|
+
// Tab groups
|
|
3101
|
+
TAB_GROUP_CREATE: "tab-group:create",
|
|
3102
|
+
TAB_GROUP_ADD_TAB: "tab-group:add-tab",
|
|
3103
|
+
TAB_GROUP_REMOVE_TAB: "tab-group:remove-tab",
|
|
3104
|
+
TAB_GROUP_TOGGLE_COLLAPSED: "tab-group:toggle-collapsed",
|
|
3105
|
+
TAB_GROUP_SET_COLOR: "tab-group:set-color",
|
|
3106
|
+
TAB_GROUP_CONTEXT_MENU: "tab-group:context-menu",
|
|
3107
|
+
// Audio / mute
|
|
3108
|
+
TAB_TOGGLE_MUTE: "tab:toggle-mute",
|
|
3109
|
+
// Print
|
|
3110
|
+
TAB_PRINT: "tab:print",
|
|
3111
|
+
TAB_PRINT_TO_PDF: "tab:print-to-pdf",
|
|
3112
|
+
// Windows
|
|
3113
|
+
OPEN_NEW_WINDOW: "window:open-new",
|
|
2804
3114
|
// Private browsing
|
|
2805
3115
|
OPEN_PRIVATE_WINDOW: "private:open-window",
|
|
2806
3116
|
IS_PRIVATE_MODE: "private:is-private",
|
|
@@ -5694,7 +6004,7 @@ function enableClipboardShortcuts(view) {
|
|
|
5694
6004
|
}
|
|
5695
6005
|
});
|
|
5696
6006
|
}
|
|
5697
|
-
const CHROME_HEIGHT$
|
|
6007
|
+
const CHROME_HEIGHT$2 = 110;
|
|
5698
6008
|
const DEFAULT_DEVTOOLS_PANEL_HEIGHT = 250;
|
|
5699
6009
|
const MIN_DEVTOOLS_PANEL = 120;
|
|
5700
6010
|
const MAX_DEVTOOLS_PANEL = 600;
|
|
@@ -5836,7 +6146,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
5836
6146
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
5837
6147
|
sandbox: true,
|
|
5838
6148
|
contextIsolation: true,
|
|
5839
|
-
nodeIntegration: false
|
|
6149
|
+
nodeIntegration: false,
|
|
6150
|
+
spellcheck: false
|
|
5840
6151
|
}
|
|
5841
6152
|
});
|
|
5842
6153
|
chromeView.setBackgroundColor("#00000000");
|
|
@@ -5846,7 +6157,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
5846
6157
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
5847
6158
|
sandbox: true,
|
|
5848
6159
|
contextIsolation: true,
|
|
5849
|
-
nodeIntegration: false
|
|
6160
|
+
nodeIntegration: false,
|
|
6161
|
+
spellcheck: false
|
|
5850
6162
|
}
|
|
5851
6163
|
});
|
|
5852
6164
|
sidebarView.setBackgroundColor("#00000000");
|
|
@@ -5860,7 +6172,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
5860
6172
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
5861
6173
|
sandbox: true,
|
|
5862
6174
|
contextIsolation: true,
|
|
5863
|
-
nodeIntegration: false
|
|
6175
|
+
nodeIntegration: false,
|
|
6176
|
+
spellcheck: false
|
|
5864
6177
|
}
|
|
5865
6178
|
});
|
|
5866
6179
|
devtoolsPanelView.setBackgroundColor("#00000000");
|
|
@@ -5909,7 +6222,7 @@ function layoutViews(state2) {
|
|
|
5909
6222
|
uiState
|
|
5910
6223
|
} = state2;
|
|
5911
6224
|
const [width, height] = mainWindow.getContentSize();
|
|
5912
|
-
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT$
|
|
6225
|
+
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT$2;
|
|
5913
6226
|
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
5914
6227
|
const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
|
|
5915
6228
|
const chromeNeedsFullHeight = uiState.settingsOpen;
|
|
@@ -5959,7 +6272,7 @@ function layoutViews(state2) {
|
|
|
5959
6272
|
function resizeSidebarViews(state2) {
|
|
5960
6273
|
const { mainWindow, sidebarView, devtoolsPanelView, tabManager, uiState } = state2;
|
|
5961
6274
|
const [width, height] = mainWindow.getContentSize();
|
|
5962
|
-
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT$
|
|
6275
|
+
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT$2;
|
|
5963
6276
|
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
5964
6277
|
const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
|
|
5965
6278
|
const resizeHandleOverlap = 6;
|
|
@@ -9851,7 +10164,7 @@ function buildCompactScopedContext(page, mode, pageType = detectPageType(page))
|
|
|
9851
10164
|
const primaryResults = primaryResultElements.map(formatElement);
|
|
9852
10165
|
if (primaryResults.length > 0) {
|
|
9853
10166
|
lines.push("");
|
|
9854
|
-
lines.push("### Results
|
|
10167
|
+
lines.push("### Primary Results");
|
|
9855
10168
|
lines.push(...primaryResults.map((item) => `- ${item}`));
|
|
9856
10169
|
lines.push("");
|
|
9857
10170
|
lines.push("IMPORTANT: Use click(index=N) on a result above. Do NOT click filter or sort links.");
|
|
@@ -9995,6 +10308,61 @@ const TOOL_DEFINITIONS = [
|
|
|
9995
10308
|
},
|
|
9996
10309
|
tier: 2
|
|
9997
10310
|
},
|
|
10311
|
+
{
|
|
10312
|
+
name: "list_groups",
|
|
10313
|
+
title: "List Tab Groups",
|
|
10314
|
+
description: "List all tab groups with their IDs, names, colors, collapsed state, and member tab count.",
|
|
10315
|
+
tier: 2
|
|
10316
|
+
},
|
|
10317
|
+
{
|
|
10318
|
+
name: "create_group",
|
|
10319
|
+
title: "Create Tab Group",
|
|
10320
|
+
description: "Create a new tab group from the active tab or a specified tab. Optionally provide a name and color.",
|
|
10321
|
+
inputSchema: {
|
|
10322
|
+
tabId: zod.z.string().optional().describe("Tab ID to group (defaults to active tab)"),
|
|
10323
|
+
name: zod.z.string().optional().describe("Optional group name"),
|
|
10324
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).optional().describe("Optional group color")
|
|
10325
|
+
},
|
|
10326
|
+
tier: 2
|
|
10327
|
+
},
|
|
10328
|
+
{
|
|
10329
|
+
name: "assign_to_group",
|
|
10330
|
+
title: "Assign Tab to Group",
|
|
10331
|
+
description: "Move a tab into an existing group by ID. Defaults to the active tab.",
|
|
10332
|
+
inputSchema: {
|
|
10333
|
+
groupId: zod.z.string().describe("Group ID to assign the tab to"),
|
|
10334
|
+
tabId: zod.z.string().optional().describe("Tab ID to move (defaults to active tab)")
|
|
10335
|
+
},
|
|
10336
|
+
tier: 2
|
|
10337
|
+
},
|
|
10338
|
+
{
|
|
10339
|
+
name: "remove_from_group",
|
|
10340
|
+
title: "Remove Tab from Group",
|
|
10341
|
+
description: "Ungroup a tab. Defaults to the active tab.",
|
|
10342
|
+
inputSchema: {
|
|
10343
|
+
tabId: zod.z.string().optional().describe("Tab ID to ungroup (defaults to active tab)")
|
|
10344
|
+
},
|
|
10345
|
+
tier: 2
|
|
10346
|
+
},
|
|
10347
|
+
{
|
|
10348
|
+
name: "toggle_group",
|
|
10349
|
+
title: "Toggle Group Collapsed",
|
|
10350
|
+
description: "Collapse or expand a tab group.",
|
|
10351
|
+
inputSchema: {
|
|
10352
|
+
groupId: zod.z.string().describe("Group ID to toggle")
|
|
10353
|
+
},
|
|
10354
|
+
tier: 2
|
|
10355
|
+
},
|
|
10356
|
+
{
|
|
10357
|
+
name: "set_group_color",
|
|
10358
|
+
title: "Set Group Color",
|
|
10359
|
+
description: "Change the color of a tab group.",
|
|
10360
|
+
inputSchema: {
|
|
10361
|
+
groupId: zod.z.string().describe("Group ID"),
|
|
10362
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).describe("New color")
|
|
10363
|
+
},
|
|
10364
|
+
tier: 2
|
|
10365
|
+
},
|
|
9998
10366
|
// --- Navigation ---
|
|
9999
10367
|
{
|
|
10000
10368
|
name: "navigate",
|
|
@@ -10777,6 +11145,12 @@ function shouldIncludeTool(toolName, pageType, intents, profile) {
|
|
|
10777
11145
|
case "switch_tab":
|
|
10778
11146
|
case "create_tab":
|
|
10779
11147
|
case "set_ad_blocking":
|
|
11148
|
+
case "list_groups":
|
|
11149
|
+
case "create_group":
|
|
11150
|
+
case "assign_to_group":
|
|
11151
|
+
case "remove_from_group":
|
|
11152
|
+
case "toggle_group":
|
|
11153
|
+
case "set_group_color":
|
|
10780
11154
|
return intents.has("tabs") || intents.has("debug");
|
|
10781
11155
|
case "save_session":
|
|
10782
11156
|
case "load_session":
|
|
@@ -10828,14 +11202,6 @@ function pruneToolsForContext(tools, pageType, query = "", options = {}) {
|
|
|
10828
11202
|
return description !== tool.description ? { ...tool, description } : tool;
|
|
10829
11203
|
});
|
|
10830
11204
|
}
|
|
10831
|
-
const SEARCH_ENGINE_PRESETS = {
|
|
10832
|
-
duckduckgo: { label: "DuckDuckGo", url: "https://duckduckgo.com/?q=" },
|
|
10833
|
-
google: { label: "Google", url: "https://www.google.com/search?q=" },
|
|
10834
|
-
bing: { label: "Bing", url: "https://www.bing.com/search?q=" },
|
|
10835
|
-
brave: { label: "Brave Search", url: "https://search.brave.com/search?q=" },
|
|
10836
|
-
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
10837
|
-
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
10838
|
-
};
|
|
10839
11205
|
function trimText(value) {
|
|
10840
11206
|
return typeof value === "string" ? value.trim() : "";
|
|
10841
11207
|
}
|
|
@@ -11027,6 +11393,7 @@ function normalizeBookmarkMetadataUpdate(input) {
|
|
|
11027
11393
|
}
|
|
11028
11394
|
const UNSORTED_ID = "unsorted";
|
|
11029
11395
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
11396
|
+
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
11030
11397
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
11031
11398
|
let state$1 = null;
|
|
11032
11399
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -11039,6 +11406,19 @@ function cloneState(current) {
|
|
|
11039
11406
|
function getBookmarksPath() {
|
|
11040
11407
|
return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
|
|
11041
11408
|
}
|
|
11409
|
+
function createPersistence() {
|
|
11410
|
+
return createDebouncedJsonPersistence({
|
|
11411
|
+
debounceMs: SAVE_DEBOUNCE_MS$1,
|
|
11412
|
+
filePath: getBookmarksPath(),
|
|
11413
|
+
getValue: () => state$1,
|
|
11414
|
+
logLabel: "bookmarks"
|
|
11415
|
+
});
|
|
11416
|
+
}
|
|
11417
|
+
let persistence$1 = null;
|
|
11418
|
+
function getPersistence() {
|
|
11419
|
+
persistence$1 ??= createPersistence();
|
|
11420
|
+
return persistence$1;
|
|
11421
|
+
}
|
|
11042
11422
|
function load$1() {
|
|
11043
11423
|
if (state$1) return state$1;
|
|
11044
11424
|
state$1 = loadJsonFile({
|
|
@@ -11054,14 +11434,8 @@ function load$1() {
|
|
|
11054
11434
|
});
|
|
11055
11435
|
return state$1;
|
|
11056
11436
|
}
|
|
11057
|
-
const persistence$1 = createDebouncedJsonPersistence({
|
|
11058
|
-
debounceMs: SAVE_DEBOUNCE_MS$1,
|
|
11059
|
-
filePath: getBookmarksPath(),
|
|
11060
|
-
getValue: () => state$1,
|
|
11061
|
-
logLabel: "bookmarks"
|
|
11062
|
-
});
|
|
11063
11437
|
function save() {
|
|
11064
|
-
|
|
11438
|
+
getPersistence().schedule();
|
|
11065
11439
|
}
|
|
11066
11440
|
function assignDefinedBookmarkFields(bookmark, fields) {
|
|
11067
11441
|
if (!fields) return;
|
|
@@ -11077,9 +11451,81 @@ function emit() {
|
|
|
11077
11451
|
listener(snapshot);
|
|
11078
11452
|
}
|
|
11079
11453
|
}
|
|
11454
|
+
function escapeBookmarkHtml(value) {
|
|
11455
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11456
|
+
}
|
|
11457
|
+
function toNetscapeTimestamp(value) {
|
|
11458
|
+
if (!value) return Math.floor(Date.now() / 1e3);
|
|
11459
|
+
const time = Date.parse(value);
|
|
11460
|
+
return Number.isNaN(time) ? Math.floor(Date.now() / 1e3) : Math.floor(time / 1e3);
|
|
11461
|
+
}
|
|
11462
|
+
function getBookmarkDescription(bookmark) {
|
|
11463
|
+
const lines = [
|
|
11464
|
+
bookmark.note ? `Note: ${bookmark.note}` : "",
|
|
11465
|
+
bookmark.intent ? `Intent: ${bookmark.intent}` : "",
|
|
11466
|
+
bookmark.expectedContent ? `Expected content: ${bookmark.expectedContent}` : "",
|
|
11467
|
+
bookmark.keyFields?.length ? `Key fields: ${bookmark.keyFields.join(", ")}` : "",
|
|
11468
|
+
bookmark.agentHints && Object.keys(bookmark.agentHints).length > 0 ? `Agent hints: ${Object.entries(bookmark.agentHints).map(([key, value]) => `${key}: ${value}`).join("; ")}` : ""
|
|
11469
|
+
].filter(Boolean);
|
|
11470
|
+
return lines.join("\n");
|
|
11471
|
+
}
|
|
11472
|
+
function appendBookmarkHtml(lines, bookmark, options, indent) {
|
|
11473
|
+
const addDate = toNetscapeTimestamp(bookmark.savedAt);
|
|
11474
|
+
lines.push(
|
|
11475
|
+
`${indent}<DT><A HREF="${escapeBookmarkHtml(bookmark.url)}" ADD_DATE="${addDate}">${escapeBookmarkHtml(bookmark.title || bookmark.url)}</A>`
|
|
11476
|
+
);
|
|
11477
|
+
if (!options.includeNotes) return;
|
|
11478
|
+
const description = getBookmarkDescription(bookmark);
|
|
11479
|
+
if (description) {
|
|
11480
|
+
lines.push(`${indent}<DD>${escapeBookmarkHtml(description)}`);
|
|
11481
|
+
}
|
|
11482
|
+
}
|
|
11080
11483
|
function getState() {
|
|
11081
11484
|
return cloneState(load$1());
|
|
11082
11485
|
}
|
|
11486
|
+
function exportBookmarksHtml(options = {}) {
|
|
11487
|
+
const current = getState();
|
|
11488
|
+
const resolvedOptions = {
|
|
11489
|
+
includeNotes: options.includeNotes ?? false
|
|
11490
|
+
};
|
|
11491
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
11492
|
+
const folders = [
|
|
11493
|
+
{ id: UNSORTED_ID, name: "Vessel Bookmarks", createdAt: "", summary: "" },
|
|
11494
|
+
...current.folders
|
|
11495
|
+
];
|
|
11496
|
+
const lines = [
|
|
11497
|
+
NETSCAPE_BOOKMARKS_DOCTYPE,
|
|
11498
|
+
'<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">',
|
|
11499
|
+
"<TITLE>Bookmarks</TITLE>",
|
|
11500
|
+
"<H1>Bookmarks</H1>",
|
|
11501
|
+
"<DL><p>"
|
|
11502
|
+
];
|
|
11503
|
+
for (const folder of folders) {
|
|
11504
|
+
const items = current.bookmarks.filter(
|
|
11505
|
+
(bookmark) => bookmark.folderId === folder.id
|
|
11506
|
+
);
|
|
11507
|
+
if (items.length === 0) continue;
|
|
11508
|
+
const addDate = toNetscapeTimestamp(folder.createdAt) || now;
|
|
11509
|
+
lines.push(
|
|
11510
|
+
` <DT><H3 ADD_DATE="${addDate}" LAST_MODIFIED="${now}">${escapeBookmarkHtml(folder.name)}</H3>`
|
|
11511
|
+
);
|
|
11512
|
+
if (resolvedOptions.includeNotes && folder.summary) {
|
|
11513
|
+
lines.push(` <DD>${escapeBookmarkHtml(folder.summary)}`);
|
|
11514
|
+
}
|
|
11515
|
+
lines.push(" <DL><p>");
|
|
11516
|
+
for (const bookmark of items) {
|
|
11517
|
+
appendBookmarkHtml(lines, bookmark, resolvedOptions, " ");
|
|
11518
|
+
}
|
|
11519
|
+
lines.push(" </DL><p>");
|
|
11520
|
+
}
|
|
11521
|
+
lines.push("</DL><p>");
|
|
11522
|
+
return `${lines.join("\n")}
|
|
11523
|
+
`;
|
|
11524
|
+
}
|
|
11525
|
+
function exportBookmarksJson() {
|
|
11526
|
+
return `${JSON.stringify(getState(), null, 2)}
|
|
11527
|
+
`;
|
|
11528
|
+
}
|
|
11083
11529
|
function subscribe(listener) {
|
|
11084
11530
|
listeners.add(listener);
|
|
11085
11531
|
return () => {
|
|
@@ -11350,7 +11796,7 @@ function renameFolder(id, newName, summary) {
|
|
|
11350
11796
|
return { ...folder };
|
|
11351
11797
|
}
|
|
11352
11798
|
function flushPersist$1() {
|
|
11353
|
-
return
|
|
11799
|
+
return getPersistence().flush();
|
|
11354
11800
|
}
|
|
11355
11801
|
function normalizeText(text) {
|
|
11356
11802
|
return text?.trim() ?? "";
|
|
@@ -19304,6 +19750,120 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19304
19750
|
return `Closed tab ${tabId}`;
|
|
19305
19751
|
})
|
|
19306
19752
|
);
|
|
19753
|
+
server.registerTool(
|
|
19754
|
+
"list_groups",
|
|
19755
|
+
{
|
|
19756
|
+
title: "List Tab Groups",
|
|
19757
|
+
description: "List all tab groups with their IDs, names, colors, collapsed state, and member tab count."
|
|
19758
|
+
},
|
|
19759
|
+
async () => {
|
|
19760
|
+
const groups = tabManager.getGroups();
|
|
19761
|
+
const tabs = tabManager.getAllStates();
|
|
19762
|
+
if (groups.length === 0) {
|
|
19763
|
+
return asTextResponse("No tab groups");
|
|
19764
|
+
}
|
|
19765
|
+
const lines = groups.map((g) => {
|
|
19766
|
+
const count = tabs.filter((t) => t.groupId === g.id).length;
|
|
19767
|
+
return `[${g.id}] ${g.name} — color:${g.color} collapsed:${g.collapsed} tabs:${count}`;
|
|
19768
|
+
});
|
|
19769
|
+
return asTextResponse(lines.join("\n"));
|
|
19770
|
+
}
|
|
19771
|
+
);
|
|
19772
|
+
server.registerTool(
|
|
19773
|
+
"create_group",
|
|
19774
|
+
{
|
|
19775
|
+
title: "Create Tab Group",
|
|
19776
|
+
description: "Create a new tab group from the active tab or a specified tab. Optionally provide a name and color.",
|
|
19777
|
+
inputSchema: {
|
|
19778
|
+
tabId: zod.z.string().optional().describe("Tab ID to group (defaults to active tab)"),
|
|
19779
|
+
name: zod.z.string().optional().describe("Optional group name"),
|
|
19780
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).optional().describe("Optional group color")
|
|
19781
|
+
}
|
|
19782
|
+
},
|
|
19783
|
+
async ({ tabId, name, color }) => withAction(runtime2, tabManager, "create_group", { tabId, name, color }, async () => {
|
|
19784
|
+
const targetId = tabId || tabManager.getActiveTabId();
|
|
19785
|
+
if (!targetId) {
|
|
19786
|
+
return "Error: No active tab";
|
|
19787
|
+
}
|
|
19788
|
+
const groupId = tabManager.createGroupFromTab(targetId, {
|
|
19789
|
+
name: name || void 0,
|
|
19790
|
+
color: color || void 0
|
|
19791
|
+
});
|
|
19792
|
+
if (!groupId) {
|
|
19793
|
+
return "Error: Could not create group";
|
|
19794
|
+
}
|
|
19795
|
+
return `Created group ${groupId}`;
|
|
19796
|
+
})
|
|
19797
|
+
);
|
|
19798
|
+
server.registerTool(
|
|
19799
|
+
"assign_to_group",
|
|
19800
|
+
{
|
|
19801
|
+
title: "Assign Tab to Group",
|
|
19802
|
+
description: "Move a tab into an existing group by ID. Defaults to the active tab.",
|
|
19803
|
+
inputSchema: {
|
|
19804
|
+
groupId: zod.z.string().describe("Group ID to assign the tab to"),
|
|
19805
|
+
tabId: zod.z.string().optional().describe("Tab ID to move (defaults to active tab)")
|
|
19806
|
+
}
|
|
19807
|
+
},
|
|
19808
|
+
async ({ groupId, tabId }) => withAction(runtime2, tabManager, "assign_to_group", { groupId, tabId }, async () => {
|
|
19809
|
+
const targetId = tabId || tabManager.getActiveTabId();
|
|
19810
|
+
if (!targetId) {
|
|
19811
|
+
return "Error: No active tab";
|
|
19812
|
+
}
|
|
19813
|
+
tabManager.assignTabToGroup(targetId, groupId);
|
|
19814
|
+
return `Assigned tab ${targetId} to group ${groupId}`;
|
|
19815
|
+
})
|
|
19816
|
+
);
|
|
19817
|
+
server.registerTool(
|
|
19818
|
+
"remove_from_group",
|
|
19819
|
+
{
|
|
19820
|
+
title: "Remove Tab from Group",
|
|
19821
|
+
description: "Ungroup a tab. Defaults to the active tab.",
|
|
19822
|
+
inputSchema: {
|
|
19823
|
+
tabId: zod.z.string().optional().describe("Tab ID to ungroup (defaults to active tab)")
|
|
19824
|
+
}
|
|
19825
|
+
},
|
|
19826
|
+
async ({ tabId }) => withAction(runtime2, tabManager, "remove_from_group", { tabId }, async () => {
|
|
19827
|
+
const targetId = tabId || tabManager.getActiveTabId();
|
|
19828
|
+
if (!targetId) {
|
|
19829
|
+
return "Error: No active tab";
|
|
19830
|
+
}
|
|
19831
|
+
tabManager.removeTabFromGroup(targetId);
|
|
19832
|
+
return `Removed tab ${targetId} from group`;
|
|
19833
|
+
})
|
|
19834
|
+
);
|
|
19835
|
+
server.registerTool(
|
|
19836
|
+
"toggle_group",
|
|
19837
|
+
{
|
|
19838
|
+
title: "Toggle Group Collapsed",
|
|
19839
|
+
description: "Collapse or expand a tab group.",
|
|
19840
|
+
inputSchema: {
|
|
19841
|
+
groupId: zod.z.string().describe("Group ID to toggle")
|
|
19842
|
+
}
|
|
19843
|
+
},
|
|
19844
|
+
async ({ groupId }) => withAction(runtime2, tabManager, "toggle_group", { groupId }, async () => {
|
|
19845
|
+
const collapsed = tabManager.toggleGroupCollapsed(groupId);
|
|
19846
|
+
if (collapsed === null) {
|
|
19847
|
+
return "Error: Group not found";
|
|
19848
|
+
}
|
|
19849
|
+
return collapsed ? `Collapsed group ${groupId}` : `Expanded group ${groupId}`;
|
|
19850
|
+
})
|
|
19851
|
+
);
|
|
19852
|
+
server.registerTool(
|
|
19853
|
+
"set_group_color",
|
|
19854
|
+
{
|
|
19855
|
+
title: "Set Group Color",
|
|
19856
|
+
description: "Change the color of a tab group.",
|
|
19857
|
+
inputSchema: {
|
|
19858
|
+
groupId: zod.z.string().describe("Group ID"),
|
|
19859
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).describe("New color")
|
|
19860
|
+
}
|
|
19861
|
+
},
|
|
19862
|
+
async ({ groupId, color }) => withAction(runtime2, tabManager, "set_group_color", { groupId, color }, async () => {
|
|
19863
|
+
tabManager.setGroupColor(groupId, color);
|
|
19864
|
+
return `Set group ${groupId} color to ${color}`;
|
|
19865
|
+
})
|
|
19866
|
+
);
|
|
19307
19867
|
server.registerTool(
|
|
19308
19868
|
"checkpoint_create",
|
|
19309
19869
|
{
|
|
@@ -22520,6 +23080,7 @@ const THIRD_PARTY_PATH_PATTERNS = [
|
|
|
22520
23080
|
/\/pixel/i
|
|
22521
23081
|
];
|
|
22522
23082
|
let installed = false;
|
|
23083
|
+
const defaultSessionTabManagers = /* @__PURE__ */ new Set();
|
|
22523
23084
|
function normalizeHostname(value) {
|
|
22524
23085
|
return value.trim().toLowerCase().replace(/\.$/, "");
|
|
22525
23086
|
}
|
|
@@ -22563,6 +23124,7 @@ function shouldBlockRequest(details) {
|
|
|
22563
23124
|
return THIRD_PARTY_PATH_PATTERNS.some((pattern) => pattern.test(candidate));
|
|
22564
23125
|
}
|
|
22565
23126
|
function installAdBlocking(tabManager) {
|
|
23127
|
+
defaultSessionTabManagers.add(tabManager);
|
|
22566
23128
|
if (installed) return;
|
|
22567
23129
|
installed = true;
|
|
22568
23130
|
electron.session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
|
@@ -22571,13 +23133,19 @@ function installAdBlocking(tabManager) {
|
|
|
22571
23133
|
callback({});
|
|
22572
23134
|
return;
|
|
22573
23135
|
}
|
|
22574
|
-
|
|
23136
|
+
const manager = [...defaultSessionTabManagers].find(
|
|
23137
|
+
(candidate) => candidate.findTabByWebContentsId(webContentsId)
|
|
23138
|
+
);
|
|
23139
|
+
if (!manager?.isAdBlockingEnabledForWebContents(webContentsId)) {
|
|
22575
23140
|
callback({});
|
|
22576
23141
|
return;
|
|
22577
23142
|
}
|
|
22578
23143
|
callback({ cancel: shouldBlockRequest(details) });
|
|
22579
23144
|
});
|
|
22580
23145
|
}
|
|
23146
|
+
function unregisterAdBlockingTabManager(tabManager) {
|
|
23147
|
+
defaultSessionTabManagers.delete(tabManager);
|
|
23148
|
+
}
|
|
22581
23149
|
function installAdBlockingForSession(ses, tabManager) {
|
|
22582
23150
|
ses.webRequest.onBeforeRequest((details, callback) => {
|
|
22583
23151
|
const webContentsId = typeof details.webContentsId === "number" ? details.webContentsId : null;
|
|
@@ -22592,6 +23160,8 @@ function installAdBlockingForSession(ses, tabManager) {
|
|
|
22592
23160
|
callback({ cancel: shouldBlockRequest(details) });
|
|
22593
23161
|
});
|
|
22594
23162
|
}
|
|
23163
|
+
const defaultDownloadViews = /* @__PURE__ */ new Set();
|
|
23164
|
+
let defaultDownloadHandlerInstalled = false;
|
|
22595
23165
|
function resolveDownloadPath(downloadDir, filename) {
|
|
22596
23166
|
fs$1.mkdirSync(downloadDir, { recursive: true });
|
|
22597
23167
|
const parsed = path.parse(filename);
|
|
@@ -22606,9 +23176,23 @@ function resolveDownloadPath(downloadDir, filename) {
|
|
|
22606
23176
|
}
|
|
22607
23177
|
}
|
|
22608
23178
|
function installDownloadHandler(chromeView) {
|
|
22609
|
-
|
|
23179
|
+
defaultDownloadViews.add(chromeView);
|
|
23180
|
+
if (defaultDownloadHandlerInstalled) return;
|
|
23181
|
+
defaultDownloadHandlerInstalled = true;
|
|
23182
|
+
installDownloadHandlerForSession(electron.session.defaultSession, defaultDownloadViews);
|
|
23183
|
+
}
|
|
23184
|
+
function unregisterDownloadHandler(chromeView) {
|
|
23185
|
+
defaultDownloadViews.delete(chromeView);
|
|
22610
23186
|
}
|
|
22611
23187
|
function installDownloadHandlerForSession(targetSession, chromeView) {
|
|
23188
|
+
const send = (channel, info) => {
|
|
23189
|
+
const views = chromeView instanceof electron.WebContentsView ? [chromeView] : [...chromeView];
|
|
23190
|
+
for (const view of views) {
|
|
23191
|
+
if (!view.webContents.isDestroyed()) {
|
|
23192
|
+
view.webContents.send(channel, info);
|
|
23193
|
+
}
|
|
23194
|
+
}
|
|
23195
|
+
};
|
|
22612
23196
|
targetSession.on("will-download", (_event, item) => {
|
|
22613
23197
|
const settings2 = loadSettings();
|
|
22614
23198
|
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
@@ -22622,30 +23206,24 @@ function installDownloadHandlerForSession(targetSession, chromeView) {
|
|
|
22622
23206
|
receivedBytes: 0,
|
|
22623
23207
|
state: "progressing"
|
|
22624
23208
|
};
|
|
22625
|
-
|
|
22626
|
-
chromeView.webContents.send(Channels.DOWNLOAD_STARTED, info);
|
|
22627
|
-
}
|
|
23209
|
+
send(Channels.DOWNLOAD_STARTED, info);
|
|
22628
23210
|
item.on("updated", (_event2, state2) => {
|
|
22629
23211
|
info.receivedBytes = item.getReceivedBytes();
|
|
22630
23212
|
info.totalBytes = item.getTotalBytes();
|
|
22631
23213
|
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
22632
|
-
|
|
22633
|
-
chromeView.webContents.send(Channels.DOWNLOAD_PROGRESS, info);
|
|
22634
|
-
}
|
|
23214
|
+
send(Channels.DOWNLOAD_PROGRESS, info);
|
|
22635
23215
|
});
|
|
22636
23216
|
item.once("done", (_event2, state2) => {
|
|
22637
23217
|
info.receivedBytes = item.getReceivedBytes();
|
|
22638
23218
|
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
22639
|
-
|
|
22640
|
-
chromeView.webContents.send(Channels.DOWNLOAD_DONE, info);
|
|
22641
|
-
}
|
|
23219
|
+
send(Channels.DOWNLOAD_DONE, info);
|
|
22642
23220
|
});
|
|
22643
23221
|
});
|
|
22644
23222
|
}
|
|
22645
23223
|
const logger$5 = createLogger("PrivateWindow");
|
|
22646
|
-
const CHROME_HEIGHT = 110;
|
|
23224
|
+
const CHROME_HEIGHT$1 = 110;
|
|
22647
23225
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
22648
|
-
function resolveRendererFile$
|
|
23226
|
+
function resolveRendererFile$2() {
|
|
22649
23227
|
const candidates = [
|
|
22650
23228
|
path.join(__dirname, "../../out/renderer/index.html"),
|
|
22651
23229
|
path.join(__dirname, "../../../out/renderer/index.html")
|
|
@@ -22662,16 +23240,16 @@ function resolveRendererFile$1() {
|
|
|
22662
23240
|
function layoutPrivateViews(state2) {
|
|
22663
23241
|
const { window: win, chromeView, tabManager } = state2;
|
|
22664
23242
|
const [width, height] = win.getContentSize();
|
|
22665
|
-
chromeView.setBounds({ x: 0, y: 0, width, height: CHROME_HEIGHT });
|
|
23243
|
+
chromeView.setBounds({ x: 0, y: 0, width, height: CHROME_HEIGHT$1 });
|
|
22666
23244
|
win.contentView.removeChildView(chromeView);
|
|
22667
23245
|
win.contentView.addChildView(chromeView);
|
|
22668
23246
|
const activeTab = tabManager.getActiveTab();
|
|
22669
23247
|
if (activeTab) {
|
|
22670
23248
|
activeTab.view.setBounds({
|
|
22671
23249
|
x: 0,
|
|
22672
|
-
y: CHROME_HEIGHT,
|
|
23250
|
+
y: CHROME_HEIGHT$1,
|
|
22673
23251
|
width,
|
|
22674
|
-
height: height - CHROME_HEIGHT
|
|
23252
|
+
height: height - CHROME_HEIGHT$1
|
|
22675
23253
|
});
|
|
22676
23254
|
}
|
|
22677
23255
|
}
|
|
@@ -22683,7 +23261,7 @@ function loadPrivateRenderer(chromeView) {
|
|
|
22683
23261
|
url.searchParams.set("private", "1");
|
|
22684
23262
|
chromeView.webContents.loadURL(url.toString());
|
|
22685
23263
|
} else {
|
|
22686
|
-
chromeView.webContents.loadFile(resolveRendererFile$
|
|
23264
|
+
chromeView.webContents.loadFile(resolveRendererFile$2(), {
|
|
22687
23265
|
query: { view: "chrome", private: "1" }
|
|
22688
23266
|
});
|
|
22689
23267
|
}
|
|
@@ -22772,12 +23350,68 @@ function registerPrivateIpcHandlers(state2) {
|
|
|
22772
23350
|
if (newId) layoutPrivateViews(state2);
|
|
22773
23351
|
return newId;
|
|
22774
23352
|
});
|
|
23353
|
+
ipc.handle(Channels.TAB_PIN, (_e, id) => {
|
|
23354
|
+
tabManager.pinTab(id);
|
|
23355
|
+
});
|
|
23356
|
+
ipc.handle(Channels.TAB_UNPIN, (_e, id) => {
|
|
23357
|
+
tabManager.unpinTab(id);
|
|
23358
|
+
});
|
|
23359
|
+
ipc.handle(Channels.TAB_GROUP_CREATE, (_e, id) => {
|
|
23360
|
+
return tabManager.createGroupFromTab(id);
|
|
23361
|
+
});
|
|
23362
|
+
ipc.handle(Channels.TAB_GROUP_ADD_TAB, (_e, id, groupId) => {
|
|
23363
|
+
tabManager.assignTabToGroup(id, groupId);
|
|
23364
|
+
});
|
|
23365
|
+
ipc.handle(Channels.TAB_GROUP_REMOVE_TAB, (_e, id) => {
|
|
23366
|
+
tabManager.removeTabFromGroup(id);
|
|
23367
|
+
});
|
|
23368
|
+
ipc.handle(Channels.TAB_GROUP_TOGGLE_COLLAPSED, (_e, groupId) => {
|
|
23369
|
+
return tabManager.toggleGroupCollapsed(groupId);
|
|
23370
|
+
});
|
|
23371
|
+
ipc.handle(
|
|
23372
|
+
Channels.TAB_GROUP_SET_COLOR,
|
|
23373
|
+
(_e, groupId, color) => {
|
|
23374
|
+
tabManager.setGroupColor(groupId, color);
|
|
23375
|
+
}
|
|
23376
|
+
);
|
|
23377
|
+
ipc.handle(Channels.TAB_TOGGLE_MUTE, (_e, id) => {
|
|
23378
|
+
return tabManager.toggleMuted(id);
|
|
23379
|
+
});
|
|
23380
|
+
ipc.handle(Channels.TAB_PRINT, (_e, id) => {
|
|
23381
|
+
tabManager.printTab(id);
|
|
23382
|
+
});
|
|
23383
|
+
ipc.handle(Channels.TAB_PRINT_TO_PDF, (_e, id) => {
|
|
23384
|
+
return tabManager.saveTabAsPdf(id);
|
|
23385
|
+
});
|
|
22775
23386
|
ipc.on(Channels.TAB_CONTEXT_MENU, (_e, id) => {
|
|
22776
23387
|
const { Menu, MenuItem } = require("electron");
|
|
23388
|
+
const tab = tabManager.getTab(id);
|
|
23389
|
+
const isPinned = tab?.state.isPinned ?? false;
|
|
23390
|
+
const groupId = tab?.state.groupId;
|
|
23391
|
+
const isMuted = tab?.state.isMuted ?? false;
|
|
23392
|
+
const groups = tabManager.getAllStates().filter((state22) => state22.groupId && state22.groupId !== groupId).reduce(
|
|
23393
|
+
(map, state22) => map.set(state22.groupId, {
|
|
23394
|
+
id: state22.groupId,
|
|
23395
|
+
name: state22.groupName || "Group"
|
|
23396
|
+
}),
|
|
23397
|
+
/* @__PURE__ */ new Map()
|
|
23398
|
+
);
|
|
22777
23399
|
const menu = new Menu();
|
|
22778
23400
|
menu.append(
|
|
22779
23401
|
new MenuItem({
|
|
22780
|
-
label: "
|
|
23402
|
+
label: isPinned ? "Unpin Tab" : "Pin Tab",
|
|
23403
|
+
click: () => {
|
|
23404
|
+
if (isPinned) {
|
|
23405
|
+
tabManager.unpinTab(id);
|
|
23406
|
+
} else {
|
|
23407
|
+
tabManager.pinTab(id);
|
|
23408
|
+
}
|
|
23409
|
+
}
|
|
23410
|
+
})
|
|
23411
|
+
);
|
|
23412
|
+
menu.append(
|
|
23413
|
+
new MenuItem({
|
|
23414
|
+
label: "Duplicate Tab",
|
|
22781
23415
|
click: () => {
|
|
22782
23416
|
const newId = tabManager.duplicateTab(id);
|
|
22783
23417
|
if (newId) layoutPrivateViews(state2);
|
|
@@ -22786,19 +23420,110 @@ function registerPrivateIpcHandlers(state2) {
|
|
|
22786
23420
|
);
|
|
22787
23421
|
menu.append(
|
|
22788
23422
|
new MenuItem({
|
|
22789
|
-
label: "
|
|
23423
|
+
label: "Add to New Group",
|
|
22790
23424
|
click: () => {
|
|
22791
|
-
tabManager.
|
|
22792
|
-
layoutPrivateViews(state2);
|
|
23425
|
+
tabManager.createGroupFromTab(id);
|
|
22793
23426
|
}
|
|
22794
23427
|
})
|
|
22795
23428
|
);
|
|
23429
|
+
if (groups.size > 0) {
|
|
23430
|
+
menu.append(
|
|
23431
|
+
new MenuItem({
|
|
23432
|
+
label: "Add to Group",
|
|
23433
|
+
submenu: [...groups.values()].map(
|
|
23434
|
+
(group) => new MenuItem({
|
|
23435
|
+
label: group.name,
|
|
23436
|
+
click: () => tabManager.assignTabToGroup(id, group.id)
|
|
23437
|
+
})
|
|
23438
|
+
)
|
|
23439
|
+
})
|
|
23440
|
+
);
|
|
23441
|
+
}
|
|
23442
|
+
if (groupId) {
|
|
23443
|
+
menu.append(
|
|
23444
|
+
new MenuItem({
|
|
23445
|
+
label: "Remove from Group",
|
|
23446
|
+
click: () => {
|
|
23447
|
+
tabManager.removeTabFromGroup(id);
|
|
23448
|
+
}
|
|
23449
|
+
})
|
|
23450
|
+
);
|
|
23451
|
+
}
|
|
23452
|
+
menu.append(
|
|
23453
|
+
new MenuItem({
|
|
23454
|
+
label: isMuted ? "Unmute Tab" : "Mute Tab",
|
|
23455
|
+
click: () => {
|
|
23456
|
+
tabManager.toggleMuted(id);
|
|
23457
|
+
}
|
|
23458
|
+
})
|
|
23459
|
+
);
|
|
23460
|
+
menu.append(new MenuItem({ type: "separator" }));
|
|
23461
|
+
menu.append(
|
|
23462
|
+
new MenuItem({
|
|
23463
|
+
label: "Print Page",
|
|
23464
|
+
click: () => {
|
|
23465
|
+
tabManager.printTab(id);
|
|
23466
|
+
}
|
|
23467
|
+
})
|
|
23468
|
+
);
|
|
23469
|
+
menu.append(
|
|
23470
|
+
new MenuItem({
|
|
23471
|
+
label: "Save Page as PDF",
|
|
23472
|
+
click: () => {
|
|
23473
|
+
void tabManager.saveTabAsPdf(id).catch((error) => {
|
|
23474
|
+
logger$5.warn("Failed to save private page as PDF:", error);
|
|
23475
|
+
});
|
|
23476
|
+
}
|
|
23477
|
+
})
|
|
23478
|
+
);
|
|
23479
|
+
if (!isPinned) {
|
|
23480
|
+
menu.append(new MenuItem({ type: "separator" }));
|
|
23481
|
+
menu.append(
|
|
23482
|
+
new MenuItem({
|
|
23483
|
+
label: "Close Tab",
|
|
23484
|
+
click: () => {
|
|
23485
|
+
tabManager.closeTab(id);
|
|
23486
|
+
layoutPrivateViews(state2);
|
|
23487
|
+
}
|
|
23488
|
+
})
|
|
23489
|
+
);
|
|
23490
|
+
}
|
|
23491
|
+
menu.popup({ window: state2.window });
|
|
23492
|
+
});
|
|
23493
|
+
ipc.on(Channels.TAB_GROUP_CONTEXT_MENU, (_e, groupId) => {
|
|
23494
|
+
const { Menu, MenuItem } = require("electron");
|
|
23495
|
+
const firstTab = tabManager.getAllStates().find((tab) => tab.groupId === groupId);
|
|
23496
|
+
if (!firstTab) return;
|
|
23497
|
+
const menu = new Menu();
|
|
23498
|
+
menu.append(
|
|
23499
|
+
new MenuItem({
|
|
23500
|
+
label: firstTab.groupCollapsed ? "Expand Group" : "Collapse Group",
|
|
23501
|
+
click: () => tabManager.toggleGroupCollapsed(groupId)
|
|
23502
|
+
})
|
|
23503
|
+
);
|
|
23504
|
+
menu.append(
|
|
23505
|
+
new MenuItem({
|
|
23506
|
+
label: "Group Color",
|
|
23507
|
+
submenu: TAB_GROUP_COLORS.map(
|
|
23508
|
+
(color) => new MenuItem({
|
|
23509
|
+
label: TAB_GROUP_COLOR_LABELS[color],
|
|
23510
|
+
type: "radio",
|
|
23511
|
+
checked: firstTab.groupColor === color,
|
|
23512
|
+
click: () => tabManager.setGroupColor(groupId, color)
|
|
23513
|
+
})
|
|
23514
|
+
)
|
|
23515
|
+
})
|
|
23516
|
+
);
|
|
22796
23517
|
menu.popup({ window: state2.window });
|
|
22797
23518
|
});
|
|
22798
23519
|
ipc.handle(Channels.IS_PRIVATE_MODE, () => true);
|
|
22799
23520
|
ipc.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
22800
23521
|
createPrivateWindow();
|
|
22801
23522
|
});
|
|
23523
|
+
ipc.handle(Channels.OPEN_NEW_WINDOW, () => {
|
|
23524
|
+
const { createSecondaryWindow: createSecondaryWindow2 } = require("../secondary/window");
|
|
23525
|
+
createSecondaryWindow2();
|
|
23526
|
+
});
|
|
22802
23527
|
ipc.handle(Channels.WINDOW_MINIMIZE, () => {
|
|
22803
23528
|
state2.window.minimize();
|
|
22804
23529
|
});
|
|
@@ -22917,6 +23642,373 @@ function createPrivateWindow() {
|
|
|
22917
23642
|
logger$5.info("Private browsing window opened");
|
|
22918
23643
|
return state2;
|
|
22919
23644
|
}
|
|
23645
|
+
const CHROME_HEIGHT = 110;
|
|
23646
|
+
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
23647
|
+
function resolveRendererFile$1() {
|
|
23648
|
+
const candidates = [
|
|
23649
|
+
path.join(__dirname, "../../out/renderer/index.html"),
|
|
23650
|
+
path.join(__dirname, "../../../out/renderer/index.html")
|
|
23651
|
+
];
|
|
23652
|
+
for (const candidate of candidates) {
|
|
23653
|
+
try {
|
|
23654
|
+
fs.accessSync(candidate);
|
|
23655
|
+
return candidate;
|
|
23656
|
+
} catch {
|
|
23657
|
+
}
|
|
23658
|
+
}
|
|
23659
|
+
return path.join(__dirname, "../../out/renderer/index.html");
|
|
23660
|
+
}
|
|
23661
|
+
function layoutSecondaryViews(state2) {
|
|
23662
|
+
const { window: win, chromeView, tabManager } = state2;
|
|
23663
|
+
const [width, height] = win.getContentSize();
|
|
23664
|
+
chromeView.setBounds({ x: 0, y: 0, width, height: CHROME_HEIGHT });
|
|
23665
|
+
win.contentView.removeChildView(chromeView);
|
|
23666
|
+
win.contentView.addChildView(chromeView);
|
|
23667
|
+
const activeTab = tabManager.getActiveTab();
|
|
23668
|
+
if (activeTab) {
|
|
23669
|
+
activeTab.view.setBounds({
|
|
23670
|
+
x: 0,
|
|
23671
|
+
y: CHROME_HEIGHT,
|
|
23672
|
+
width,
|
|
23673
|
+
height: height - CHROME_HEIGHT
|
|
23674
|
+
});
|
|
23675
|
+
}
|
|
23676
|
+
}
|
|
23677
|
+
function loadSecondaryRenderer(chromeView) {
|
|
23678
|
+
const devUrl = process.env.ELECTRON_RENDERER_URL;
|
|
23679
|
+
if (devUrl) {
|
|
23680
|
+
const url = new URL(devUrl);
|
|
23681
|
+
url.searchParams.set("view", "chrome");
|
|
23682
|
+
url.searchParams.set("secondary", "1");
|
|
23683
|
+
chromeView.webContents.loadURL(url.toString());
|
|
23684
|
+
} else {
|
|
23685
|
+
chromeView.webContents.loadFile(resolveRendererFile$1(), {
|
|
23686
|
+
query: { view: "chrome", secondary: "1" }
|
|
23687
|
+
});
|
|
23688
|
+
}
|
|
23689
|
+
}
|
|
23690
|
+
function showTabContextMenu(state2, id) {
|
|
23691
|
+
const { tabManager } = state2;
|
|
23692
|
+
const tab = tabManager.getTab(id);
|
|
23693
|
+
const isPinned = tab?.state.isPinned ?? false;
|
|
23694
|
+
const groupId = tab?.state.groupId;
|
|
23695
|
+
const isMuted = tab?.state.isMuted ?? false;
|
|
23696
|
+
const groups = tabManager.getAllStates().filter((state22) => state22.groupId && state22.groupId !== groupId).reduce(
|
|
23697
|
+
(map, state22) => map.set(state22.groupId, {
|
|
23698
|
+
id: state22.groupId,
|
|
23699
|
+
name: state22.groupName || "Group"
|
|
23700
|
+
}),
|
|
23701
|
+
/* @__PURE__ */ new Map()
|
|
23702
|
+
);
|
|
23703
|
+
const menu = new electron.Menu();
|
|
23704
|
+
menu.append(
|
|
23705
|
+
new electron.MenuItem({
|
|
23706
|
+
label: isPinned ? "Unpin Tab" : "Pin Tab",
|
|
23707
|
+
click: () => isPinned ? tabManager.unpinTab(id) : tabManager.pinTab(id)
|
|
23708
|
+
})
|
|
23709
|
+
);
|
|
23710
|
+
menu.append(
|
|
23711
|
+
new electron.MenuItem({
|
|
23712
|
+
label: "Duplicate Tab",
|
|
23713
|
+
click: () => {
|
|
23714
|
+
const newId = tabManager.duplicateTab(id);
|
|
23715
|
+
if (newId) layoutSecondaryViews(state2);
|
|
23716
|
+
}
|
|
23717
|
+
})
|
|
23718
|
+
);
|
|
23719
|
+
menu.append(
|
|
23720
|
+
new electron.MenuItem({
|
|
23721
|
+
label: "Add to New Group",
|
|
23722
|
+
click: () => {
|
|
23723
|
+
tabManager.createGroupFromTab(id);
|
|
23724
|
+
}
|
|
23725
|
+
})
|
|
23726
|
+
);
|
|
23727
|
+
if (groups.size > 0) {
|
|
23728
|
+
menu.append(
|
|
23729
|
+
new electron.MenuItem({
|
|
23730
|
+
label: "Add to Group",
|
|
23731
|
+
submenu: [...groups.values()].map(
|
|
23732
|
+
(group) => new electron.MenuItem({
|
|
23733
|
+
label: group.name,
|
|
23734
|
+
click: () => tabManager.assignTabToGroup(id, group.id)
|
|
23735
|
+
})
|
|
23736
|
+
)
|
|
23737
|
+
})
|
|
23738
|
+
);
|
|
23739
|
+
}
|
|
23740
|
+
if (groupId) {
|
|
23741
|
+
menu.append(
|
|
23742
|
+
new electron.MenuItem({
|
|
23743
|
+
label: "Remove from Group",
|
|
23744
|
+
click: () => tabManager.removeTabFromGroup(id)
|
|
23745
|
+
})
|
|
23746
|
+
);
|
|
23747
|
+
}
|
|
23748
|
+
menu.append(
|
|
23749
|
+
new electron.MenuItem({
|
|
23750
|
+
label: isMuted ? "Unmute Tab" : "Mute Tab",
|
|
23751
|
+
click: () => tabManager.toggleMuted(id)
|
|
23752
|
+
})
|
|
23753
|
+
);
|
|
23754
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
23755
|
+
menu.append(
|
|
23756
|
+
new electron.MenuItem({ label: "Print Page", click: () => tabManager.printTab(id) })
|
|
23757
|
+
);
|
|
23758
|
+
menu.append(
|
|
23759
|
+
new electron.MenuItem({
|
|
23760
|
+
label: "Save Page as PDF",
|
|
23761
|
+
click: () => void tabManager.saveTabAsPdf(id)
|
|
23762
|
+
})
|
|
23763
|
+
);
|
|
23764
|
+
if (!isPinned) {
|
|
23765
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
23766
|
+
menu.append(
|
|
23767
|
+
new electron.MenuItem({
|
|
23768
|
+
label: "Close Tab",
|
|
23769
|
+
click: () => {
|
|
23770
|
+
tabManager.closeTab(id);
|
|
23771
|
+
layoutSecondaryViews(state2);
|
|
23772
|
+
}
|
|
23773
|
+
})
|
|
23774
|
+
);
|
|
23775
|
+
}
|
|
23776
|
+
menu.popup({ window: state2.window });
|
|
23777
|
+
}
|
|
23778
|
+
function showGroupContextMenu(state2, groupId) {
|
|
23779
|
+
const { tabManager } = state2;
|
|
23780
|
+
const firstTab = tabManager.getAllStates().find((tab) => tab.groupId === groupId);
|
|
23781
|
+
if (!firstTab) return;
|
|
23782
|
+
const menu = new electron.Menu();
|
|
23783
|
+
menu.append(
|
|
23784
|
+
new electron.MenuItem({
|
|
23785
|
+
label: firstTab.groupCollapsed ? "Expand Group" : "Collapse Group",
|
|
23786
|
+
click: () => tabManager.toggleGroupCollapsed(groupId)
|
|
23787
|
+
})
|
|
23788
|
+
);
|
|
23789
|
+
menu.append(
|
|
23790
|
+
new electron.MenuItem({
|
|
23791
|
+
label: "Group Color",
|
|
23792
|
+
submenu: TAB_GROUP_COLORS.map(
|
|
23793
|
+
(color) => new electron.MenuItem({
|
|
23794
|
+
label: TAB_GROUP_COLOR_LABELS[color],
|
|
23795
|
+
type: "radio",
|
|
23796
|
+
checked: firstTab.groupColor === color,
|
|
23797
|
+
click: () => tabManager.setGroupColor(groupId, color)
|
|
23798
|
+
})
|
|
23799
|
+
)
|
|
23800
|
+
})
|
|
23801
|
+
);
|
|
23802
|
+
menu.popup({ window: state2.window });
|
|
23803
|
+
}
|
|
23804
|
+
function registerSecondaryIpcHandlers(state2) {
|
|
23805
|
+
const { chromeView, tabManager } = state2;
|
|
23806
|
+
const ipc = chromeView.webContents.ipc;
|
|
23807
|
+
let findResultListener = null;
|
|
23808
|
+
let findWiredWcId = null;
|
|
23809
|
+
const wireFindEvents = (wc) => {
|
|
23810
|
+
if (findWiredWcId === wc.id && findResultListener) return;
|
|
23811
|
+
if (findWiredWcId && findResultListener) {
|
|
23812
|
+
const previous = tabManager.findTabByWebContentsId(findWiredWcId);
|
|
23813
|
+
previous?.view.webContents.removeListener(
|
|
23814
|
+
"found-in-page",
|
|
23815
|
+
findResultListener
|
|
23816
|
+
);
|
|
23817
|
+
}
|
|
23818
|
+
findWiredWcId = wc.id;
|
|
23819
|
+
if (wc.isDestroyed()) return;
|
|
23820
|
+
const listener = (_event, result) => {
|
|
23821
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
23822
|
+
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
23823
|
+
}
|
|
23824
|
+
};
|
|
23825
|
+
findResultListener = listener;
|
|
23826
|
+
wc.on("found-in-page", listener);
|
|
23827
|
+
const capturedWcId = wc.id;
|
|
23828
|
+
wc.once("destroyed", () => {
|
|
23829
|
+
if (findWiredWcId === capturedWcId) {
|
|
23830
|
+
findWiredWcId = null;
|
|
23831
|
+
findResultListener = null;
|
|
23832
|
+
}
|
|
23833
|
+
});
|
|
23834
|
+
};
|
|
23835
|
+
ipc.handle(Channels.TAB_CREATE, (_e, url) => {
|
|
23836
|
+
return tabManager.createTab(url || loadSettings().defaultUrl);
|
|
23837
|
+
});
|
|
23838
|
+
ipc.handle(Channels.TAB_CLOSE, (_e, id) => {
|
|
23839
|
+
tabManager.closeTab(id);
|
|
23840
|
+
layoutSecondaryViews(state2);
|
|
23841
|
+
});
|
|
23842
|
+
ipc.handle(Channels.TAB_SWITCH, (_e, id) => {
|
|
23843
|
+
tabManager.switchTab(id);
|
|
23844
|
+
layoutSecondaryViews(state2);
|
|
23845
|
+
});
|
|
23846
|
+
ipc.handle(Channels.TAB_NAVIGATE, (_e, id, url) => {
|
|
23847
|
+
return tabManager.navigateTab(id, url);
|
|
23848
|
+
});
|
|
23849
|
+
ipc.handle(Channels.TAB_BACK, (_e, id) => tabManager.goBack(id));
|
|
23850
|
+
ipc.handle(Channels.TAB_FORWARD, (_e, id) => tabManager.goForward(id));
|
|
23851
|
+
ipc.handle(Channels.TAB_RELOAD, (_e, id) => tabManager.reloadTab(id));
|
|
23852
|
+
ipc.handle(Channels.TAB_TOGGLE_AD_BLOCK, (_e, id) => {
|
|
23853
|
+
const tab = tabManager.getTab(id);
|
|
23854
|
+
if (!tab) return null;
|
|
23855
|
+
const enabled = !tab.state.adBlockingEnabled;
|
|
23856
|
+
tab.setAdBlockingEnabled(enabled);
|
|
23857
|
+
return enabled;
|
|
23858
|
+
});
|
|
23859
|
+
ipc.handle(Channels.TAB_ZOOM_IN, (_e, id) => tabManager.zoomIn(id));
|
|
23860
|
+
ipc.handle(Channels.TAB_ZOOM_OUT, (_e, id) => tabManager.zoomOut(id));
|
|
23861
|
+
ipc.handle(Channels.TAB_ZOOM_RESET, (_e, id) => tabManager.zoomReset(id));
|
|
23862
|
+
ipc.handle(Channels.TAB_REOPEN_CLOSED, () => {
|
|
23863
|
+
const id = tabManager.reopenClosedTab();
|
|
23864
|
+
if (id) layoutSecondaryViews(state2);
|
|
23865
|
+
return id;
|
|
23866
|
+
});
|
|
23867
|
+
ipc.handle(Channels.TAB_DUPLICATE, (_e, id) => {
|
|
23868
|
+
const newId = tabManager.duplicateTab(id);
|
|
23869
|
+
if (newId) layoutSecondaryViews(state2);
|
|
23870
|
+
return newId;
|
|
23871
|
+
});
|
|
23872
|
+
ipc.handle(Channels.TAB_PIN, (_e, id) => tabManager.pinTab(id));
|
|
23873
|
+
ipc.handle(Channels.TAB_UNPIN, (_e, id) => tabManager.unpinTab(id));
|
|
23874
|
+
ipc.handle(
|
|
23875
|
+
Channels.TAB_GROUP_CREATE,
|
|
23876
|
+
(_e, id) => tabManager.createGroupFromTab(id)
|
|
23877
|
+
);
|
|
23878
|
+
ipc.handle(
|
|
23879
|
+
Channels.TAB_GROUP_ADD_TAB,
|
|
23880
|
+
(_e, id, groupId) => tabManager.assignTabToGroup(id, groupId)
|
|
23881
|
+
);
|
|
23882
|
+
ipc.handle(
|
|
23883
|
+
Channels.TAB_GROUP_REMOVE_TAB,
|
|
23884
|
+
(_e, id) => tabManager.removeTabFromGroup(id)
|
|
23885
|
+
);
|
|
23886
|
+
ipc.handle(
|
|
23887
|
+
Channels.TAB_GROUP_TOGGLE_COLLAPSED,
|
|
23888
|
+
(_e, groupId) => tabManager.toggleGroupCollapsed(groupId)
|
|
23889
|
+
);
|
|
23890
|
+
ipc.handle(
|
|
23891
|
+
Channels.TAB_GROUP_SET_COLOR,
|
|
23892
|
+
(_e, groupId, color) => tabManager.setGroupColor(groupId, color)
|
|
23893
|
+
);
|
|
23894
|
+
ipc.handle(
|
|
23895
|
+
Channels.TAB_TOGGLE_MUTE,
|
|
23896
|
+
(_e, id) => tabManager.toggleMuted(id)
|
|
23897
|
+
);
|
|
23898
|
+
ipc.handle(Channels.TAB_PRINT, (_e, id) => tabManager.printTab(id));
|
|
23899
|
+
ipc.handle(
|
|
23900
|
+
Channels.TAB_PRINT_TO_PDF,
|
|
23901
|
+
(_e, id) => tabManager.saveTabAsPdf(id)
|
|
23902
|
+
);
|
|
23903
|
+
ipc.handle(Channels.TAB_STATE_GET, () => ({
|
|
23904
|
+
tabs: tabManager.getAllStates(),
|
|
23905
|
+
activeId: tabManager.getActiveTabId() || ""
|
|
23906
|
+
}));
|
|
23907
|
+
ipc.on(
|
|
23908
|
+
Channels.TAB_CONTEXT_MENU,
|
|
23909
|
+
(_e, id) => showTabContextMenu(state2, id)
|
|
23910
|
+
);
|
|
23911
|
+
ipc.on(
|
|
23912
|
+
Channels.TAB_GROUP_CONTEXT_MENU,
|
|
23913
|
+
(_e, groupId) => showGroupContextMenu(state2, groupId)
|
|
23914
|
+
);
|
|
23915
|
+
ipc.handle(Channels.OPEN_NEW_WINDOW, () => createSecondaryWindow());
|
|
23916
|
+
ipc.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
23917
|
+
const { createPrivateWindow: createPrivateWindow2 } = require("../private/window");
|
|
23918
|
+
createPrivateWindow2();
|
|
23919
|
+
});
|
|
23920
|
+
ipc.handle(Channels.IS_PRIVATE_MODE, () => false);
|
|
23921
|
+
ipc.handle(Channels.WINDOW_MINIMIZE, () => state2.window.minimize());
|
|
23922
|
+
ipc.handle(Channels.WINDOW_MAXIMIZE, () => {
|
|
23923
|
+
if (state2.window.isMaximized()) state2.window.unmaximize();
|
|
23924
|
+
else state2.window.maximize();
|
|
23925
|
+
});
|
|
23926
|
+
ipc.handle(Channels.WINDOW_CLOSE, () => state2.window.close());
|
|
23927
|
+
ipc.handle(Channels.SETTINGS_VISIBILITY, () => false);
|
|
23928
|
+
ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
|
|
23929
|
+
ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
|
|
23930
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => ({ open: false }));
|
|
23931
|
+
ipc.handle(
|
|
23932
|
+
Channels.FIND_IN_PAGE_START,
|
|
23933
|
+
(_e, text, options) => {
|
|
23934
|
+
const tab = tabManager.getActiveTab();
|
|
23935
|
+
if (!tab) return null;
|
|
23936
|
+
const wc = tab.view.webContents;
|
|
23937
|
+
if (wc.isDestroyed()) return null;
|
|
23938
|
+
wireFindEvents(wc);
|
|
23939
|
+
return wc.findInPage(text, {
|
|
23940
|
+
forward: options?.forward ?? true,
|
|
23941
|
+
findNext: options?.findNext ?? false
|
|
23942
|
+
});
|
|
23943
|
+
}
|
|
23944
|
+
);
|
|
23945
|
+
ipc.handle(Channels.FIND_IN_PAGE_NEXT, (_e, forward) => {
|
|
23946
|
+
const tab = tabManager.getActiveTab();
|
|
23947
|
+
if (!tab) return null;
|
|
23948
|
+
const wc = tab.view.webContents;
|
|
23949
|
+
if (wc.isDestroyed()) return null;
|
|
23950
|
+
wireFindEvents(wc);
|
|
23951
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
23952
|
+
});
|
|
23953
|
+
ipc.handle(
|
|
23954
|
+
Channels.FIND_IN_PAGE_STOP,
|
|
23955
|
+
(_e, action) => {
|
|
23956
|
+
const tab = tabManager.getActiveTab();
|
|
23957
|
+
if (!tab) return;
|
|
23958
|
+
const wc = tab.view.webContents;
|
|
23959
|
+
if (wc.isDestroyed()) return;
|
|
23960
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
23961
|
+
}
|
|
23962
|
+
);
|
|
23963
|
+
}
|
|
23964
|
+
function createSecondaryWindow() {
|
|
23965
|
+
const win = new electron.BaseWindow({
|
|
23966
|
+
width: 1280,
|
|
23967
|
+
height: 800,
|
|
23968
|
+
minWidth: 800,
|
|
23969
|
+
minHeight: 600,
|
|
23970
|
+
frame: false,
|
|
23971
|
+
show: false,
|
|
23972
|
+
backgroundColor: "#1a1a1e",
|
|
23973
|
+
title: "Vessel"
|
|
23974
|
+
});
|
|
23975
|
+
const chromeView = new electron.WebContentsView({
|
|
23976
|
+
webPreferences: {
|
|
23977
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
23978
|
+
sandbox: true,
|
|
23979
|
+
contextIsolation: true,
|
|
23980
|
+
nodeIntegration: false
|
|
23981
|
+
}
|
|
23982
|
+
});
|
|
23983
|
+
chromeView.setBackgroundColor("#00000000");
|
|
23984
|
+
win.contentView.addChildView(chromeView);
|
|
23985
|
+
const tabManager = new TabManager(win, (tabs, activeId) => {
|
|
23986
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
23987
|
+
chromeView.webContents.send(Channels.TAB_STATE_UPDATE, tabs, activeId);
|
|
23988
|
+
}
|
|
23989
|
+
layoutSecondaryViews(state2);
|
|
23990
|
+
});
|
|
23991
|
+
const state2 = { window: win, chromeView, tabManager };
|
|
23992
|
+
installAdBlocking(tabManager);
|
|
23993
|
+
installDownloadHandler(chromeView);
|
|
23994
|
+
registerSecondaryIpcHandlers(state2);
|
|
23995
|
+
win.on("resize", () => layoutSecondaryViews(state2));
|
|
23996
|
+
win.on("show", () => layoutSecondaryViews(state2));
|
|
23997
|
+
win.on("closed", () => {
|
|
23998
|
+
secondaryWindows.delete(state2);
|
|
23999
|
+
unregisterAdBlockingTabManager(tabManager);
|
|
24000
|
+
unregisterDownloadHandler(chromeView);
|
|
24001
|
+
tabManager.destroyAllTabs();
|
|
24002
|
+
});
|
|
24003
|
+
secondaryWindows.add(state2);
|
|
24004
|
+
chromeView.webContents.once("dom-ready", () => {
|
|
24005
|
+
tabManager.createTab(loadSettings().defaultUrl);
|
|
24006
|
+
layoutSecondaryViews(state2);
|
|
24007
|
+
});
|
|
24008
|
+
loadSecondaryRenderer(chromeView);
|
|
24009
|
+
win.show();
|
|
24010
|
+
return state2;
|
|
24011
|
+
}
|
|
22920
24012
|
let activeChatProvider = null;
|
|
22921
24013
|
const logger$4 = createLogger("IPC");
|
|
22922
24014
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
@@ -22925,6 +24017,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22925
24017
|
electron.ipcMain.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
22926
24018
|
createPrivateWindow();
|
|
22927
24019
|
});
|
|
24020
|
+
electron.ipcMain.handle(Channels.OPEN_NEW_WINDOW, () => {
|
|
24021
|
+
createSecondaryWindow();
|
|
24022
|
+
});
|
|
22928
24023
|
electron.ipcMain.handle(Channels.IS_PRIVATE_MODE, () => false);
|
|
22929
24024
|
let sidebarResizeRecoveryTimer = null;
|
|
22930
24025
|
let sidebarResizeActive = false;
|
|
@@ -23136,9 +24231,77 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23136
24231
|
if (newId) layoutViews(windowState);
|
|
23137
24232
|
return newId;
|
|
23138
24233
|
});
|
|
24234
|
+
electron.ipcMain.handle(Channels.TAB_PIN, (_, id) => {
|
|
24235
|
+
assertString(id, "id");
|
|
24236
|
+
tabManager.pinTab(id);
|
|
24237
|
+
});
|
|
24238
|
+
electron.ipcMain.handle(Channels.TAB_UNPIN, (_, id) => {
|
|
24239
|
+
assertString(id, "id");
|
|
24240
|
+
tabManager.unpinTab(id);
|
|
24241
|
+
});
|
|
24242
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_CREATE, (_, id) => {
|
|
24243
|
+
assertString(id, "id");
|
|
24244
|
+
return tabManager.createGroupFromTab(id);
|
|
24245
|
+
});
|
|
24246
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_ADD_TAB, (_, id, groupId) => {
|
|
24247
|
+
assertString(id, "id");
|
|
24248
|
+
assertString(groupId, "groupId");
|
|
24249
|
+
tabManager.assignTabToGroup(id, groupId);
|
|
24250
|
+
});
|
|
24251
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_REMOVE_TAB, (_, id) => {
|
|
24252
|
+
assertString(id, "id");
|
|
24253
|
+
tabManager.removeTabFromGroup(id);
|
|
24254
|
+
});
|
|
24255
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_TOGGLE_COLLAPSED, (_, groupId) => {
|
|
24256
|
+
assertString(groupId, "groupId");
|
|
24257
|
+
return tabManager.toggleGroupCollapsed(groupId);
|
|
24258
|
+
});
|
|
24259
|
+
electron.ipcMain.handle(
|
|
24260
|
+
Channels.TAB_GROUP_SET_COLOR,
|
|
24261
|
+
(_, groupId, color) => {
|
|
24262
|
+
assertString(groupId, "groupId");
|
|
24263
|
+
assertString(color, "color");
|
|
24264
|
+
tabManager.setGroupColor(groupId, color);
|
|
24265
|
+
}
|
|
24266
|
+
);
|
|
24267
|
+
electron.ipcMain.handle(Channels.TAB_TOGGLE_MUTE, (_, id) => {
|
|
24268
|
+
assertString(id, "id");
|
|
24269
|
+
return tabManager.toggleMuted(id);
|
|
24270
|
+
});
|
|
24271
|
+
electron.ipcMain.handle(Channels.TAB_PRINT, (_, id) => {
|
|
24272
|
+
assertString(id, "id");
|
|
24273
|
+
tabManager.printTab(id);
|
|
24274
|
+
});
|
|
24275
|
+
electron.ipcMain.handle(Channels.TAB_PRINT_TO_PDF, (_, id) => {
|
|
24276
|
+
assertString(id, "id");
|
|
24277
|
+
return tabManager.saveTabAsPdf(id);
|
|
24278
|
+
});
|
|
23139
24279
|
electron.ipcMain.on(Channels.TAB_CONTEXT_MENU, (_event, id) => {
|
|
23140
24280
|
assertString(id, "id");
|
|
24281
|
+
const tab = tabManager.getTab(id);
|
|
24282
|
+
const isPinned = tab?.state.isPinned ?? false;
|
|
24283
|
+
const groupId = tab?.state.groupId;
|
|
24284
|
+
const isMuted = tab?.state.isMuted ?? false;
|
|
24285
|
+
const groups = tabManager.getAllStates().filter((state2) => state2.groupId && state2.groupId !== groupId).reduce(
|
|
24286
|
+
(map, state2) => map.set(state2.groupId, {
|
|
24287
|
+
id: state2.groupId,
|
|
24288
|
+
name: state2.groupName || "Group"
|
|
24289
|
+
}),
|
|
24290
|
+
/* @__PURE__ */ new Map()
|
|
24291
|
+
);
|
|
23141
24292
|
const menu = new electron.Menu();
|
|
24293
|
+
menu.append(
|
|
24294
|
+
new electron.MenuItem({
|
|
24295
|
+
label: isPinned ? "Unpin Tab" : "Pin Tab",
|
|
24296
|
+
click: () => {
|
|
24297
|
+
if (isPinned) {
|
|
24298
|
+
tabManager.unpinTab(id);
|
|
24299
|
+
} else {
|
|
24300
|
+
tabManager.pinTab(id);
|
|
24301
|
+
}
|
|
24302
|
+
}
|
|
24303
|
+
})
|
|
24304
|
+
);
|
|
23142
24305
|
menu.append(
|
|
23143
24306
|
new electron.MenuItem({
|
|
23144
24307
|
label: "Duplicate Tab",
|
|
@@ -23150,13 +24313,102 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23150
24313
|
);
|
|
23151
24314
|
menu.append(
|
|
23152
24315
|
new electron.MenuItem({
|
|
23153
|
-
label: "
|
|
24316
|
+
label: "Add to New Group",
|
|
23154
24317
|
click: () => {
|
|
23155
|
-
tabManager.
|
|
23156
|
-
layoutViews(windowState);
|
|
24318
|
+
tabManager.createGroupFromTab(id);
|
|
23157
24319
|
}
|
|
23158
24320
|
})
|
|
23159
24321
|
);
|
|
24322
|
+
if (groups.size > 0) {
|
|
24323
|
+
menu.append(
|
|
24324
|
+
new electron.MenuItem({
|
|
24325
|
+
label: "Add to Group",
|
|
24326
|
+
submenu: [...groups.values()].map(
|
|
24327
|
+
(group) => new electron.MenuItem({
|
|
24328
|
+
label: group.name,
|
|
24329
|
+
click: () => tabManager.assignTabToGroup(id, group.id)
|
|
24330
|
+
})
|
|
24331
|
+
)
|
|
24332
|
+
})
|
|
24333
|
+
);
|
|
24334
|
+
}
|
|
24335
|
+
if (groupId) {
|
|
24336
|
+
menu.append(
|
|
24337
|
+
new electron.MenuItem({
|
|
24338
|
+
label: "Remove from Group",
|
|
24339
|
+
click: () => {
|
|
24340
|
+
tabManager.removeTabFromGroup(id);
|
|
24341
|
+
}
|
|
24342
|
+
})
|
|
24343
|
+
);
|
|
24344
|
+
}
|
|
24345
|
+
menu.append(
|
|
24346
|
+
new electron.MenuItem({
|
|
24347
|
+
label: isMuted ? "Unmute Tab" : "Mute Tab",
|
|
24348
|
+
click: () => {
|
|
24349
|
+
tabManager.toggleMuted(id);
|
|
24350
|
+
}
|
|
24351
|
+
})
|
|
24352
|
+
);
|
|
24353
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
24354
|
+
menu.append(
|
|
24355
|
+
new electron.MenuItem({
|
|
24356
|
+
label: "Print Page",
|
|
24357
|
+
click: () => {
|
|
24358
|
+
tabManager.printTab(id);
|
|
24359
|
+
}
|
|
24360
|
+
})
|
|
24361
|
+
);
|
|
24362
|
+
menu.append(
|
|
24363
|
+
new electron.MenuItem({
|
|
24364
|
+
label: "Save Page as PDF",
|
|
24365
|
+
click: () => {
|
|
24366
|
+
void tabManager.saveTabAsPdf(id).catch((error) => {
|
|
24367
|
+
logger$4.warn("Failed to save page as PDF:", error);
|
|
24368
|
+
});
|
|
24369
|
+
}
|
|
24370
|
+
})
|
|
24371
|
+
);
|
|
24372
|
+
if (!isPinned) {
|
|
24373
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
24374
|
+
menu.append(
|
|
24375
|
+
new electron.MenuItem({
|
|
24376
|
+
label: "Close Tab",
|
|
24377
|
+
click: () => {
|
|
24378
|
+
tabManager.closeTab(id);
|
|
24379
|
+
layoutViews(windowState);
|
|
24380
|
+
}
|
|
24381
|
+
})
|
|
24382
|
+
);
|
|
24383
|
+
}
|
|
24384
|
+
menu.popup({ window: mainWindow });
|
|
24385
|
+
});
|
|
24386
|
+
electron.ipcMain.on(Channels.TAB_GROUP_CONTEXT_MENU, (_event, groupId) => {
|
|
24387
|
+
assertString(groupId, "groupId");
|
|
24388
|
+
const firstTab = tabManager.getAllStates().find((tab) => tab.groupId === groupId);
|
|
24389
|
+
if (!firstTab) return;
|
|
24390
|
+
const menu = new electron.Menu();
|
|
24391
|
+
menu.append(
|
|
24392
|
+
new electron.MenuItem({
|
|
24393
|
+
label: firstTab.groupCollapsed ? "Expand Group" : "Collapse Group",
|
|
24394
|
+
click: () => {
|
|
24395
|
+
tabManager.toggleGroupCollapsed(groupId);
|
|
24396
|
+
}
|
|
24397
|
+
})
|
|
24398
|
+
);
|
|
24399
|
+
menu.append(
|
|
24400
|
+
new electron.MenuItem({
|
|
24401
|
+
label: "Group Color",
|
|
24402
|
+
submenu: TAB_GROUP_COLORS.map(
|
|
24403
|
+
(color) => new electron.MenuItem({
|
|
24404
|
+
label: TAB_GROUP_COLOR_LABELS[color],
|
|
24405
|
+
type: "radio",
|
|
24406
|
+
checked: firstTab.groupColor === color,
|
|
24407
|
+
click: () => tabManager.setGroupColor(groupId, color)
|
|
24408
|
+
})
|
|
24409
|
+
)
|
|
24410
|
+
})
|
|
24411
|
+
);
|
|
23160
24412
|
menu.popup({ window: mainWindow });
|
|
23161
24413
|
});
|
|
23162
24414
|
electron.ipcMain.handle(Channels.TAB_STATE_GET, () => ({
|
|
@@ -23423,6 +24675,41 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23423
24675
|
trackBookmarkAction("remove");
|
|
23424
24676
|
return removeBookmark(id);
|
|
23425
24677
|
});
|
|
24678
|
+
electron.ipcMain.handle(
|
|
24679
|
+
Channels.BOOKMARKS_EXPORT_HTML,
|
|
24680
|
+
async (_, options) => {
|
|
24681
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
24682
|
+
title: "Export Bookmarks",
|
|
24683
|
+
defaultPath: "vessel-bookmarks.html",
|
|
24684
|
+
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
24685
|
+
});
|
|
24686
|
+
if (canceled || !filePath) return null;
|
|
24687
|
+
const content = exportBookmarksHtml({
|
|
24688
|
+
includeNotes: options?.includeNotes ?? false
|
|
24689
|
+
});
|
|
24690
|
+
await fs.promises.writeFile(filePath, content, "utf-8");
|
|
24691
|
+
trackBookmarkAction("export");
|
|
24692
|
+
return {
|
|
24693
|
+
filePath,
|
|
24694
|
+
count: getState().bookmarks.length
|
|
24695
|
+
};
|
|
24696
|
+
}
|
|
24697
|
+
);
|
|
24698
|
+
electron.ipcMain.handle(Channels.BOOKMARKS_EXPORT_JSON, async () => {
|
|
24699
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
24700
|
+
title: "Export Vessel Bookmark Archive",
|
|
24701
|
+
defaultPath: "vessel-bookmarks.json",
|
|
24702
|
+
filters: [{ name: "Vessel Bookmark Archive", extensions: ["json"] }]
|
|
24703
|
+
});
|
|
24704
|
+
if (canceled || !filePath) return null;
|
|
24705
|
+
const content = exportBookmarksJson();
|
|
24706
|
+
await fs.promises.writeFile(filePath, content, "utf-8");
|
|
24707
|
+
trackBookmarkAction("export");
|
|
24708
|
+
return {
|
|
24709
|
+
filePath,
|
|
24710
|
+
count: getState().bookmarks.length
|
|
24711
|
+
};
|
|
24712
|
+
});
|
|
23426
24713
|
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (_, id, deleteContents) => {
|
|
23427
24714
|
trackBookmarkAction("folder_remove");
|
|
23428
24715
|
return removeFolder(id, deleteContents ?? false);
|
|
@@ -24691,6 +25978,16 @@ function setupAppMenu(handlers) {
|
|
|
24691
25978
|
{
|
|
24692
25979
|
label: "File",
|
|
24693
25980
|
submenu: [
|
|
25981
|
+
{
|
|
25982
|
+
label: "New Window",
|
|
25983
|
+
accelerator: "CommandOrControl+N",
|
|
25984
|
+
click: handlers.newWindow
|
|
25985
|
+
},
|
|
25986
|
+
{
|
|
25987
|
+
label: "Save Page As...",
|
|
25988
|
+
accelerator: "CommandOrControl+S",
|
|
25989
|
+
click: handlers.savePageAs
|
|
25990
|
+
},
|
|
24694
25991
|
{
|
|
24695
25992
|
label: "Reopen Closed Tab",
|
|
24696
25993
|
accelerator: "CommandOrControl+Shift+T",
|
|
@@ -24727,6 +26024,12 @@ function setupAppMenu(handlers) {
|
|
|
24727
26024
|
label: "Actual Size",
|
|
24728
26025
|
accelerator: "CommandOrControl+0",
|
|
24729
26026
|
click: handlers.zoomReset
|
|
26027
|
+
},
|
|
26028
|
+
{ type: "separator" },
|
|
26029
|
+
{
|
|
26030
|
+
label: "View Page Source",
|
|
26031
|
+
accelerator: "CommandOrControl+U",
|
|
26032
|
+
click: handlers.viewPageSource
|
|
24730
26033
|
}
|
|
24731
26034
|
]
|
|
24732
26035
|
}
|
|
@@ -25088,6 +26391,9 @@ async function bootstrap() {
|
|
|
25088
26391
|
registerIpcHandlers(windowState, runtime);
|
|
25089
26392
|
registerHighlightShortcut(windowState.mainWindow, tabManager);
|
|
25090
26393
|
setupAppMenu({
|
|
26394
|
+
newWindow: () => {
|
|
26395
|
+
createSecondaryWindow();
|
|
26396
|
+
},
|
|
25091
26397
|
reopenClosedTab: () => {
|
|
25092
26398
|
const id = tabManager.reopenClosedTab();
|
|
25093
26399
|
if (id) layoutViews(windowState);
|
|
@@ -25103,6 +26409,16 @@ async function bootstrap() {
|
|
|
25103
26409
|
zoomReset: () => {
|
|
25104
26410
|
const id = tabManager.getActiveTabId();
|
|
25105
26411
|
if (id) tabManager.zoomReset(id);
|
|
26412
|
+
},
|
|
26413
|
+
viewPageSource: () => {
|
|
26414
|
+
const activeTab = tabManager.getActiveTab();
|
|
26415
|
+
if (activeTab) activeTab.viewSource();
|
|
26416
|
+
},
|
|
26417
|
+
savePageAs: () => {
|
|
26418
|
+
const activeTabId = tabManager.getActiveTabId();
|
|
26419
|
+
if (activeTabId) {
|
|
26420
|
+
void tabManager.savePage(activeTabId);
|
|
26421
|
+
}
|
|
25106
26422
|
}
|
|
25107
26423
|
});
|
|
25108
26424
|
subscribe((state2) => {
|