0b61f86dd4
Site history is now managed in localStorage for faster access and better cross-tab experience, with file sync for persistence. Added a dropdown for quick site history access in the address bar, updated settings page to display and manage site history from localStorage, and improved history recording logic in both main and renderer processes. Also added debug info and test data utilities for development.
668 lines
20 KiB
JavaScript
668 lines
20 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') : [];
|
||
|
||
let siteHistoryDropdown = null;
|
||
|
||
function initializeSiteHistoryDropdown() {
|
||
// Create site history dropdown
|
||
siteHistoryDropdown = document.createElement('div');
|
||
siteHistoryDropdown.id = 'site-history-dropdown';
|
||
siteHistoryDropdown.style.cssText = `
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
background: white;
|
||
border: 1px solid #ccc;
|
||
border-top: none;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
z-index: 1000;
|
||
display: none;
|
||
`;
|
||
|
||
if (urlBox && urlBox.parentElement) {
|
||
urlBox.parentElement.style.position = 'relative';
|
||
urlBox.parentElement.appendChild(siteHistoryDropdown);
|
||
}
|
||
}
|
||
|
||
function showSiteHistory() {
|
||
if (!siteHistoryDropdown) return;
|
||
|
||
const history = getSiteHistory();
|
||
if (history.length === 0) {
|
||
siteHistoryDropdown.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
siteHistoryDropdown.innerHTML = '';
|
||
history.slice(0, 10).forEach(url => {
|
||
const item = document.createElement('div');
|
||
item.style.cssText = `
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
border-bottom: 1px solid #eee;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
`;
|
||
item.textContent = url;
|
||
item.addEventListener('mouseenter', () => item.style.backgroundColor = '#f0f0f0');
|
||
item.addEventListener('mouseleave', () => item.style.backgroundColor = 'white');
|
||
item.addEventListener('click', () => {
|
||
urlBox.value = url;
|
||
navigate();
|
||
hideSiteHistory();
|
||
});
|
||
siteHistoryDropdown.appendChild(item);
|
||
});
|
||
|
||
siteHistoryDropdown.style.display = 'block';
|
||
}
|
||
|
||
function hideSiteHistory() {
|
||
if (siteHistoryDropdown) {
|
||
siteHistoryDropdown.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Select all text on focus and prevent mouseup from deselecting
|
||
urlBox.addEventListener('focus', () => {
|
||
urlBox.select();
|
||
showSiteHistory();
|
||
});
|
||
urlBox.addEventListener('mouseup', e => e.preventDefault());
|
||
// Add Enter key navigation
|
||
urlBox.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
navigate();
|
||
hideSiteHistory();
|
||
} else if (e.key === 'Escape') {
|
||
hideSiteHistory();
|
||
}
|
||
});
|
||
|
||
// Hide history when clicking outside
|
||
document.addEventListener('click', (e) => {
|
||
if (!urlBox.contains(e.target) && (!siteHistoryDropdown || !siteHistoryDropdown.contains(e.target))) {
|
||
hideSiteHistory();
|
||
}
|
||
});
|
||
|
||
|
||
let tabs = [];
|
||
let activeTabId = null;
|
||
const allowedInternalPages = ['settings', 'home'];
|
||
let bookmarks = [];
|
||
|
||
// 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();
|
||
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.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]);
|
||
});
|
||
|
||
// 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);
|
||
const webview = document.getElementById(`tab-${activeTabId}`);
|
||
if (!tab || !webview) 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);
|
||
}
|
||
|
||
// 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 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) {
|
||
tabs.forEach(t => {
|
||
const w = document.getElementById(`tab-${t.id}`);
|
||
if (w) w.classList.remove('active');
|
||
});
|
||
|
||
const activeWebview = document.getElementById(`tab-${id}`);
|
||
if (activeWebview) activeWebview.classList.add('active');
|
||
|
||
activeTabId = id;
|
||
|
||
const tab = tabs.find(t => t.id === 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', () => {
|
||
// Initialize site history dropdown after DOM is ready
|
||
initializeSiteHistoryDropdown();
|
||
|
||
// 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();
|
||
// 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 = menu ? menu.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); }
|
||
|
||
const fs = require('fs');
|
||
const { remote } = require('electron');
|
||
|
||
// 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');
|
||
}));
|