Skip to content

Commit 9310b7a

Browse files
Merge pull request #48 from Ankesh2004/feature-tabs
Feature tabs
2 parents 8152eb7 + 4690949 commit 9310b7a

38 files changed

+4811
-11396
lines changed

package-lock.json

Lines changed: 0 additions & 11025 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
},
154154
"devDependencies": {
155155
"electron": "^29.0.1",
156-
"electron-builder": "^24.12.0"
156+
"electron-builder": "^24.12.0",
157+
"electron-rebuild": "^3.2.9"
157158
}
158159
}

src/actions.js

Lines changed: 196 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,42 @@ export function createActions(windowManager) {
1616
label: "New Window",
1717
accelerator: "CommandOrControl+N",
1818
click: () => {
19-
windowManager.open();
19+
const newWin = windowManager.open({ newWindow: true });
20+
if (newWin && newWin.webContents) {
21+
newWin.webContents.once('did-finish-load', () => {
22+
newWin.webContents.executeJavaScript(`
23+
setTimeout(() => {
24+
const urlInput = document.getElementById('url');
25+
if (urlInput) {
26+
urlInput.focus();
27+
urlInput.select();
28+
}
29+
}, 400);
30+
`);
31+
});
32+
}
33+
},
34+
},
35+
NewTab: {
36+
label: "New Tab",
37+
accelerator: "CommandOrControl+T",
38+
click: (focusedWindow) => {
39+
if (focusedWindow) {
40+
focusedWindow.webContents.executeJavaScript(`{
41+
const tabBar = document.querySelector('#tabbar');
42+
if (tabBar && typeof tabBar.addTab === 'function') {
43+
tabBar.addTab();
44+
// Ensure URL bar gets focus after tab creation
45+
setTimeout(() => {
46+
const urlInput = document.getElementById('url');
47+
if (urlInput) {
48+
urlInput.focus();
49+
urlInput.select();
50+
}
51+
}, 150);
52+
}
53+
}`);
54+
}
2055
},
2156
},
2257
Forward: {
@@ -25,9 +60,15 @@ export function createActions(windowManager) {
2560
click: (focusedWindow) => {
2661
if (focusedWindow) {
2762
focusedWindow.webContents.executeJavaScript(`{
28-
const webview = document.querySelector('webview');
29-
if (webview && webview.canGoForward()) {
30-
webview.goForward();
63+
const tabBar = document.querySelector('#tabbar');
64+
if (tabBar && typeof tabBar.goForwardActiveTab === 'function') {
65+
tabBar.goForwardActiveTab();
66+
} else {
67+
// Fallback for single webview
68+
const webview = document.querySelector('webview');
69+
if (webview && webview.canGoForward()) {
70+
webview.goForward();
71+
}
3172
}
3273
}`);
3374
}
@@ -39,9 +80,15 @@ export function createActions(windowManager) {
3980
click: (focusedWindow) => {
4081
if (focusedWindow) {
4182
focusedWindow.webContents.executeJavaScript(`{
42-
const webview = document.querySelector('webview');
43-
if (webview && webview.canGoBack()) {
44-
webview.goBack();
83+
const tabBar = document.querySelector('#tabbar');
84+
if (tabBar && typeof tabBar.goBackActiveTab === 'function') {
85+
tabBar.goBackActiveTab();
86+
} else {
87+
// Fallback for single webview
88+
const webview = document.querySelector('webview');
89+
if (webview && webview.canGoBack()) {
90+
webview.goBack();
91+
}
4592
}
4693
}`);
4794
}
@@ -53,7 +100,28 @@ export function createActions(windowManager) {
53100
click: (focusedWindow) => {
54101
if (focusedWindow) {
55102
focusedWindow.webContents.executeJavaScript(`
56-
document.getElementById('url').focus();
103+
function focusUrlBar() {
104+
const urlInput = document.getElementById('url');
105+
if (urlInput) {
106+
urlInput.focus();
107+
urlInput.select();
108+
return true;
109+
}
110+
return false;
111+
}
112+
113+
// Try to focus immediately
114+
if (!focusUrlBar()) {
115+
// If not found, wait a bit for DOM to be ready
116+
setTimeout(() => {
117+
if (!focusUrlBar()) {
118+
// Last resort: wait longer for new tab/window initialization
119+
setTimeout(() => {
120+
focusUrlBar();
121+
}, 200);
122+
}
123+
}, 50);
124+
}
57125
`);
58126
}
59127
},
@@ -64,11 +132,37 @@ export function createActions(windowManager) {
64132
click: (focusedWindow) => {
65133
if (focusedWindow) {
66134
focusedWindow.webContents.executeJavaScript(`{
67-
const webview = document.querySelector('webview');
68-
if (webview) {
69-
webview.reload();
135+
const tabBar = document.querySelector('#tabbar');
136+
if (tabBar && typeof tabBar.reloadActiveTab === 'function' && tabBar.activeTabId) {
137+
// Only use tab-based reload if we have an active tab
138+
console.log('Reloading active tab:', tabBar.activeTabId);
139+
tabBar.reloadActiveTab();
140+
} else {
141+
// Fallback: find the currently visible webview only
142+
const webviews = document.querySelectorAll('webview');
143+
let activeWebview = null;
144+
145+
// Find the visible webview (not hidden)
146+
for (const webview of webviews) {
147+
if (webview.style.display !== 'none' && webview.offsetParent !== null) {
148+
activeWebview = webview;
149+
break;
150+
}
151+
}
152+
153+
if (activeWebview) {
154+
console.log('Reloading visible webview:', activeWebview.src);
155+
activeWebview.reload();
156+
} else {
157+
// Last resort: reload the first webview
158+
const firstWebview = document.querySelector('webview');
159+
if (firstWebview) {
160+
console.log('Reloading first webview as fallback');
161+
firstWebview.reload();
162+
}
70163
}
71-
}`);
164+
}
165+
}`);
72166
}
73167
},
74168
},
@@ -86,7 +180,24 @@ export function createActions(windowManager) {
86180
accelerator: "CommandOrControl+W",
87181
click: (focusedWindow) => {
88182
if (focusedWindow) {
89-
focusedWindow.close();
183+
focusedWindow.webContents.executeJavaScript(`
184+
try {
185+
const tabBar = document.querySelector('#tabbar');
186+
if (tabBar && tabBar.tabs && tabBar.tabs.length > 1 && typeof tabBar.closeTab === 'function' && typeof tabBar.getActiveTab === 'function') {
187+
const activeTab = tabBar.getActiveTab();
188+
if (activeTab && activeTab.id) {
189+
tabBar.closeTab(activeTab.id);
190+
}
191+
} else {
192+
window.close();
193+
}
194+
} catch (error) {
195+
console.error('Error in Close action:', error);
196+
window.close();
197+
}
198+
`).catch(error => {
199+
console.error('Script execution failed in Close action:', error);
200+
});
90201
}
91202
},
92203
},
@@ -119,6 +230,77 @@ export function createActions(windowManager) {
119230
}
120231
},
121232
},
233+
CloseTab: {
234+
label: "Close Tab",
235+
accelerator: "CommandOrControl+Shift+W",
236+
click: (focusedWindow) => {
237+
if (focusedWindow) {
238+
focusedWindow.webContents.executeJavaScript(`
239+
try {
240+
const tabBar = document.querySelector('#tabbar');
241+
if (tabBar && typeof tabBar.getActiveTab === 'function' && typeof tabBar.closeTab === 'function') {
242+
const activeTab = tabBar.getActiveTab();
243+
if (activeTab && activeTab.id) {
244+
tabBar.closeTab(activeTab.id);
245+
}
246+
}
247+
} catch (error) {
248+
console.error('Error closing tab:', error);
249+
}
250+
`).catch(error => {
251+
console.error('Script execution failed:', error);
252+
});
253+
}
254+
},
255+
},
256+
NextTab: {
257+
label: "Next Tab",
258+
accelerator: "CommandOrControl+Tab",
259+
click: (focusedWindow) => {
260+
if (focusedWindow) {
261+
focusedWindow.webContents.executeJavaScript(`
262+
try {
263+
const tabBar = document.querySelector('#tabbar');
264+
if (tabBar && tabBar.tabs && tabBar.tabs.length > 1) {
265+
const activeIndex = tabBar.tabs.findIndex(tab => tab.id === tabBar.activeTabId);
266+
const nextIndex = (activeIndex + 1) % tabBar.tabs.length;
267+
if (typeof tabBar.selectTab === 'function') {
268+
tabBar.selectTab(tabBar.tabs[nextIndex].id);
269+
}
270+
}
271+
} catch (error) {
272+
console.error('Error switching to next tab:', error);
273+
}
274+
`).catch(error => {
275+
console.error('Script execution failed:', error);
276+
});
277+
}
278+
},
279+
},
280+
PreviousTab: {
281+
label: "Previous Tab",
282+
accelerator: "CommandOrControl+Shift+Tab",
283+
click: (focusedWindow) => {
284+
if (focusedWindow) {
285+
focusedWindow.webContents.executeJavaScript(`
286+
try {
287+
const tabBar = document.querySelector('#tabbar');
288+
if (tabBar && tabBar.tabs && tabBar.tabs.length > 1) {
289+
const activeIndex = tabBar.tabs.findIndex(tab => tab.id === tabBar.activeTabId);
290+
const prevIndex = (activeIndex - 1 + tabBar.tabs.length) % tabBar.tabs.length;
291+
if (typeof tabBar.selectTab === 'function') {
292+
tabBar.selectTab(tabBar.tabs[prevIndex].id);
293+
}
294+
}
295+
} catch (error) {
296+
console.error('Error switching to previous tab:', error);
297+
}
298+
`).catch(error => {
299+
console.error('Script execution failed:', error);
300+
});
301+
}
302+
},
303+
},
122304
};
123305

124306
return actions;
@@ -159,4 +341,4 @@ export function registerShortcuts(windowManager) {
159341
});
160342
}
161343
});
162-
}
344+
}

src/context-menu.js

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,61 @@ export function attachContextMenus(browserWindow, windowManager) {
8686
new MenuItem({
8787
label: "Back",
8888
enabled: webContents.canGoBack(),
89-
click: () => webContents.goBack(),
89+
click: () => {
90+
// Try tab-based navigation first, fallback to direct webContents
91+
browserWindow.webContents.executeJavaScript(`
92+
const tabBar = document.querySelector('#tabbar');
93+
if (tabBar && typeof tabBar.goBackActiveTab === 'function') {
94+
tabBar.goBackActiveTab();
95+
} else {
96+
// Direct webContents navigation for single webview
97+
return 'fallback';
98+
}
99+
`).then(result => {
100+
if (result === 'fallback') {
101+
webContents.goBack();
102+
}
103+
}).catch(() => webContents.goBack());
104+
},
90105
})
91106
);
92107
menu.append(
93108
new MenuItem({
94109
label: "Forward",
95110
enabled: webContents.canGoForward(),
96-
click: () => webContents.goForward(),
111+
click: () => {
112+
browserWindow.webContents.executeJavaScript(`
113+
const tabBar = document.querySelector('#tabbar');
114+
if (tabBar && typeof tabBar.goForwardActiveTab === 'function') {
115+
tabBar.goForwardActiveTab();
116+
} else {
117+
return 'fallback';
118+
}
119+
`).then(result => {
120+
if (result === 'fallback') {
121+
webContents.goForward();
122+
}
123+
}).catch(() => webContents.goForward());
124+
},
97125
})
98126
);
99127
menu.append(
100128
new MenuItem({
101129
label: "Reload",
102-
click: () => webContents.reload(),
130+
click: () => {
131+
browserWindow.webContents.executeJavaScript(`
132+
const tabBar = document.querySelector('#tabbar');
133+
if (tabBar && typeof tabBar.reloadActiveTab === 'function') {
134+
tabBar.reloadActiveTab();
135+
} else {
136+
return 'fallback';
137+
}
138+
`).then(result => {
139+
if (result === 'fallback') {
140+
webContents.reload();
141+
}
142+
}).catch(() => webContents.reload());
143+
},
103144
})
104145
);
105146

@@ -129,7 +170,7 @@ export function attachContextMenus(browserWindow, windowManager) {
129170
label: "Open Link in New Window",
130171
click: () => {
131172
if (windowManagerInstance) {
132-
windowManagerInstance.open({ url: params.linkURL });
173+
windowManagerInstance.open({ url: params.linkURL, newWindow: true });
133174
} else {
134175
console.error("WindowManager instance not set.");
135176
}
@@ -151,13 +192,38 @@ export function attachContextMenus(browserWindow, windowManager) {
151192
(event, webviewWebContents) => {
152193
attachMenuToWebContents(webviewWebContents);
153194

195+
// Intercept window.open / target="_blank" requests and try to add them as tabs
154196
webviewWebContents.setWindowOpenHandler(({ url }) => {
155-
if (windowManagerInstance) {
156-
windowManagerInstance.open({ url });
157-
} else {
158-
console.error("WindowManager instance not set.");
159-
}
160-
return { action: "deny" };
197+
// First, attempt to add the URL as a new tab in the current window (renderer side)
198+
const escapedUrl = url.replace(/'/g, "\\'");
199+
200+
browserWindow.webContents
201+
.executeJavaScript(`
202+
const tabBar = document.querySelector('#tabbar');
203+
if (tabBar && typeof tabBar.addTab === 'function') {
204+
tabBar.addTab('${escapedUrl}');
205+
// Indicate success so main process knows no fallback is required
206+
true;
207+
} else {
208+
// Tab bar not available – signal fallback
209+
false;
210+
}
211+
`)
212+
.then((added) => {
213+
if (!added && windowManagerInstance) {
214+
// Fallback: open in new window if tab creation failed
215+
windowManagerInstance.open({ url });
216+
}
217+
})
218+
.catch((err) => {
219+
console.error('Failed to add tab from windowOpenHandler:', err);
220+
if (windowManagerInstance) {
221+
windowManagerInstance.open({ url });
222+
}
223+
});
224+
225+
// Always deny the automatic window creation – we will handle it ourselves
226+
return { action: 'deny' };
161227
});
162228
}
163229
);

0 commit comments

Comments
 (0)