Refactor history and settings UI; improve tab rendering

Moved site and search history management from the main process to the renderer for better performance and reliability. Updated settings UI to use a sidebar tab layout with improved accessibility and responsive design. Refactored tab rendering in the browser to use efficient scheduling and added a robust tab label function. Cleaned up context menu code and improved async file operations for bookmarks and history.
This commit is contained in:
2025-08-09 21:51:31 +12:00
parent 4cf0634ef5
commit 8deeccb32e
6 changed files with 356 additions and 321 deletions
+37 -81
View File
@@ -53,6 +53,30 @@ let activeTabId = null;
const allowedInternalPages = ['settings', 'home'];
let bookmarks = [];
// Efficient render scheduling to avoid redundant DOM work
let tabsRenderPending = false;
function scheduleRenderTabs() {
if (tabsRenderPending) return;
tabsRenderPending = true;
requestAnimationFrame(() => {
tabsRenderPending = false;
renderTabs();
});
}
// Derive a stable, safe label for a tab without throwing on non-URLs
function getTabLabel(tab) {
if (tab.title && tab.title !== 'New Tab') return tab.title;
const u = tab.url || '';
try {
if (u.startsWith('http')) return new URL(u).hostname;
if (u.startsWith('browser://')) return u.replace('browser://', '');
return u || 'New Tab';
} catch {
return u || 'New Tab';
}
}
// Load bookmarks on startup
async function loadBookmarks() {
try {
@@ -107,9 +131,9 @@ function createTab(inputUrl) {
isHome: true
};
tabs.push(tab);
setActiveTab(id);
// Render the tab bar so the new home tab appears
renderTabs();
setActiveTab(id);
// Render the tab bar so the new home tab appears
scheduleRenderTabs();
return id;
}
@@ -208,7 +232,7 @@ function createTab(inputUrl) {
});
setActiveTab(id);
renderTabs();
scheduleRenderTabs();
}
@@ -236,7 +260,7 @@ function updateTabMetadata(id, key, value) {
const tab = tabs.find(t => t.id === id);
if (tab) {
tab[key] = value;
renderTabs();
scheduleRenderTabs();
}
}
@@ -276,7 +300,7 @@ function navigate() {
tab.url = input;
webview.src = resolved;
renderTabs();
scheduleRenderTabs();
updateNavButtons();
}
@@ -363,7 +387,7 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
updateNavButtons();
// Activate converted webview tab and update UI
setActiveTab(tabId);
renderTabs();
scheduleRenderTabs();
}
function handleNavigation(tabId, newUrl) {
@@ -406,7 +430,7 @@ function handleNavigation(tabId, newUrl) {
urlBox.value = displayUrl === 'browser://home' ? '' : displayUrl;
}
renderTabs();
scheduleRenderTabs();
updateNavButtons();
}
@@ -439,7 +463,7 @@ function setActiveTab(id) {
if (tab) {
// If the tab URL represents the home page, keep the URL bar blank.
urlBox.value = tab.url === 'browser://home' ? '' : tab.url;
renderTabs();
scheduleRenderTabs();
updateNavButtons();
updateZoomUI(); // ← update zoom display for new active tab
}
@@ -455,7 +479,7 @@ function closeTab(id) {
if (tabs.length > 0) setActiveTab(tabs[0].id);
}
renderTabs();
scheduleRenderTabs();
updateNavButtons();
}
@@ -475,7 +499,7 @@ function renderTabs() {
el.appendChild(icon);
}
el.appendChild(document.createTextNode(tab.title || new URL(tab.url).hostname));
el.appendChild(document.createTextNode(getTabLabel(tab)));
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
@@ -572,17 +596,7 @@ menuBtn.addEventListener('click', () => {
});
window.addEventListener('DOMContentLoaded', () => {
// Add some debug info
console.log('[DEBUG] Site history initialized, current entries:', getSiteHistory().length);
// Add test entries if none exist (for debugging)
if (getSiteHistory().length === 0) {
console.log('[DEBUG] No existing history, adding test entries for debugging');
addToSiteHistory('https://www.google.com');
addToSiteHistory('https://github.com');
console.log('[DEBUG] Test entries added, history now:', getSiteHistory());
}
// Initial boot
createTab();
// Handle IPC messages from the static home webview (bookmarks navigation)
const staticHome = document.getElementById('home-webview');
@@ -651,65 +665,7 @@ window.addEventListener('DOMContentLoaded', () => {
document.getElementById('zoom-percent').textContent = `${Math.round(z * 100)}%`;
});
// menurelated code (moved here so #context-menu exists)
const items = contextMenu ? contextMenu.querySelectorAll('li') : [];
function showContextMenu(x, y) {
if (!menu) return;
menu.style.top = `${y}px`;
menu.style.left = `${x}px`;
menu.classList.add('visible');
}
document.addEventListener('contextmenu', e => {
if (e.target.tagName === 'WEBVIEW' ||
e.composedPath().some(el => el.id === 'webviews')) {
e.preventDefault();
showContextMenu(e.clientX, e.clientY);
}
});
document.addEventListener('click', () => {
if (menu) menu.classList.remove('visible');
});
items.forEach(item => {
item.addEventListener('click', async () => {
const action = item.dataset.action;
const win = remote.getCurrentWindow();
switch (action) {
case 'save-page': {
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'page.html' });
if (!canceled && filePath) win.webContents.savePage(filePath, 'HTMLComplete');
break;
}
case 'select-all':
document.execCommand('selectAll');
break;
case 'screenshot': {
const image = await win.webContents.capturePage();
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'screenshot.png' });
if (!canceled && filePath) fs.writeFileSync(filePath, image.toPNG());
break;
}
case 'view-source': {
const html = document.documentElement.outerHTML;
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'source.html' });
if (!canceled && filePath) fs.writeFileSync(filePath, html);
break;
}
case 'inspect-accessibility':
win.webContents.inspectAccessibilityNode(e.clientX, e.clientY);
break;
case 'inspect-element':
win.webContents.inspectElement(e.clientX, e.clientY);
break;
}
menu.classList.remove('visible');
});
});
// (Removed broken duplicate context menu wiring)
// Migrate existing site history from JSON file to localStorage (one-time migration)
const migrateSiteHistory = async () => {