electrobun 0.5.0-beta.0 → 0.6.0-beta.0
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/{templates/multitab-browser/bun.lock → bun.lock} +20 -13
- package/dist/api/bun/proc/native.ts +84 -16
- package/package.json +14 -16
- package/BETA_RELEASE.md +0 -67
- package/BUILD.md +0 -90
- package/LICENSE +0 -21
- package/README.md +0 -102
- package/debug.js +0 -5
- package/templates/hello-world/README.md +0 -57
- package/templates/hello-world/bun.lock +0 -225
- package/templates/hello-world/electrobun.config.ts +0 -28
- package/templates/hello-world/package.json +0 -16
- package/templates/hello-world/src/bun/index.ts +0 -15
- package/templates/hello-world/src/mainview/index.css +0 -124
- package/templates/hello-world/src/mainview/index.html +0 -46
- package/templates/hello-world/src/mainview/index.ts +0 -1
- package/templates/interactive-playground/README.md +0 -26
- package/templates/interactive-playground/assets/tray-icon.png +0 -0
- package/templates/interactive-playground/electrobun.config.ts +0 -36
- package/templates/interactive-playground/package-lock.json +0 -1112
- package/templates/interactive-playground/package.json +0 -15
- package/templates/interactive-playground/src/bun/demos/files.ts +0 -70
- package/templates/interactive-playground/src/bun/demos/menus.ts +0 -139
- package/templates/interactive-playground/src/bun/demos/rpc.ts +0 -83
- package/templates/interactive-playground/src/bun/demos/system.ts +0 -72
- package/templates/interactive-playground/src/bun/demos/updates.ts +0 -105
- package/templates/interactive-playground/src/bun/demos/windows.ts +0 -90
- package/templates/interactive-playground/src/bun/index.ts +0 -124
- package/templates/interactive-playground/src/bun/types/rpc.ts +0 -109
- package/templates/interactive-playground/src/mainview/components/EventLog.ts +0 -107
- package/templates/interactive-playground/src/mainview/components/Sidebar.ts +0 -65
- package/templates/interactive-playground/src/mainview/components/Toast.ts +0 -57
- package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +0 -211
- package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +0 -102
- package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +0 -229
- package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +0 -132
- package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +0 -465
- package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +0 -207
- package/templates/interactive-playground/src/mainview/index.css +0 -538
- package/templates/interactive-playground/src/mainview/index.html +0 -103
- package/templates/interactive-playground/src/mainview/index.ts +0 -238
- package/templates/multitab-browser/README.md +0 -34
- package/templates/multitab-browser/electrobun.config.ts +0 -32
- package/templates/multitab-browser/package-lock.json +0 -20
- package/templates/multitab-browser/package.json +0 -12
- package/templates/multitab-browser/src/bun/index.ts +0 -144
- package/templates/multitab-browser/src/bun/tabManager.ts +0 -200
- package/templates/multitab-browser/src/bun/types/rpc.ts +0 -78
- package/templates/multitab-browser/src/mainview/index.css +0 -487
- package/templates/multitab-browser/src/mainview/index.html +0 -94
- package/templates/multitab-browser/src/mainview/index.ts +0 -634
- package/templates/photo-booth/README.md +0 -108
- package/templates/photo-booth/bun.lock +0 -239
- package/templates/photo-booth/electrobun.config.ts +0 -32
- package/templates/photo-booth/package.json +0 -17
- package/templates/photo-booth/src/bun/index.ts +0 -92
- package/templates/photo-booth/src/mainview/index.css +0 -465
- package/templates/photo-booth/src/mainview/index.html +0 -124
- package/templates/photo-booth/src/mainview/index.ts +0 -499
- package/test-new-window-events.ts +0 -26
- package/test-new-window.html +0 -75
- package/test-npm-install.sh +0 -34
- package/tests/bun.lock +0 -14
- package/tests/electrobun.config.ts +0 -45
- package/tests/package-lock.json +0 -36
- package/tests/package.json +0 -13
- package/tests/src/bun/index.ts +0 -100
- package/tests/src/bun/test-runner.ts +0 -508
- package/tests/src/mainview/index.html +0 -110
- package/tests/src/mainview/index.ts +0 -458
- package/tests/src/mainview/styles/main.css +0 -451
- package/tests/src/testviews/tray-test.html +0 -57
- package/tests/src/testviews/webview-mask.html +0 -114
- package/tests/src/testviews/webview-navigation.html +0 -36
- package/tests/src/testviews/window-create.html +0 -17
- package/tests/src/testviews/window-events.html +0 -29
- package/tests/src/testviews/window-focus.html +0 -37
- package/tests/src/webviewtag/index.ts +0 -11
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
import Electrobun, { Electroview } from "electrobun/view";
|
|
2
|
-
|
|
3
|
-
console.log("🌐 Initializing Multitab Browser UI...");
|
|
4
|
-
|
|
5
|
-
// Create RPC client
|
|
6
|
-
const rpc = Electroview.defineRPC({
|
|
7
|
-
maxRequestTime: 10000,
|
|
8
|
-
handlers: {
|
|
9
|
-
requests: {},
|
|
10
|
-
messages: {
|
|
11
|
-
tabUpdated: (tab: any) => {
|
|
12
|
-
console.log("Tab updated:", tab);
|
|
13
|
-
if ((window as any).multitabBrowser) {
|
|
14
|
-
(window as any).multitabBrowser.handleTabUpdate(tab);
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
tabClosed: ({ id }: { id: string }) => {
|
|
18
|
-
console.log("Tab closed:", id);
|
|
19
|
-
if ((window as any).multitabBrowser) {
|
|
20
|
-
(window as any).multitabBrowser.handleTabClosed(id);
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Initialize Electrobun with RPC
|
|
28
|
-
const electrobun = new Electrobun.Electroview({ rpc });
|
|
29
|
-
|
|
30
|
-
class MultitabBrowser {
|
|
31
|
-
private tabs: Map<string, any> = new Map();
|
|
32
|
-
private webviews: Map<string, HTMLElement> = new Map();
|
|
33
|
-
private activeTabId: string | null = null;
|
|
34
|
-
private bookmarks: Map<string, any> = new Map();
|
|
35
|
-
|
|
36
|
-
constructor() {
|
|
37
|
-
// Store reference globally for RPC message handlers
|
|
38
|
-
(window as any).multitabBrowser = this;
|
|
39
|
-
|
|
40
|
-
this.initializeUI();
|
|
41
|
-
this.loadBookmarks();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private initializeUI(): void {
|
|
45
|
-
// New tab button
|
|
46
|
-
document.getElementById("new-tab-btn")?.addEventListener("click", () => {
|
|
47
|
-
this.createNewTab();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// URL bar navigation
|
|
51
|
-
const urlBar = document.getElementById("url-bar") as HTMLInputElement;
|
|
52
|
-
urlBar?.addEventListener("keypress", async (e) => {
|
|
53
|
-
if (e.key === "Enter") {
|
|
54
|
-
const url = urlBar.value.trim();
|
|
55
|
-
if (url) {
|
|
56
|
-
try {
|
|
57
|
-
// Process URL
|
|
58
|
-
let processedUrl = url;
|
|
59
|
-
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
60
|
-
if (url.includes(".") && !url.includes(" ")) {
|
|
61
|
-
processedUrl = `https://${url}`;
|
|
62
|
-
} else {
|
|
63
|
-
processedUrl = `https://www.google.com/search?q=${encodeURIComponent(url)}`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If no active tab, create a new one with this URL
|
|
68
|
-
if (!this.activeTabId) {
|
|
69
|
-
await this.createNewTab(processedUrl);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Navigate the existing webview
|
|
74
|
-
const webview = this.webviews.get(this.activeTabId) as any;
|
|
75
|
-
if (webview) {
|
|
76
|
-
webview.src = processedUrl;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Update tab info
|
|
80
|
-
const tab = this.tabs.get(this.activeTabId);
|
|
81
|
-
if (tab) {
|
|
82
|
-
tab.url = processedUrl;
|
|
83
|
-
this.handleTabUpdate(tab);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
await rpc.request.navigateTo({ tabId: this.activeTabId, url: processedUrl });
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error("Failed to navigate:", error);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Navigation buttons
|
|
95
|
-
document.getElementById("back-btn")?.addEventListener("click", async () => {
|
|
96
|
-
if (this.activeTabId) {
|
|
97
|
-
const webview = this.webviews.get(this.activeTabId) as any;
|
|
98
|
-
if (webview && webview.goBack) {
|
|
99
|
-
webview.goBack();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
document.getElementById("forward-btn")?.addEventListener("click", async () => {
|
|
105
|
-
if (this.activeTabId) {
|
|
106
|
-
const webview = this.webviews.get(this.activeTabId) as any;
|
|
107
|
-
if (webview && webview.goForward) {
|
|
108
|
-
webview.goForward();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
document.getElementById("reload-btn")?.addEventListener("click", async () => {
|
|
114
|
-
if (this.activeTabId) {
|
|
115
|
-
const webview = this.webviews.get(this.activeTabId) as any;
|
|
116
|
-
if (webview && webview.reload) {
|
|
117
|
-
webview.reload();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
document.getElementById("home-btn")?.addEventListener("click", async () => {
|
|
123
|
-
const homeUrl = "https://electrobun.dev";
|
|
124
|
-
|
|
125
|
-
if (this.activeTabId) {
|
|
126
|
-
// Navigate existing tab to home
|
|
127
|
-
const webview = this.webviews.get(this.activeTabId) as any;
|
|
128
|
-
if (webview) {
|
|
129
|
-
webview.src = homeUrl;
|
|
130
|
-
|
|
131
|
-
// Update tab info
|
|
132
|
-
const tab = this.tabs.get(this.activeTabId);
|
|
133
|
-
if (tab) {
|
|
134
|
-
tab.url = homeUrl;
|
|
135
|
-
this.handleTabUpdate(tab);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
// Create new tab with home URL
|
|
140
|
-
await this.createNewTab(homeUrl);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Bookmark button
|
|
145
|
-
document.getElementById("bookmark-btn")?.addEventListener("click", () => {
|
|
146
|
-
this.toggleBookmark();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Bookmarks menu button
|
|
150
|
-
const bookmarksMenuBtn = document.getElementById("bookmarks-menu-btn");
|
|
151
|
-
console.log("Found bookmarks menu button:", bookmarksMenuBtn);
|
|
152
|
-
bookmarksMenuBtn?.addEventListener("click", (e) => {
|
|
153
|
-
console.log("Bookmarks menu button clicked");
|
|
154
|
-
e.stopPropagation();
|
|
155
|
-
this.toggleBookmarksMenu();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Reset bookmarks button (delegate to handle dynamically added buttons)
|
|
159
|
-
document.addEventListener("click", (e) => {
|
|
160
|
-
if ((e.target as HTMLElement)?.id === "reset-bookmarks-btn") {
|
|
161
|
-
e.preventDefault();
|
|
162
|
-
e.stopPropagation();
|
|
163
|
-
console.log("Reset button clicked");
|
|
164
|
-
this.resetBookmarks();
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Close bookmarks dropdown when clicking outside
|
|
170
|
-
document.addEventListener("click", (e) => {
|
|
171
|
-
const dropdown = document.getElementById("bookmarks-dropdown");
|
|
172
|
-
const menuBtn = document.getElementById("bookmarks-menu-btn");
|
|
173
|
-
const resetBtn = document.getElementById("reset-bookmarks-btn");
|
|
174
|
-
if (dropdown && !dropdown.contains(e.target as Node) && e.target !== menuBtn && e.target !== resetBtn) {
|
|
175
|
-
dropdown.classList.add("hidden");
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Keyboard shortcuts - support both Cmd (Mac) and Ctrl (Windows/Linux)
|
|
180
|
-
document.addEventListener("keydown", (e) => {
|
|
181
|
-
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
|
182
|
-
const modifierPressed = isMac ? e.metaKey : e.ctrlKey;
|
|
183
|
-
|
|
184
|
-
// Also support the opposite modifier for cross-platform compatibility
|
|
185
|
-
const altModifierPressed = isMac ? e.ctrlKey : e.metaKey;
|
|
186
|
-
|
|
187
|
-
if ((modifierPressed || altModifierPressed) && e.key.toLowerCase() === "t") {
|
|
188
|
-
e.preventDefault();
|
|
189
|
-
this.createNewTab();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if ((modifierPressed || altModifierPressed) && e.key.toLowerCase() === "w") {
|
|
193
|
-
e.preventDefault();
|
|
194
|
-
if (this.activeTabId) {
|
|
195
|
-
this.closeTab(this.activeTabId);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if ((modifierPressed || altModifierPressed) && e.key.toLowerCase() === "l") {
|
|
200
|
-
e.preventDefault();
|
|
201
|
-
const urlBar = document.getElementById("url-bar") as HTMLInputElement;
|
|
202
|
-
urlBar?.focus();
|
|
203
|
-
urlBar?.select();
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Show welcome screen initially
|
|
208
|
-
this.showWelcomeScreen();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private async createNewTab(url?: string): Promise<void> {
|
|
212
|
-
try {
|
|
213
|
-
const tab = await rpc.request.createTab({ url });
|
|
214
|
-
this.tabs.set(tab.id, tab);
|
|
215
|
-
|
|
216
|
-
// Create electrobun-webview element for this tab
|
|
217
|
-
const webview = document.createElement('electrobun-webview');
|
|
218
|
-
webview.setAttribute('src', tab.url);
|
|
219
|
-
webview.setAttribute('id', `webview-${tab.id}`);
|
|
220
|
-
webview.setAttribute('masks', '#bookmarks-dropdown');
|
|
221
|
-
webview.setAttribute('renderer', 'cef');
|
|
222
|
-
webview.classList.add('tab-webview');
|
|
223
|
-
|
|
224
|
-
// Add webview to container
|
|
225
|
-
const container = document.getElementById('webview-container');
|
|
226
|
-
if (container) {
|
|
227
|
-
container.appendChild(webview);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
this.webviews.set(tab.id, webview);
|
|
231
|
-
|
|
232
|
-
// Set up webview event listeners
|
|
233
|
-
webview.addEventListener('page-title-updated', (e: any) => {
|
|
234
|
-
const updatedTab = this.tabs.get(tab.id);
|
|
235
|
-
if (updatedTab) {
|
|
236
|
-
updatedTab.title = e.detail?.title || 'New Tab';
|
|
237
|
-
this.handleTabUpdate(updatedTab);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
webview.addEventListener('did-navigate', (e: any) => {
|
|
242
|
-
const updatedTab = this.tabs.get(tab.id);
|
|
243
|
-
if (updatedTab && e.detail?.url) {
|
|
244
|
-
updatedTab.url = e.detail.url;
|
|
245
|
-
this.handleTabUpdate(updatedTab);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
this.renderTab(tab);
|
|
250
|
-
this.switchToTab(tab.id);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.error("Failed to create tab:", error);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private renderTab(tab: any): void {
|
|
257
|
-
const tabsContainer = document.getElementById("tabs-container");
|
|
258
|
-
if (!tabsContainer) return;
|
|
259
|
-
|
|
260
|
-
const tabElement = document.createElement("div");
|
|
261
|
-
tabElement.className = "tab";
|
|
262
|
-
tabElement.id = `tab-${tab.id}`;
|
|
263
|
-
tabElement.innerHTML = `
|
|
264
|
-
<span class="tab-title">${this.truncateTitle(tab.title)}</span>
|
|
265
|
-
<button class="tab-close" data-tab-id="${tab.id}">×</button>
|
|
266
|
-
`;
|
|
267
|
-
|
|
268
|
-
tabElement.addEventListener("click", (e) => {
|
|
269
|
-
if (!(e.target as HTMLElement).classList.contains("tab-close")) {
|
|
270
|
-
this.switchToTab(tab.id);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
tabElement.querySelector(".tab-close")?.addEventListener("click", (e) => {
|
|
275
|
-
e.stopPropagation();
|
|
276
|
-
this.closeTab(tab.id);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
tabsContainer.appendChild(tabElement);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
private async switchToTab(tabId: string): Promise<void> {
|
|
283
|
-
try {
|
|
284
|
-
// Update UI immediately
|
|
285
|
-
document.querySelectorAll(".tab").forEach(tab => {
|
|
286
|
-
tab.classList.remove("active");
|
|
287
|
-
});
|
|
288
|
-
document.getElementById(`tab-${tabId}`)?.classList.add("active");
|
|
289
|
-
|
|
290
|
-
// Hide all webviews
|
|
291
|
-
this.webviews.forEach((webview) => {
|
|
292
|
-
// webview.classList.remove('active');
|
|
293
|
-
webview.toggleHidden(true)
|
|
294
|
-
webview.togglePassthrough(true)
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Show the selected webview
|
|
298
|
-
const selectedWebview = this.webviews.get(tabId);
|
|
299
|
-
if (selectedWebview) {
|
|
300
|
-
selectedWebview.classList.add('active');
|
|
301
|
-
selectedWebview.toggleHidden(false)
|
|
302
|
-
selectedWebview.togglePassthrough(false)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
this.activeTabId = tabId;
|
|
306
|
-
const tab = this.tabs.get(tabId);
|
|
307
|
-
|
|
308
|
-
if (tab) {
|
|
309
|
-
const urlBar = document.getElementById("url-bar") as HTMLInputElement;
|
|
310
|
-
if (urlBar) {
|
|
311
|
-
urlBar.value = tab.url;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
this.updateBookmarkButton();
|
|
315
|
-
this.hideWelcomeScreen();
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Notify backend about tab switch (optional)
|
|
319
|
-
await rpc.request.activateTab({ tabId });
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("Failed to switch tab:", error);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
private async closeTab(tabId: string): Promise<void> {
|
|
326
|
-
try {
|
|
327
|
-
console.log(`Closing tab ${tabId}, active tab: ${this.activeTabId}, total tabs before: ${this.tabs.size}`);
|
|
328
|
-
|
|
329
|
-
await rpc.request.closeTab({ id: tabId });
|
|
330
|
-
this.tabs.delete(tabId);
|
|
331
|
-
|
|
332
|
-
// Remove the webview element
|
|
333
|
-
const webview = this.webviews.get(tabId);
|
|
334
|
-
if (webview) {
|
|
335
|
-
webview.remove();
|
|
336
|
-
this.webviews.delete(tabId);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
document.getElementById(`tab-${tabId}`)?.remove();
|
|
340
|
-
|
|
341
|
-
const remainingTabs = Array.from(this.tabs.keys());
|
|
342
|
-
console.log(`Remaining tabs after close: ${remainingTabs.length}`, remainingTabs);
|
|
343
|
-
|
|
344
|
-
// Check if this was the active tab
|
|
345
|
-
if (this.activeTabId === tabId) {
|
|
346
|
-
console.log("Closed the active tab");
|
|
347
|
-
this.activeTabId = null;
|
|
348
|
-
|
|
349
|
-
if (remainingTabs.length > 0) {
|
|
350
|
-
console.log("Switching to remaining tab:", remainingTabs[remainingTabs.length - 1]);
|
|
351
|
-
this.switchToTab(remainingTabs[remainingTabs.length - 1]);
|
|
352
|
-
} else {
|
|
353
|
-
console.log("No tabs left - showing welcome screen");
|
|
354
|
-
this.showWelcomeScreen();
|
|
355
|
-
}
|
|
356
|
-
} else {
|
|
357
|
-
console.log("Closed a non-active tab");
|
|
358
|
-
if (remainingTabs.length === 0) {
|
|
359
|
-
console.log("No tabs left after closing non-active tab - showing welcome screen");
|
|
360
|
-
this.activeTabId = null;
|
|
361
|
-
this.showWelcomeScreen();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
} catch (error) {
|
|
365
|
-
console.error("Failed to close tab:", error);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
public handleTabUpdate(tab: any): void {
|
|
370
|
-
this.tabs.set(tab.id, tab);
|
|
371
|
-
|
|
372
|
-
const tabElement = document.getElementById(`tab-${tab.id}`);
|
|
373
|
-
if (tabElement) {
|
|
374
|
-
const titleElement = tabElement.querySelector(".tab-title");
|
|
375
|
-
if (titleElement) {
|
|
376
|
-
titleElement.textContent = this.truncateTitle(tab.title);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (this.activeTabId === tab.id) {
|
|
381
|
-
const urlBar = document.getElementById("url-bar") as HTMLInputElement;
|
|
382
|
-
if (urlBar && document.activeElement !== urlBar) {
|
|
383
|
-
urlBar.value = tab.url;
|
|
384
|
-
}
|
|
385
|
-
this.updateBookmarkButton();
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
public handleTabClosed(id: string): void {
|
|
390
|
-
this.tabs.delete(id);
|
|
391
|
-
document.getElementById(`tab-${id}`)?.remove();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
private showWelcomeScreen(): void {
|
|
395
|
-
const welcome = document.getElementById("welcome-screen");
|
|
396
|
-
const webview = document.getElementById("webview-container");
|
|
397
|
-
if (welcome) welcome.style.display = "flex";
|
|
398
|
-
if (webview) webview.style.display = "none";
|
|
399
|
-
|
|
400
|
-
const urlBar = document.getElementById("url-bar") as HTMLInputElement;
|
|
401
|
-
if (urlBar) urlBar.value = "";
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
private hideWelcomeScreen(): void {
|
|
405
|
-
const welcome = document.getElementById("welcome-screen");
|
|
406
|
-
const webview = document.getElementById("webview-container");
|
|
407
|
-
if (welcome) welcome.style.display = "none";
|
|
408
|
-
if (webview) webview.style.display = "block";
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private truncateTitle(title: string, maxLength: number = 20): string {
|
|
412
|
-
if (title.length <= maxLength) return title;
|
|
413
|
-
return title.substring(0, maxLength - 3) + "...";
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private async loadBookmarks(): Promise<void> {
|
|
417
|
-
try {
|
|
418
|
-
// Load bookmarks from localStorage or backend
|
|
419
|
-
const stored = localStorage.getItem('bookmarks');
|
|
420
|
-
if (stored) {
|
|
421
|
-
const bookmarksArray = JSON.parse(stored);
|
|
422
|
-
bookmarksArray.forEach((bookmark: any) => {
|
|
423
|
-
this.bookmarks.set(bookmark.url, bookmark);
|
|
424
|
-
});
|
|
425
|
-
} else {
|
|
426
|
-
// Add default bookmarks
|
|
427
|
-
this.addBookmark("Electrobun", "https://electrobun.dev");
|
|
428
|
-
this.addBookmark("Electrobun GitHub", "https://github.com/blackboardsh/electrobun");
|
|
429
|
-
this.addBookmark("Yoav on Bluesky", "https://bsky.app/profile/yoav.codes");
|
|
430
|
-
this.addBookmark("Blackboard", "https://www.blackboard.sh");
|
|
431
|
-
}
|
|
432
|
-
this.renderBookmarks();
|
|
433
|
-
this.renderQuickLinks();
|
|
434
|
-
} catch (error) {
|
|
435
|
-
console.error("Failed to load bookmarks:", error);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private saveBookmarks(): void {
|
|
440
|
-
const bookmarksArray = Array.from(this.bookmarks.values());
|
|
441
|
-
localStorage.setItem('bookmarks', JSON.stringify(bookmarksArray));
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private resetBookmarks(): void {
|
|
445
|
-
console.log("resetBookmarks called - resetting without confirmation");
|
|
446
|
-
|
|
447
|
-
// Clear existing bookmarks
|
|
448
|
-
this.bookmarks.clear();
|
|
449
|
-
localStorage.removeItem('bookmarks');
|
|
450
|
-
|
|
451
|
-
// Add default bookmarks with unique IDs
|
|
452
|
-
let counter = 0;
|
|
453
|
-
const addDefaultBookmark = (title: string, url: string) => {
|
|
454
|
-
const bookmark = {
|
|
455
|
-
id: `bookmark-default-${counter++}`,
|
|
456
|
-
title,
|
|
457
|
-
url,
|
|
458
|
-
createdAt: Date.now() + counter
|
|
459
|
-
};
|
|
460
|
-
this.bookmarks.set(url, bookmark);
|
|
461
|
-
console.log("Added bookmark:", bookmark);
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
addDefaultBookmark("Electrobun", "https://electrobun.dev");
|
|
465
|
-
addDefaultBookmark("Electrobun GitHub", "https://github.com/blackboardsh/electrobun");
|
|
466
|
-
addDefaultBookmark("Yoav on Bluesky", "https://bsky.app/profile/yoav.codes");
|
|
467
|
-
addDefaultBookmark("Blackboard", "https://www.blackboard.sh");
|
|
468
|
-
|
|
469
|
-
// Save and re-render
|
|
470
|
-
this.saveBookmarks();
|
|
471
|
-
this.renderBookmarks();
|
|
472
|
-
this.renderQuickLinks();
|
|
473
|
-
this.updateBookmarkButton();
|
|
474
|
-
|
|
475
|
-
console.log("Bookmarks reset completed, total bookmarks:", this.bookmarks.size);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private addBookmark(title: string, url: string): void {
|
|
479
|
-
const bookmark = {
|
|
480
|
-
id: `bookmark-${Date.now()}`,
|
|
481
|
-
title,
|
|
482
|
-
url,
|
|
483
|
-
createdAt: Date.now()
|
|
484
|
-
};
|
|
485
|
-
this.bookmarks.set(url, bookmark);
|
|
486
|
-
this.saveBookmarks();
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
private removeBookmark(url: string): void {
|
|
490
|
-
this.bookmarks.delete(url);
|
|
491
|
-
this.saveBookmarks();
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
private toggleBookmark(): void {
|
|
495
|
-
if (!this.activeTabId) return;
|
|
496
|
-
|
|
497
|
-
const tab = this.tabs.get(this.activeTabId);
|
|
498
|
-
if (!tab) return;
|
|
499
|
-
|
|
500
|
-
const bookmarkBtn = document.getElementById("bookmark-btn");
|
|
501
|
-
if (!bookmarkBtn) return;
|
|
502
|
-
|
|
503
|
-
if (this.bookmarks.has(tab.url)) {
|
|
504
|
-
// Remove bookmark
|
|
505
|
-
this.removeBookmark(tab.url);
|
|
506
|
-
bookmarkBtn.classList.remove("bookmarked");
|
|
507
|
-
} else {
|
|
508
|
-
// Add bookmark
|
|
509
|
-
this.addBookmark(tab.title || "Untitled", tab.url);
|
|
510
|
-
bookmarkBtn.classList.add("bookmarked");
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
this.renderBookmarks();
|
|
514
|
-
this.renderQuickLinks();
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
private updateBookmarkButton(): void {
|
|
518
|
-
const bookmarkBtn = document.getElementById("bookmark-btn");
|
|
519
|
-
if (!bookmarkBtn || !this.activeTabId) return;
|
|
520
|
-
|
|
521
|
-
const tab = this.tabs.get(this.activeTabId);
|
|
522
|
-
if (tab && this.bookmarks.has(tab.url)) {
|
|
523
|
-
bookmarkBtn.classList.add("bookmarked");
|
|
524
|
-
} else {
|
|
525
|
-
bookmarkBtn.classList.remove("bookmarked");
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
private toggleBookmarksMenu(): void {
|
|
530
|
-
const dropdown = document.getElementById("bookmarks-dropdown");
|
|
531
|
-
if (dropdown) {
|
|
532
|
-
console.log("Toggling bookmarks menu, current hidden:", dropdown.classList.contains("hidden"));
|
|
533
|
-
dropdown.classList.toggle("hidden");
|
|
534
|
-
} else {
|
|
535
|
-
console.error("Bookmarks dropdown not found");
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
private renderBookmarks(): void {
|
|
540
|
-
const bookmarksList = document.getElementById("bookmarks-list");
|
|
541
|
-
if (!bookmarksList) return;
|
|
542
|
-
|
|
543
|
-
bookmarksList.innerHTML = "";
|
|
544
|
-
|
|
545
|
-
if (this.bookmarks.size === 0) {
|
|
546
|
-
bookmarksList.innerHTML = '<div class="no-bookmarks">No bookmarks yet</div>';
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
this.bookmarks.forEach(bookmark => {
|
|
551
|
-
const item = document.createElement("div");
|
|
552
|
-
item.className = "bookmark-item";
|
|
553
|
-
item.innerHTML = `
|
|
554
|
-
<div class="bookmark-info">
|
|
555
|
-
<div class="bookmark-title">${bookmark.title}</div>
|
|
556
|
-
<div class="bookmark-url">${this.truncateUrl(bookmark.url)}</div>
|
|
557
|
-
</div>
|
|
558
|
-
<button class="bookmark-delete" data-url="${bookmark.url}">×</button>
|
|
559
|
-
`;
|
|
560
|
-
|
|
561
|
-
item.querySelector(".bookmark-info")?.addEventListener("click", async () => {
|
|
562
|
-
if (this.activeTabId) {
|
|
563
|
-
// Navigate current tab
|
|
564
|
-
const webview = this.webviews.get(this.activeTabId) as any;
|
|
565
|
-
if (webview) {
|
|
566
|
-
webview.src = bookmark.url;
|
|
567
|
-
const tab = this.tabs.get(this.activeTabId);
|
|
568
|
-
if (tab) {
|
|
569
|
-
tab.url = bookmark.url;
|
|
570
|
-
this.handleTabUpdate(tab);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
} else {
|
|
574
|
-
// Create new tab with bookmark
|
|
575
|
-
await this.createNewTab(bookmark.url);
|
|
576
|
-
}
|
|
577
|
-
// Hide dropdown
|
|
578
|
-
const dropdown = document.getElementById("bookmarks-dropdown");
|
|
579
|
-
if (dropdown) {
|
|
580
|
-
dropdown.classList.add("hidden");
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
item.querySelector(".bookmark-delete")?.addEventListener("click", (e) => {
|
|
585
|
-
e.stopPropagation();
|
|
586
|
-
const url = (e.currentTarget as HTMLElement).dataset.url;
|
|
587
|
-
if (url) {
|
|
588
|
-
this.removeBookmark(url);
|
|
589
|
-
this.renderBookmarks();
|
|
590
|
-
this.renderQuickLinks();
|
|
591
|
-
this.updateBookmarkButton();
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
bookmarksList.appendChild(item);
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
private renderQuickLinks(): void {
|
|
600
|
-
const container = document.getElementById("quick-links-container");
|
|
601
|
-
if (!container) return;
|
|
602
|
-
|
|
603
|
-
container.innerHTML = "";
|
|
604
|
-
|
|
605
|
-
// Show first 6 bookmarks as quick links
|
|
606
|
-
const bookmarksArray = Array.from(this.bookmarks.values());
|
|
607
|
-
bookmarksArray.slice(0, 6).forEach(bookmark => {
|
|
608
|
-
const link = document.createElement("button");
|
|
609
|
-
link.className = "quick-link";
|
|
610
|
-
link.innerHTML = `
|
|
611
|
-
<div class="quick-link-favicon">🌐</div>
|
|
612
|
-
<div class="quick-link-title">${this.truncateTitle(bookmark.title, 15)}</div>
|
|
613
|
-
`;
|
|
614
|
-
link.addEventListener("click", () => {
|
|
615
|
-
this.createNewTab(bookmark.url);
|
|
616
|
-
});
|
|
617
|
-
container.appendChild(link);
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
private truncateUrl(url: string, maxLength: number = 40): string {
|
|
622
|
-
if (url.length <= maxLength) return url;
|
|
623
|
-
return url.substring(0, maxLength - 3) + "...";
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Initialize when DOM is ready
|
|
628
|
-
if (document.readyState === "loading") {
|
|
629
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
630
|
-
new MultitabBrowser();
|
|
631
|
-
});
|
|
632
|
-
} else {
|
|
633
|
-
new MultitabBrowser();
|
|
634
|
-
}
|