Bookmarks saved to json added

This commit is contained in:
2025-07-31 21:33:58 +12:00
parent 870c52b4a6
commit 0f93274d84
8 changed files with 356 additions and 43 deletions
+17 -1
View File
@@ -1 +1,17 @@
[] [
{
"title": "Google",
"url": "https://www.google.com",
"icon": "search"
},
{
"title": "GitHub",
"url": "https://github.com",
"icon": "code"
},
{
"title": "YouTube",
"url": "https://www.youtube.com",
"icon": "play_arrow"
}
]
+47
View File
@@ -375,6 +375,53 @@ ipcMain.on('homepage-changed', (event, url) => {
console.log('[MAIN] homepage-changed →', url); console.log('[MAIN] homepage-changed →', url);
}); });
// Bookmark management
ipcMain.handle('load-bookmarks', async () => {
try {
const bookmarksPath = path.join(__dirname, 'bookmarks.json');
if (fs.existsSync(bookmarksPath)) {
const data = fs.readFileSync(bookmarksPath, 'utf8');
const bookmarks = JSON.parse(data);
console.log(`Loaded ${bookmarks.length} bookmarks from file`);
return bookmarks;
}
console.log('No bookmarks file found, starting with empty array');
return [];
} catch (error) {
console.error('Error loading bookmarks:', error);
// Try to create a backup if the file is corrupted
const bookmarksPath = path.join(__dirname, 'bookmarks.json');
const backupPath = path.join(__dirname, `bookmarks.backup.${Date.now()}.json`);
try {
if (fs.existsSync(bookmarksPath)) {
fs.copyFileSync(bookmarksPath, backupPath);
console.log(`Corrupted bookmarks file backed up to: ${backupPath}`);
}
} catch (backupError) {
console.error('Failed to create backup:', backupError);
}
return [];
}
});
ipcMain.handle('save-bookmarks', async (event, bookmarks) => {
try {
const bookmarksPath = path.join(__dirname, 'bookmarks.json');
// Create a backup before saving
if (fs.existsSync(bookmarksPath)) {
const backupPath = path.join(__dirname, 'bookmarks.backup.json');
fs.copyFileSync(bookmarksPath, backupPath);
}
fs.writeFileSync(bookmarksPath, JSON.stringify(bookmarks, null, 2));
console.log(`Saved ${bookmarks.length} bookmarks to file`);
return true;
} catch (error) {
console.error('Error saving bookmarks:', error);
return false;
}
});
ipcMain.handle('clear-browser-data', async () => { ipcMain.handle('clear-browser-data', async () => {
try { try {
+8
View File
@@ -17,6 +17,14 @@ const electronAPI = {
console.error('IPC send error:', err); console.error('IPC send error:', err);
} }
}, },
// Send message to embedding page (webview host)
sendToHost: (ch, ...args) => {
try {
return ipcRenderer.sendToHost(ch, ...args);
} catch (err) {
console.error('IPC sendToHost error:', err);
}
},
invoke: (ch, ...args) => { invoke: (ch, ...args) => {
try { try {
return ipcRenderer.invoke(ch, ...args); return ipcRenderer.invoke(ch, ...args);
+47 -13
View File
@@ -1,7 +1,5 @@
import { icons as initialIcons, fetchAllIcons } from './icons.js'; import { icons as initialIcons, fetchAllIcons } from './icons.js';
const BOOKMARKS_KEY = 'steamos_browser_bookmarks';
const bookmarkList = document.getElementById('bookmarkList'); const bookmarkList = document.getElementById('bookmarkList');
const titleInput = document.getElementById('titleInput'); const titleInput = document.getElementById('titleInput');
const urlInput = document.getElementById('urlInput'); const urlInput = document.getElementById('urlInput');
@@ -26,18 +24,44 @@ const searchEngines = {
}; };
let selectedSearchEngine = 'google'; let selectedSearchEngine = 'google';
let bookmarks = JSON.parse(localStorage.getItem(BOOKMARKS_KEY)) || []; let bookmarks = [];
function saveBookmarks() { // Load bookmarks from main via Electron IPC
localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks)); // Load bookmarks via contextBridge API
async function loadBookmarks() {
try {
let data = [];
// Use bookmarksAPI if available
if (window.bookmarksAPI && typeof window.bookmarksAPI.load === 'function') {
data = await window.bookmarksAPI.load();
} else if (window.electronAPI && typeof window.electronAPI.invoke === 'function') {
data = await window.electronAPI.invoke('load-bookmarks');
} else {
console.error('No API available to load bookmarks');
}
return Array.isArray(data) ? data : [];
} catch (error) {
console.error('Error loading bookmarks:', error);
return [];
}
} }
// Save bookmarks to main process
// Save bookmarks via contextBridge API
async function saveBookmarks() {
try {
await window.bookmarksAPI.save(bookmarks);
} catch (error) {
console.error('Error saving bookmarks:', error);
}
}
// Render bookmarks
function renderBookmarks() { function renderBookmarks() {
const list = JSON.parse(localStorage.getItem(BOOKMARKS_KEY) || '[]');
bookmarkList.innerHTML = ''; bookmarkList.innerHTML = '';
// Render each bookmark // Render each bookmark
list.forEach((b, index) => { bookmarks.forEach((b, index) => {
const box = document.createElement('div'); const box = document.createElement('div');
box.className = 'bookmark'; box.className = 'bookmark';
@@ -54,14 +78,21 @@ function renderBookmarks() {
const close = document.createElement('button'); const close = document.createElement('button');
close.textContent = '×'; close.textContent = '×';
close.className = 'delete-btn'; close.className = 'delete-btn';
close.onclick = (e) => { close.onclick = async (e) => {
e.stopPropagation(); e.stopPropagation();
bookmarks.splice(index, 1); bookmarks.splice(index, 1);
saveBookmarks(); await saveBookmarks();
renderBookmarks(); renderBookmarks();
}; };
box.onclick = () => window.location.href = b.url; // Navigate via IPC to host page
box.onclick = () => {
if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
window.electronAPI.sendToHost('navigate', b.url);
} else {
console.error('Unable to send navigation IPC to host');
}
};
box.appendChild(label); box.appendChild(label);
box.appendChild(close); box.appendChild(close);
@@ -121,14 +152,14 @@ renderIconGrid();
} }
})(); })();
saveBookmarkBtn.onclick = () => { saveBookmarkBtn.onclick = async () => {
const title = titleInput.value.trim(); const title = titleInput.value.trim();
const url = urlInput.value.trim(); const url = urlInput.value.trim();
const icon = selectedIcon; const icon = selectedIcon;
if (!title || !url) return; if (!title || !url) return;
bookmarks.push({ title, url, icon }); bookmarks.push({ title, url, icon });
saveBookmarks(); await saveBookmarks();
renderBookmarks(); renderBookmarks();
titleInput.value = ''; titleInput.value = '';
@@ -181,5 +212,8 @@ searchInput.addEventListener('keydown', e => {
if (e.key === 'Enter') searchBtn.click(); if (e.key === 'Enter') searchBtn.click();
}); });
// initial render from localStorage // Load and render bookmarks immediately
(async () => {
bookmarks = await loadBookmarks();
renderBookmarks(); renderBookmarks();
})();
+11 -17
View File
@@ -56,25 +56,19 @@
</div> </div>
</div> </div>
<div id="webviews"> <div id="webviews" class="hidden"></div>
<webview id="webview" src="https://example.com"></webview>
<!-- Home page container for direct loading -->
<div id="home-container" class="active">
<webview id="home-webview"
src="home.html"
preload="../preload.js"
partition="persist:default"
allowpopups
style="width:100%; height:100%; border:none;">
</webview>
</div> </div>
<script src="script.js"></script> <script src="script.js"></script>
<script>
const { ipcRenderer } = require('electron');
const webview = document.getElementById('webview');
webview.addEventListener('did-navigate', (e) => {
// save site URL
ipcRenderer.invoke('save-site-history', e.url);
// extract search query if present
const m = /[?&](?:q|query)=([^&]+)/.exec(e.url);
if (m && m[1]) {
const query = decodeURIComponent(m[1].replace(/\+/g, ' '));
ipcRenderer.invoke('save-search-history', query);
}
});
</script>
</body> </body>
</html> </html>
+181 -6
View File
@@ -53,6 +53,34 @@ let activeTabId = null;
const allowedInternalPages = ['settings', 'home']; const allowedInternalPages = ['settings', 'home'];
let bookmarks = []; 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();
// Create initial home tab on startup
createTab();
// Remove iframe-based navigation listener (using webview IPC now)
// Listen for site history updates from main process // Listen for site history updates from main process
ipcRenderer.on('record-site-history', (event, url) => { ipcRenderer.on('record-site-history', (event, url) => {
console.log('[DEBUG] Received site history update:', url); console.log('[DEBUG] Received site history update:', url);
@@ -63,6 +91,29 @@ function createTab(inputUrl) {
inputUrl = inputUrl || 'browser://home'; inputUrl = inputUrl || 'browser://home';
console.log('[DEBUG] createTab() inputUrl =', inputUrl); console.log('[DEBUG] createTab() inputUrl =', inputUrl);
const id = crypto.randomUUID(); 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); const resolvedUrl = resolveInternalUrl(inputUrl);
console.log('[DEBUG] createTab() resolvedUrl =', resolvedUrl); console.log('[DEBUG] createTab() resolvedUrl =', resolvedUrl);
@@ -72,6 +123,7 @@ function createTab(inputUrl) {
webview.src = resolvedUrl; webview.src = resolvedUrl;
webview.setAttribute('allowpopups', ''); webview.setAttribute('allowpopups', '');
webview.setAttribute('partition', 'persist:default'); webview.setAttribute('partition', 'persist:default');
webview.setAttribute('preload', '../preload.js');
webview.classList.add('active'); webview.classList.add('active');
webview.addEventListener('did-fail-load', handleLoadFail(id)); webview.addEventListener('did-fail-load', handleLoadFail(id));
@@ -80,6 +132,20 @@ function createTab(inputUrl) {
if (e.favicons.length > 0) updateTabMetadata(id, 'favicon', e.favicons[0]); 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 // Consolidated navigation recording - only use did-navigate to avoid duplicates
webview.addEventListener('did-navigate', e => { webview.addEventListener('did-navigate', e => {
handleNavigation(id, e.url); handleNavigation(id, e.url);
@@ -169,8 +235,7 @@ function updateTabMetadata(id, key, value) {
function navigate() { function navigate() {
const input = urlBox.value.trim(); const input = urlBox.value.trim();
const tab = tabs.find(t => t.id === activeTabId); const tab = tabs.find(t => t.id === activeTabId);
const webview = document.getElementById(`tab-${activeTabId}`); if (!tab) return;
if (!tab || !webview) return;
// decide if this is a search query or a URL/internal page // decide if this is a search query or a URL/internal page
const hasProtocol = /^https?:\/\//i.test(input); const hasProtocol = /^https?:\/\//i.test(input);
@@ -183,6 +248,18 @@ function navigate() {
resolved = resolveInternalUrl(input); 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 // Push to history using the original input
tab.history = tab.history.slice(0, tab.historyIndex + 1); tab.history = tab.history.slice(0, tab.historyIndex + 1);
tab.history.push(input); tab.history.push(input);
@@ -195,6 +272,75 @@ function navigate() {
updateNavButtons(); updateNavButtons();
} }
function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
const tab = tabs.find(t => t.id === tabId);
if (!tab) return;
// 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();
}
function handleNavigation(tabId, newUrl) { function handleNavigation(tabId, newUrl) {
const tab = tabs.find(t => t.id === tabId); const tab = tabs.find(t => t.id === tabId);
if (!tab) return; if (!tab) return;
@@ -241,17 +387,30 @@ function handleNavigation(tabId, newUrl) {
function setActiveTab(id) { function setActiveTab(id) {
// hide all individual webviews
tabs.forEach(t => { tabs.forEach(t => {
const w = document.getElementById(`tab-${t.id}`); const w = document.getElementById(`tab-${t.id}`);
if (w) w.classList.remove('active'); 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}`); const activeWebview = document.getElementById(`tab-${id}`);
if (activeWebview) activeWebview.classList.add('active'); if (activeWebview) activeWebview.classList.add('active');
}
}
activeTabId = id; activeTabId = id;
const tab = tabs.find(t => t.id === id);
if (tab) { if (tab) {
// If the tab URL represents the home page, keep the URL bar blank. // If the tab URL represents the home page, keep the URL bar blank.
urlBox.value = tab.url === 'browser://home' ? '' : tab.url; urlBox.value = tab.url === 'browser://home' ? '' : tab.url;
@@ -399,6 +558,16 @@ window.addEventListener('DOMContentLoaded', () => {
} }
createTab(); 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) // only now bind the reload button (guaranteed to exist)
const reloadBtn = document.getElementById('reload-btn'); const reloadBtn = document.getElementById('reload-btn');
reloadBtn.addEventListener('click', reload); reloadBtn.addEventListener('click', reload);
@@ -435,7 +604,7 @@ window.addEventListener('DOMContentLoaded', () => {
}); });
// menurelated code (moved here so #context-menu exists) // menurelated code (moved here so #context-menu exists)
const items = menu ? menu.querySelectorAll('li') : []; const items = contextMenu ? contextMenu.querySelectorAll('li') : [];
function showContextMenu(x, y) { function showContextMenu(x, y) {
if (!menu) return; if (!menu) return;
@@ -531,8 +700,14 @@ function updateZoomUI() {
function zoomIn() { ipcRenderer.invoke('zoom-in').then(updateZoomUI); } function zoomIn() { ipcRenderer.invoke('zoom-in').then(updateZoomUI); }
function zoomOut() { ipcRenderer.invoke('zoom-out').then(updateZoomUI); } function zoomOut() { ipcRenderer.invoke('zoom-out').then(updateZoomUI); }
const fs = require('fs'); // Attempt to load Node modules if available for context-menu actions
const { remote } = require('electron'); 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 // 4) unify context-menu wiring
function showContextMenu(x,y) { function showContextMenu(x,y) {
+32 -2
View File
@@ -130,14 +130,44 @@ html, body {
width: 100%; width: 100%;
position: relative; position: relative;
} }
#webviews.hidden {
display: none;
}
#webviews webview { #webviews webview {
flex: 1; flex: 1;
display: none;
border: none; border: none;
display: none;
}
#webviews webview.active {
display: flex;
}
/* When webviews is hidden, collapse its flex size */
#webviews.hidden {
flex: 0;
} }
#webviews webview.active { /* HOME CONTAINER */
#home-container {
flex: 1;
display: none;
width: 100%;
position: relative;
}
#home-container.active {
display: flex;
}
#home-webview {
width: 100%;
height: 100%;
border: none;
display: none;
flex: 1;
}
/* Show home webview when container is active */
#home-container.active > #home-webview {
display: flex; display: flex;
} }
+10 -1
View File
@@ -1,10 +1,19 @@
[ [
"https://www.youtube.com/",
"https://github.com/",
"file:///X:/Projects/Code/NebulaBrowser/renderer/index.html",
"https://youtube.com/",
"file:///X:/Projects/Code/NebulaBrowser/renderer/index.html",
"https://youtube.com/",
"file:///X:/Projects/Code/NebulaBrowser/renderer/index.html",
"file:///X:/Projects/Code/NebulaBrowser/renderer/index.html",
"https://www.google.com/",
"file:///X:/Projects/Code/NebulaBrowser/renderer/index.html",
"file:///X:/Projects/Code/NebulaBrowser/renderer/index.html", "file:///X:/Projects/Code/NebulaBrowser/renderer/index.html",
"file:///Users/andrewzambazos/Repositories/NebulaBrowser/renderer/index.html", "file:///Users/andrewzambazos/Repositories/NebulaBrowser/renderer/index.html",
"https://inscribe.zambazosmedia.group/renderer/editor.html", "https://inscribe.zambazosmedia.group/renderer/editor.html",
"https://inscribe.zambazosmedia.group/", "https://inscribe.zambazosmedia.group/",
"file:///Users/andrewzambazos/Repositories/NebulaBrowser/renderer/index.html", "file:///Users/andrewzambazos/Repositories/NebulaBrowser/renderer/index.html",
"https://www.youtube.com/",
"https://www.youtube.com/watch?v=9FuNtfsnRNo", "https://www.youtube.com/watch?v=9FuNtfsnRNo",
"https://youtube.com/", "https://youtube.com/",
"http://homelab.andrewzambazos.com:8081/", "http://homelab.andrewzambazos.com:8081/",