diff --git a/bookmarks.json b/bookmarks.json index 0637a08..c1c7ba7 100644 --- a/bookmarks.json +++ b/bookmarks.json @@ -1 +1,17 @@ -[] \ No newline at end of file +[ + { + "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" + } +] \ No newline at end of file diff --git a/main.js b/main.js index d478531..180f25a 100644 --- a/main.js +++ b/main.js @@ -375,6 +375,53 @@ ipcMain.on('homepage-changed', (event, 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 () => { try { diff --git a/preload.js b/preload.js index b804c96..ccf7d7d 100644 --- a/preload.js +++ b/preload.js @@ -17,6 +17,14 @@ const electronAPI = { 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) => { try { return ipcRenderer.invoke(ch, ...args); diff --git a/renderer/home.js b/renderer/home.js index 308424d..9e64858 100644 --- a/renderer/home.js +++ b/renderer/home.js @@ -1,7 +1,5 @@ import { icons as initialIcons, fetchAllIcons } from './icons.js'; -const BOOKMARKS_KEY = 'steamos_browser_bookmarks'; - const bookmarkList = document.getElementById('bookmarkList'); const titleInput = document.getElementById('titleInput'); const urlInput = document.getElementById('urlInput'); @@ -26,18 +24,44 @@ const searchEngines = { }; let selectedSearchEngine = 'google'; -let bookmarks = JSON.parse(localStorage.getItem(BOOKMARKS_KEY)) || []; +let bookmarks = []; -function saveBookmarks() { - localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks)); +// Load bookmarks from main via Electron IPC +// 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() { - const list = JSON.parse(localStorage.getItem(BOOKMARKS_KEY) || '[]'); bookmarkList.innerHTML = ''; // Render each bookmark - list.forEach((b, index) => { + bookmarks.forEach((b, index) => { const box = document.createElement('div'); box.className = 'bookmark'; @@ -54,14 +78,21 @@ function renderBookmarks() { const close = document.createElement('button'); close.textContent = '×'; close.className = 'delete-btn'; - close.onclick = (e) => { + close.onclick = async (e) => { e.stopPropagation(); bookmarks.splice(index, 1); - saveBookmarks(); + await saveBookmarks(); 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(close); @@ -121,14 +152,14 @@ renderIconGrid(); } })(); -saveBookmarkBtn.onclick = () => { +saveBookmarkBtn.onclick = async () => { const title = titleInput.value.trim(); const url = urlInput.value.trim(); const icon = selectedIcon; if (!title || !url) return; bookmarks.push({ title, url, icon }); - saveBookmarks(); + await saveBookmarks(); renderBookmarks(); titleInput.value = ''; @@ -181,5 +212,8 @@ searchInput.addEventListener('keydown', e => { if (e.key === 'Enter') searchBtn.click(); }); -// initial render from localStorage -renderBookmarks(); +// Load and render bookmarks immediately +(async () => { + bookmarks = await loadBookmarks(); + renderBookmarks(); +})(); diff --git a/renderer/index.html b/renderer/index.html index eb9769d..f4e836a 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -56,25 +56,19 @@ -
- + + + +
+ +
- \ No newline at end of file diff --git a/renderer/script.js b/renderer/script.js index 7a7e8a3..c829882 100644 --- a/renderer/script.js +++ b/renderer/script.js @@ -53,6 +53,34 @@ 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(); + +// Create initial home tab on startup +createTab(); + +// 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); @@ -63,6 +91,29 @@ 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); @@ -72,6 +123,7 @@ function createTab(inputUrl) { 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)); @@ -80,6 +132,20 @@ function createTab(inputUrl) { 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); @@ -169,8 +235,7 @@ function updateTabMetadata(id, key, value) { 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; + if (!tab) return; // decide if this is a search query or a URL/internal page const hasProtocol = /^https?:\/\//i.test(input); @@ -183,6 +248,18 @@ function navigate() { 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); @@ -195,6 +272,75 @@ function navigate() { 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) { const tab = tabs.find(t => t.id === tabId); if (!tab) return; @@ -241,17 +387,30 @@ function handleNavigation(tabId, newUrl) { 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 activeWebview = document.getElementById(`tab-${id}`); - if (activeWebview) activeWebview.classList.add('active'); + 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; - 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; @@ -399,6 +558,16 @@ window.addEventListener('DOMContentLoaded', () => { } 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); @@ -435,7 +604,7 @@ window.addEventListener('DOMContentLoaded', () => { }); // menu‐related code (moved here so #context-menu exists) - const items = menu ? menu.querySelectorAll('li') : []; + const items = contextMenu ? contextMenu.querySelectorAll('li') : []; function showContextMenu(x, y) { if (!menu) return; @@ -531,8 +700,14 @@ function updateZoomUI() { function zoomIn() { ipcRenderer.invoke('zoom-in').then(updateZoomUI); } function zoomOut() { ipcRenderer.invoke('zoom-out').then(updateZoomUI); } -const fs = require('fs'); -const { remote } = require('electron'); +// 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) { diff --git a/renderer/style.css b/renderer/style.css index 6dca7aa..a79d97b 100644 --- a/renderer/style.css +++ b/renderer/style.css @@ -130,14 +130,44 @@ html, body { width: 100%; position: relative; } +#webviews.hidden { + display: none; +} #webviews webview { flex: 1; - display: 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; } diff --git a/site-history.json b/site-history.json index 5c64c1e..fcc5204 100644 --- a/site-history.json +++ b/site-history.json @@ -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:///Users/andrewzambazos/Repositories/NebulaBrowser/renderer/index.html", "https://inscribe.zambazosmedia.group/renderer/editor.html", "https://inscribe.zambazosmedia.group/", "file:///Users/andrewzambazos/Repositories/NebulaBrowser/renderer/index.html", - "https://www.youtube.com/", "https://www.youtube.com/watch?v=9FuNtfsnRNo", "https://youtube.com/", "http://homelab.andrewzambazos.com:8081/",