aba6145bea
Fixed 2 tabs opening on start up
765 lines
24 KiB
JavaScript
765 lines
24 KiB
JavaScript
const ipcRenderer = window.electronAPI;
|
||
|
||
// Site history management using localStorage
|
||
function getSiteHistory() {
|
||
try {
|
||
const history = localStorage.getItem('siteHistory');
|
||
return history ? JSON.parse(history) : [];
|
||
} catch (err) {
|
||
console.error('Error reading site history from localStorage:', err);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function addToSiteHistory(url) {
|
||
try {
|
||
let history = getSiteHistory();
|
||
// Remove if already exists to avoid duplicates
|
||
history = history.filter(item => item !== url);
|
||
// Add to beginning
|
||
history.unshift(url);
|
||
// Keep only last 100 entries
|
||
if (history.length > 100) {
|
||
history = history.slice(0, 100);
|
||
}
|
||
localStorage.setItem('siteHistory', JSON.stringify(history));
|
||
} catch (err) {
|
||
console.error('Error saving site history to localStorage:', err);
|
||
}
|
||
}
|
||
|
||
// 1) cache hot DOM references
|
||
const urlBox = document.getElementById('url');
|
||
const tabBarEl = document.getElementById('tab-bar');
|
||
const webviewsEl = document.getElementById('webviews');
|
||
const menuPopup = document.getElementById('menu-popup');
|
||
const contextMenu = document.getElementById('context-menu');
|
||
const menuItems = contextMenu ? contextMenu.querySelectorAll('li') : [];
|
||
|
||
// Select all text on focus and prevent mouseup from deselecting
|
||
urlBox.addEventListener('focus', () => {
|
||
urlBox.select();
|
||
});
|
||
urlBox.addEventListener('mouseup', e => e.preventDefault());
|
||
// Add Enter key navigation
|
||
urlBox.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
navigate();
|
||
}
|
||
});
|
||
|
||
let tabs = [];
|
||
let activeTabId = null;
|
||
const allowedInternalPages = ['settings', 'home'];
|
||
let bookmarks = [];
|
||
|
||
// Load bookmarks on startup
|
||
async function loadBookmarks() {
|
||
try {
|
||
bookmarks = await ipcRenderer.invoke('load-bookmarks');
|
||
} catch (error) {
|
||
console.error('Error loading bookmarks in main context:', error);
|
||
bookmarks = [];
|
||
}
|
||
}
|
||
|
||
// Function to save bookmarks
|
||
async function saveBookmarks(newBookmarks) {
|
||
try {
|
||
bookmarks = newBookmarks;
|
||
await ipcRenderer.invoke('save-bookmarks', bookmarks);
|
||
} catch (error) {
|
||
console.error('Error saving bookmarks in main context:', error);
|
||
}
|
||
}
|
||
|
||
// Load bookmarks when the script starts
|
||
loadBookmarks();
|
||
// Home tab will be created on DOMContentLoaded event
|
||
|
||
// Remove iframe-based navigation listener (using webview IPC now)
|
||
|
||
// Listen for site history updates from main process
|
||
ipcRenderer.on('record-site-history', (event, url) => {
|
||
console.log('[DEBUG] Received site history update:', url);
|
||
addToSiteHistory(url);
|
||
});
|
||
|
||
function createTab(inputUrl) {
|
||
inputUrl = inputUrl || 'browser://home';
|
||
console.log('[DEBUG] createTab() inputUrl =', inputUrl);
|
||
const id = crypto.randomUUID();
|
||
|
||
// Handle home page specially
|
||
if (inputUrl === 'browser://home') {
|
||
// Show home container and hide webviews
|
||
const homeContainer = document.getElementById('home-container');
|
||
const webviewsEl = document.getElementById('webviews');
|
||
if (homeContainer) homeContainer.classList.add('active');
|
||
if (webviewsEl) webviewsEl.classList.add('hidden');
|
||
const tab = {
|
||
id,
|
||
url: inputUrl,
|
||
title: 'New Tab',
|
||
favicon: '',
|
||
history: [inputUrl],
|
||
historyIndex: 0,
|
||
isHome: true
|
||
};
|
||
tabs.push(tab);
|
||
setActiveTab(id);
|
||
return id;
|
||
}
|
||
|
||
// For all other URLs, use webview
|
||
const resolvedUrl = resolveInternalUrl(inputUrl);
|
||
console.log('[DEBUG] createTab() resolvedUrl =', resolvedUrl);
|
||
|
||
const webview = document.createElement('webview');
|
||
|
||
webview.id = `tab-${id}`;
|
||
webview.src = resolvedUrl;
|
||
webview.setAttribute('allowpopups', '');
|
||
webview.setAttribute('partition', 'persist:default');
|
||
webview.setAttribute('preload', '../preload.js');
|
||
webview.classList.add('active');
|
||
|
||
webview.addEventListener('did-fail-load', handleLoadFail(id));
|
||
webview.addEventListener('page-title-updated', e => updateTabMetadata(id, 'title', e.title));
|
||
webview.addEventListener('page-favicon-updated', e => {
|
||
if (e.favicons.length > 0) updateTabMetadata(id, 'favicon', e.favicons[0]);
|
||
});
|
||
|
||
// Send bookmarks to home page when it loads
|
||
webview.addEventListener('dom-ready', () => {
|
||
if (inputUrl === 'browser://home') {
|
||
webview.executeJavaScript(`
|
||
if (window.receiveBookmarks) {
|
||
window.receiveBookmarks(${JSON.stringify(bookmarks)});
|
||
} else {
|
||
// Store bookmarks for when the page script loads
|
||
window._pendingBookmarks = ${JSON.stringify(bookmarks)};
|
||
}
|
||
`);
|
||
}
|
||
});
|
||
|
||
// Consolidated navigation recording - only use did-navigate to avoid duplicates
|
||
webview.addEventListener('did-navigate', e => {
|
||
handleNavigation(id, e.url);
|
||
// Record ALL HTTP navigations
|
||
if (e.url.startsWith('http')) {
|
||
console.log('[DEBUG] Recording navigation to:', e.url);
|
||
addToSiteHistory(e.url);
|
||
// Also save to file for cross-context sharing
|
||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
||
console.error('Failed to save to file:', err)
|
||
);
|
||
}
|
||
});
|
||
|
||
webview.addEventListener('did-navigate-in-page', e => {
|
||
handleNavigation(id, e.url);
|
||
// Record in-page navigations too
|
||
if (e.url.startsWith('http')) {
|
||
console.log('[DEBUG] Recording in-page navigation to:', e.url);
|
||
addToSiteHistory(e.url);
|
||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
||
console.error('Failed to save to file:', err)
|
||
);
|
||
}
|
||
});
|
||
|
||
// Also capture when pages finish loading
|
||
webview.addEventListener('did-finish-load', () => {
|
||
const currentUrl = webview.getURL();
|
||
if (currentUrl.startsWith('http') && !currentUrl.includes('browser://')) {
|
||
console.log('[DEBUG] Webview did-finish-load, recording:', currentUrl);
|
||
addToSiteHistory(currentUrl);
|
||
ipcRenderer.invoke('save-site-history-entry', currentUrl);
|
||
}
|
||
});
|
||
|
||
// catch any target="_blank" or window.open() calls and open them as new tabs
|
||
webview.addEventListener('new-window', e => {
|
||
e.preventDefault();
|
||
createTab(e.url);
|
||
});
|
||
|
||
webviewsEl.appendChild(webview);
|
||
|
||
tabs.push({
|
||
id,
|
||
url: inputUrl, // ← save the original input like "browser://home"
|
||
title: 'New Tab',
|
||
favicon: null,
|
||
history: [inputUrl],
|
||
historyIndex: 0
|
||
});
|
||
|
||
setActiveTab(id);
|
||
renderTabs();
|
||
}
|
||
|
||
|
||
|
||
function resolveInternalUrl(url) {
|
||
if (url.startsWith('browser://')) {
|
||
const page = url.replace('browser://', '');
|
||
if (allowedInternalPages.includes(page)) return `${page}.html`;
|
||
else return '404.html';
|
||
}
|
||
return url.startsWith('http') ? url : `https://${url}`;
|
||
}
|
||
|
||
|
||
function handleLoadFail(tabId) {
|
||
return (event) => {
|
||
if (!event.validatedURL.includes('browser://') && event.errorCode !== -3) {
|
||
const webview = document.getElementById(`tab-${tabId}`);
|
||
webview.src = `404.html?url=${encodeURIComponent(tabs.find(t => t.id === tabId).url)}`;
|
||
}
|
||
};
|
||
}
|
||
|
||
function updateTabMetadata(id, key, value) {
|
||
const tab = tabs.find(t => t.id === id);
|
||
if (tab) {
|
||
tab[key] = value;
|
||
renderTabs();
|
||
}
|
||
}
|
||
|
||
function navigate() {
|
||
const input = urlBox.value.trim();
|
||
const tab = tabs.find(t => t.id === activeTabId);
|
||
if (!tab) return;
|
||
|
||
// decide if this is a search query or a URL/internal page
|
||
const hasProtocol = /^https?:\/\//i.test(input);
|
||
const isInternal = input.startsWith('browser://');
|
||
const isLikelyUrl = hasProtocol || input.includes('.');
|
||
let resolved;
|
||
if (!isInternal && !isLikelyUrl) {
|
||
resolved = `https://www.google.com/search?q=${encodeURIComponent(input)}`;
|
||
} else {
|
||
resolved = resolveInternalUrl(input);
|
||
}
|
||
|
||
// If current tab is a home tab and we're navigating to a website,
|
||
// we need to convert it to a webview tab or create a new one
|
||
if (tab.isHome && !input.startsWith('browser://')) {
|
||
// Convert home tab to webview tab
|
||
convertHomeTabToWebview(tab.id, input, resolved);
|
||
return;
|
||
}
|
||
|
||
// For regular webview tabs, just navigate
|
||
const webview = document.getElementById(`tab-${activeTabId}`);
|
||
if (!webview) return;
|
||
|
||
// Push to history using the original input
|
||
tab.history = tab.history.slice(0, tab.historyIndex + 1);
|
||
tab.history.push(input);
|
||
tab.historyIndex++;
|
||
|
||
tab.url = input;
|
||
webview.src = resolved;
|
||
|
||
renderTabs();
|
||
updateNavButtons();
|
||
}
|
||
|
||
function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
|
||
const tab = tabs.find(t => t.id === tabId);
|
||
if (!tab) return;
|
||
|
||
// Ensure webviews container is visible
|
||
const webviewsEl = document.getElementById('webviews');
|
||
if (webviewsEl) webviewsEl.classList.remove('hidden');
|
||
// Create a new webview for this tab
|
||
const webview = document.createElement('webview');
|
||
webview.id = `tab-${tabId}`;
|
||
webview.src = resolvedUrl;
|
||
webview.setAttribute('allowpopups', '');
|
||
webview.setAttribute('partition', 'persist:default');
|
||
webview.setAttribute('preload', '../preload.js');
|
||
|
||
// Add event listeners
|
||
webview.addEventListener('did-fail-load', handleLoadFail(tabId));
|
||
webview.addEventListener('page-title-updated', e => updateTabMetadata(tabId, 'title', e.title));
|
||
webview.addEventListener('page-favicon-updated', e => {
|
||
if (e.favicons.length > 0) updateTabMetadata(tabId, 'favicon', e.favicons[0]);
|
||
});
|
||
|
||
webview.addEventListener('did-navigate', e => {
|
||
handleNavigation(tabId, e.url);
|
||
if (e.url.startsWith('http')) {
|
||
addToSiteHistory(e.url);
|
||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
||
console.error('Failed to save to file:', err)
|
||
);
|
||
}
|
||
});
|
||
|
||
webview.addEventListener('did-navigate-in-page', e => {
|
||
handleNavigation(tabId, e.url);
|
||
if (e.url.startsWith('http')) {
|
||
addToSiteHistory(e.url);
|
||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
||
console.error('Failed to save to file:', err)
|
||
);
|
||
}
|
||
});
|
||
|
||
webview.addEventListener('did-finish-load', () => {
|
||
const currentUrl = webview.getURL();
|
||
if (currentUrl.startsWith('http') && !currentUrl.includes('browser://')) {
|
||
addToSiteHistory(currentUrl);
|
||
ipcRenderer.invoke('save-site-history-entry', currentUrl);
|
||
}
|
||
});
|
||
|
||
webview.addEventListener('new-window', e => {
|
||
createTab(e.url);
|
||
});
|
||
|
||
// Add webview to DOM
|
||
webviewsEl.appendChild(webview);
|
||
|
||
// Update tab properties
|
||
tab.isHome = false;
|
||
tab.webview = webview;
|
||
tab.url = inputUrl;
|
||
tab.history = [inputUrl];
|
||
tab.historyIndex = 0;
|
||
|
||
// Hide home container and show webview
|
||
const homeContainer = document.getElementById('home-container');
|
||
if (homeContainer) homeContainer.classList.remove('active');
|
||
webview.classList.add('active');
|
||
|
||
updateNavButtons();
|
||
// Activate converted webview tab and update UI
|
||
setActiveTab(tabId);
|
||
renderTabs();
|
||
}
|
||
|
||
function handleNavigation(tabId, newUrl) {
|
||
const tab = tabs.find(t => t.id === tabId);
|
||
if (!tab) return;
|
||
|
||
console.log('[DEBUG] handleNavigation called with:', newUrl);
|
||
|
||
// --- record every real navigation into history ---
|
||
if (tab.history[tab.historyIndex] !== newUrl) {
|
||
tab.history = tab.history.slice(0, tab.historyIndex + 1);
|
||
tab.history.push(newUrl);
|
||
tab.historyIndex++;
|
||
}
|
||
|
||
// Record site history in localStorage (skip internal pages and file:// URLs)
|
||
if (!newUrl.endsWith('home.html') &&
|
||
!newUrl.endsWith('settings.html') &&
|
||
!newUrl.startsWith('file://') &&
|
||
!newUrl.includes('browser://') &&
|
||
newUrl.startsWith('http')) {
|
||
console.log('[DEBUG] Adding to site history:', newUrl);
|
||
addToSiteHistory(newUrl);
|
||
// Also send to main process for file storage
|
||
ipcRenderer.invoke('save-site-history-entry', newUrl);
|
||
}
|
||
|
||
// translate local files back to our browser:// scheme
|
||
const isHome = newUrl.endsWith('home.html');
|
||
const isSettings = newUrl.endsWith('settings.html');
|
||
const displayUrl = isHome
|
||
? 'browser://home'
|
||
: isSettings
|
||
? 'browser://settings'
|
||
: newUrl;
|
||
|
||
tab.url = displayUrl;
|
||
|
||
if (tabId === activeTabId) {
|
||
urlBox.value = displayUrl === 'browser://home' ? '' : displayUrl;
|
||
}
|
||
|
||
renderTabs();
|
||
updateNavButtons();
|
||
}
|
||
|
||
|
||
function setActiveTab(id) {
|
||
// hide all individual webviews
|
||
tabs.forEach(t => {
|
||
const w = document.getElementById(`tab-${t.id}`);
|
||
if (w) w.classList.remove('active');
|
||
});
|
||
// toggle containers
|
||
const homeContainer = document.getElementById('home-container');
|
||
const webviewsEl = document.getElementById('webviews');
|
||
|
||
const tab = tabs.find(t => t.id === id);
|
||
if (tab) {
|
||
if (tab.isHome) {
|
||
homeContainer.classList.add('active');
|
||
webviewsEl.classList.add('hidden');
|
||
} else {
|
||
if (homeContainer) homeContainer.classList.remove('active');
|
||
webviewsEl.classList.remove('hidden');
|
||
const activeWebview = document.getElementById(`tab-${id}`);
|
||
if (activeWebview) activeWebview.classList.add('active');
|
||
}
|
||
}
|
||
|
||
activeTabId = 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();
|
||
updateNavButtons();
|
||
updateZoomUI(); // ← update zoom display for new active tab
|
||
}
|
||
}
|
||
|
||
function closeTab(id) {
|
||
const w = document.getElementById(`tab-${id}`);
|
||
if (w) w.remove();
|
||
|
||
tabs = tabs.filter(t => t.id !== id);
|
||
|
||
if (id === activeTabId) {
|
||
if (tabs.length > 0) setActiveTab(tabs[0].id);
|
||
}
|
||
|
||
renderTabs();
|
||
updateNavButtons();
|
||
}
|
||
|
||
// 2) streamline renderTabs with a fragment
|
||
function renderTabs() {
|
||
const frag = document.createDocumentFragment();
|
||
tabs.forEach(tab => {
|
||
const el = document.createElement('div');
|
||
el.className = 'tab' + (tab.id === activeTabId ? ' active' : '');
|
||
|
||
if (tab.favicon) {
|
||
const icon = document.createElement('img');
|
||
icon.src = tab.favicon;
|
||
icon.style.width = '16px';
|
||
icon.style.height = '16px';
|
||
icon.style.marginRight = '6px';
|
||
el.appendChild(icon);
|
||
}
|
||
|
||
el.appendChild(document.createTextNode(tab.title || new URL(tab.url).hostname));
|
||
|
||
const closeBtn = document.createElement('button');
|
||
closeBtn.textContent = '×';
|
||
closeBtn.onclick = (e) => {
|
||
e.stopPropagation();
|
||
closeTab(tab.id);
|
||
};
|
||
|
||
// 2a) make tab draggable
|
||
el.draggable = true;
|
||
el.addEventListener('dragstart', e => {
|
||
e.dataTransfer.setData('tabId', tab.id);
|
||
});
|
||
|
||
// 2b) on dragend outside window, open in new window and close here
|
||
el.addEventListener('dragend', e => {
|
||
if (
|
||
e.clientX < 0 || e.clientX > window.innerWidth ||
|
||
e.clientY < 0 || e.clientY > window.innerHeight
|
||
) {
|
||
ipcRenderer.invoke('open-tab-in-new-window', tab.url);
|
||
closeTab(tab.id);
|
||
}
|
||
});
|
||
|
||
el.onclick = () => setActiveTab(tab.id);
|
||
el.appendChild(closeBtn);
|
||
frag.appendChild(el);
|
||
});
|
||
// add the “+” at the end
|
||
const plus = document.createElement('div');
|
||
plus.className = 'tab'; plus.textContent = '+'; plus.onclick = () => createTab();
|
||
frag.appendChild(plus);
|
||
|
||
tabBarEl.innerHTML = ''; // clear once
|
||
tabBarEl.appendChild(frag); // append in one shot
|
||
}
|
||
|
||
// 1) handle URL sent by main for a detached window
|
||
ipcRenderer.on('open-url', (event, url) => {
|
||
tabs = [];
|
||
activeTabId = null;
|
||
webviewsEl.innerHTML = '';
|
||
tabBarEl.innerHTML = '';
|
||
createTab(url);
|
||
});
|
||
|
||
function goBack() {
|
||
const webview = document.getElementById(`tab-${activeTabId}`);
|
||
if (webview && webview.canGoBack()) {
|
||
webview.goBack();
|
||
}
|
||
}
|
||
|
||
function goForward() {
|
||
const webview = document.getElementById(`tab-${activeTabId}`);
|
||
if (webview && webview.canGoForward()) {
|
||
webview.goForward();
|
||
}
|
||
}
|
||
|
||
function updateNavButtons() {
|
||
const webview = document.getElementById(`tab-${activeTabId}`);
|
||
const backBtn = document.querySelector('.nav-left button:nth-child(1)');
|
||
const forwardBtn = document.querySelector('.nav-left button:nth-child(2)');
|
||
|
||
backBtn.disabled = !webview || !webview.canGoBack();
|
||
forwardBtn.disabled = !webview || !webview.canGoForward();
|
||
}
|
||
|
||
function reload() {
|
||
const webview = document.getElementById(`tab-${activeTabId}`);
|
||
if (webview) {
|
||
webview.reload();
|
||
updateNavButtons(); // keep back/forward buttons in sync after a reload
|
||
}
|
||
}
|
||
|
||
|
||
function openSettings() {
|
||
urlBox.value = 'browser://settings';
|
||
navigate();
|
||
}
|
||
|
||
// Toggle menu dropdown
|
||
const menuBtn = document.getElementById('menu-btn');
|
||
|
||
menuBtn.addEventListener('click', () => {
|
||
menuPopup.classList.toggle('hidden');
|
||
if (!menuPopup.classList.contains('hidden')) {
|
||
updateZoomUI(); // ← refresh zoom % whenever menu opens
|
||
}
|
||
});
|
||
|
||
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());
|
||
}
|
||
|
||
createTab();
|
||
// Listen for navigation IPC messages from home webview
|
||
const homeWebview = document.getElementById('home-webview');
|
||
if (homeWebview) {
|
||
homeWebview.addEventListener('ipc-message', e => {
|
||
if (e.channel === 'navigate' && e.args[0]) {
|
||
urlBox.value = e.args[0];
|
||
navigate();
|
||
}
|
||
});
|
||
}
|
||
// only now bind the reload button (guaranteed to exist)
|
||
const reloadBtn = document.getElementById('reload-btn');
|
||
reloadBtn.addEventListener('click', reload);
|
||
|
||
// bind zoom buttons (single binding)
|
||
const zoomInBtn = document.getElementById('zoom-in-btn');
|
||
const zoomOutBtn = document.getElementById('zoom-out-btn');
|
||
zoomInBtn.addEventListener('click', zoomIn);
|
||
zoomOutBtn.addEventListener('click', zoomOut);
|
||
|
||
// wire up back/forward buttons
|
||
const backBtn = document.querySelector('.nav-left button:nth-child(1)');
|
||
const forwardBtn = document.querySelector('.nav-left button:nth-child(2)');
|
||
backBtn.addEventListener('click', goBack);
|
||
forwardBtn.addEventListener('click', goForward);
|
||
|
||
// window control bindings
|
||
const minBtn = document.getElementById('min-btn');
|
||
const maxBtn = document.getElementById('max-btn');
|
||
const closeBtn = document.getElementById('close-btn');
|
||
if (minBtn && maxBtn && closeBtn) {
|
||
if (process.platform !== 'darwin') {
|
||
minBtn.addEventListener('click', () => ipcRenderer.invoke('window-minimize'));
|
||
maxBtn.addEventListener('click', () => ipcRenderer.invoke('window-maximize'));
|
||
closeBtn.addEventListener('click', () => ipcRenderer.invoke('window-close'));
|
||
} else {
|
||
document.getElementById('window-controls').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// update initial zoom display
|
||
ipcRenderer.invoke('get-zoom-factor').then(z => {
|
||
document.getElementById('zoom-percent').textContent = `${Math.round(z * 100)}%`;
|
||
});
|
||
|
||
// menu‐related 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');
|
||
});
|
||
});
|
||
|
||
// Migrate existing site history from JSON file to localStorage (one-time migration)
|
||
const migrateSiteHistory = async () => {
|
||
try {
|
||
// Check if we already have data in localStorage
|
||
const existingHistory = getSiteHistory();
|
||
if (existingHistory.length === 0) {
|
||
// Try to load from the old JSON file system
|
||
console.log('Attempting to migrate site history from JSON file...');
|
||
// Since we can't access the file directly, we'll just start fresh
|
||
// The site-history.json file was the old method, localStorage is the new method
|
||
}
|
||
} catch (err) {
|
||
console.log('Site history migration skipped:', err.message);
|
||
}
|
||
};
|
||
migrateSiteHistory();
|
||
|
||
// ipcRenderer.invoke('load-bookmarks').then(bs => {
|
||
// bookmarks = bs;
|
||
// console.log('[DEBUG] Loaded bookmarks:', bookmarks);
|
||
// });
|
||
});
|
||
|
||
// zoom helpers
|
||
function updateZoomUI() {
|
||
const zp = document.getElementById('zoom-percent');
|
||
if (zp) {
|
||
ipcRenderer.invoke('get-zoom-factor').then(zf => {
|
||
// just show "NN%", not "Zoom: NN%"
|
||
zp.textContent = `${Math.round(zf * 100)}%`;
|
||
});
|
||
}
|
||
}
|
||
|
||
function zoomIn() { ipcRenderer.invoke('zoom-in').then(updateZoomUI); }
|
||
function zoomOut() { ipcRenderer.invoke('zoom-out').then(updateZoomUI); }
|
||
|
||
// Attempt to load Node modules if available for context-menu actions
|
||
let fs, remote;
|
||
try {
|
||
fs = require('fs');
|
||
remote = require('electron').remote;
|
||
} catch (err) {
|
||
console.warn('fs or remote modules unavailable in renderer:', err);
|
||
}
|
||
|
||
// 4) unify context-menu wiring
|
||
function showContextMenu(x,y) {
|
||
if (!contextMenu) return;
|
||
contextMenu.style.top = `${y}px`;
|
||
contextMenu.style.left = `${x}px`;
|
||
contextMenu.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', ()=> contextMenu && contextMenu.classList.remove('visible'));
|
||
menuItems.forEach(item => item.addEventListener('click', async evt => {
|
||
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;
|
||
}
|
||
|
||
contextMenu.classList.remove('visible');
|
||
}));
|