From d278388afd8040f3b16016fdc5d5321c4660ca40 Mon Sep 17 00:00:00 2001 From: Bastian Gruber Date: Sun, 4 Jan 2026 22:36:05 -0400 Subject: [PATCH] v1.0.3: Add refresh behavior setting (every time, daily, manual) --- README.md | 13 +++---- background.js | 81 +++++++++++++++++++++++++++++++++++++++++--- manifest.json | 2 +- options/options.html | 21 ++++++++++++ options/options.js | 6 ++++ popup/popup.js | 48 ++++++++++++++++++++------ shared/ui.js | 27 --------------- 7 files changed, 148 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index cf64ce6..7991f8d 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,17 @@ A Firefox extension that surfaces your forgotten bookmarks — the ones you save ## Installation -### From Firefox Add-ons (recommended) +### From Firefox Add-ons -[Install from AMO](https://addons.mozilla.org/firefox/addon/embermarks/) - -### Manual Install - -Download the latest `.xpi` from [Releases](https://github.com/gruberb/embermarks/releases) and drag it into Firefox. +[Install from AMO](https://addons.mozilla.org/en-US/firefox/addon/embermarks1/) ## Features - **Discover forgotten bookmarks** — Shows bookmarks you've rarely or never visited +- **Click to open** — Just click any bookmark to visit it +- **Refresh behavior** — Choose to see new bookmarks every time, once a day, or only when you manually refresh - **Configurable criteria** — Set maximum visit count and minimum age for "forgotten" status - **Exclude folders** — Keep certain bookmark folders out of suggestions -- **Quick actions** — Visit, skip, or delete bookmarks directly from the popup ## Building from Source @@ -37,4 +34,4 @@ npx web-ext run ## License -Mozilla Public License Version 2.0 +Mozilla Public License Version 2.0 \ No newline at end of file diff --git a/background.js b/background.js index f639a42..21245a3 100644 --- a/background.js +++ b/background.js @@ -4,13 +4,21 @@ // Minimum valid timestamp (Jan 1, 2010) - older dates are likely corrupted imports const MIN_VALID_TIMESTAMP = 1262304000000; +// Cache duration for "daily" refresh mode (24 hours in ms) +const DAILY_CACHE_DURATION = 24 * 60 * 60 * 1000; + const DEFAULT_SETTINGS = { excludedFolders: [], numBookmarks: 5, maxVisitCount: 2, // Consider "forgotten" if visited 2 or fewer times minAgeDays: 7, // Only show bookmarks older than 7 days + refreshBehavior: "always", // "always", "daily", or "manual" }; +// In-memory cache for bookmarks +let cachedBookmarks = null; +let cacheTimestamp = null; + // Get user settings async function getSettings() { const result = await browser.storage.local.get("settings"); @@ -24,6 +32,33 @@ async function saveSettings(settings) { }); } +// Get cached bookmarks if valid +async function getCachedBookmarks() { + const result = await browser.storage.local.get([ + "cachedBookmarks", + "cacheTimestamp", + ]); + return { + bookmarks: result.cachedBookmarks || null, + timestamp: result.cacheTimestamp || null, + }; +} + +// Save bookmarks to cache +async function setCachedBookmarks(bookmarks) { + await browser.storage.local.set({ + cachedBookmarks: bookmarks, + cacheTimestamp: Date.now(), + }); +} + +// Clear the cache +async function clearCache() { + await browser.storage.local.remove(["cachedBookmarks", "cacheTimestamp"]); + cachedBookmarks = null; + cacheTimestamp = null; +} + // Get all bookmarks recursively, respecting excluded folders async function getAllBookmarks(excludedFolderIds = []) { const tree = await browser.bookmarks.getTree(); @@ -71,8 +106,8 @@ async function getVisitCount(url) { } } -// Get forgotten bookmarks (low visit count, old enough) -async function getForgottenBookmarks() { +// Fetch fresh forgotten bookmarks +async function fetchFreshBookmarks() { const settings = await getSettings(); const bookmarks = await getAllBookmarks(settings.excludedFolders); @@ -118,6 +153,41 @@ async function getForgottenBookmarks() { return shuffled.slice(0, settings.numBookmarks); } +// Get forgotten bookmarks (with caching logic) +async function getForgottenBookmarks(forceRefresh = false) { + const settings = await getSettings(); + const { bookmarks: cached, timestamp } = await getCachedBookmarks(); + + // Determine if we should use cache + if (!forceRefresh && cached && timestamp) { + const now = Date.now(); + const cacheAge = now - timestamp; + + if (settings.refreshBehavior === "manual") { + // Always use cache in manual mode (until explicit refresh) + return cached; + } + + if ( + settings.refreshBehavior === "daily" && + cacheAge < DAILY_CACHE_DURATION + ) { + // Use cache if less than 24 hours old + return cached; + } + } + + // Fetch fresh bookmarks + const freshBookmarks = await fetchFreshBookmarks(); + + // Cache the results (for daily and manual modes) + if (settings.refreshBehavior !== "always") { + await setCachedBookmarks(freshBookmarks); + } + + return freshBookmarks; +} + // Get all bookmark folders for settings UI async function getBookmarkFolders() { const tree = await browser.bookmarks.getTree(); @@ -140,11 +210,14 @@ async function getBookmarkFolders() { return folders; } -// Handle messages from popup/newtab/options +// Handle messages from popup/options browser.runtime.onMessage.addListener((message, sender, sendResponse) => { switch (message.action) { case "getForgottenBookmarks": - return getForgottenBookmarks(); + return getForgottenBookmarks(message.forceRefresh || false); + + case "clearCache": + return clearCache(); case "getBookmarkFolders": return getBookmarkFolders(); diff --git a/manifest.json b/manifest.json index 6c3479d..936f376 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Embermarks", - "version": "1.0.2", + "version": "1.0.3", "description": "Rediscover your forgotten bookmarks. Surface the ones you never visit.", "author": "Bastian Gruber", "homepage_url": "https://github.com/gruberb/embermarks", diff --git a/options/options.html b/options/options.html index 248332f..b32f3e6 100644 --- a/options/options.html +++ b/options/options.html @@ -108,6 +108,15 @@ text-align: center; } + select { + padding: 0.5rem; + border: 1px solid #444; + border-radius: 6px; + background: #1a1a1a; + color: #fff; + font-size: 1rem; + } + input[type="checkbox"] { width: 20px; height: 20px; @@ -219,6 +228,18 @@ value="5" /> + +
+
+ Refresh behavior + When to show new bookmarks +
+ +
diff --git a/options/options.js b/options/options.js index 42676a0..d6a916e 100644 --- a/options/options.js +++ b/options/options.js @@ -2,6 +2,7 @@ document.addEventListener("DOMContentLoaded", async () => { const numBookmarksInput = document.getElementById("numBookmarks"); + const refreshBehaviorSelect = document.getElementById("refreshBehavior"); const maxVisitCountInput = document.getElementById("maxVisitCount"); const minAgeDaysInput = document.getElementById("minAgeDays"); const folderList = document.getElementById("folderList"); @@ -12,6 +13,7 @@ document.addEventListener("DOMContentLoaded", async () => { const settings = await browser.runtime.sendMessage({ action: "getSettings" }); numBookmarksInput.value = settings.numBookmarks; + refreshBehaviorSelect.value = settings.refreshBehavior || "always"; maxVisitCountInput.value = settings.maxVisitCount; minAgeDaysInput.value = settings.minAgeDays; @@ -55,6 +57,7 @@ document.addEventListener("DOMContentLoaded", async () => { const newSettings = { numBookmarks: parseInt(numBookmarksInput.value, 10), + refreshBehavior: refreshBehaviorSelect.value, maxVisitCount: parseInt(maxVisitCountInput.value, 10), minAgeDays: parseInt(minAgeDaysInput.value, 10), excludedFolders, @@ -65,6 +68,9 @@ document.addEventListener("DOMContentLoaded", async () => { settings: newSettings, }); + // Clear cache when settings change + await browser.runtime.sendMessage({ action: "clearCache" }); + // Show save confirmation saveStatus.classList.add("visible"); setTimeout(() => { diff --git a/popup/popup.js b/popup/popup.js index c79e629..af7c834 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -1,14 +1,42 @@ // Embermarks - Popup Script -document.addEventListener('DOMContentLoaded', async () => { - const container = document.getElementById('bookmarks'); - const refreshBtn = document.getElementById('refresh'); - - // Load bookmarks - await loadBookmarks(container); - - // Refresh button - refreshBtn.addEventListener('click', async () => { - await loadBookmarks(container); +document.addEventListener("DOMContentLoaded", async () => { + const container = document.getElementById("bookmarks"); + const refreshBtn = document.getElementById("refresh"); + + // Load bookmarks (respects cache settings) + await loadBookmarks(container, false); + + // Refresh button always forces a fresh load + refreshBtn.addEventListener("click", async () => { + await loadBookmarks(container, true); }); }); + +// Load and display bookmarks +async function loadBookmarks(container, forceRefresh) { + showLoading(container); + + try { + const bookmarks = await browser.runtime.sendMessage({ + action: "getForgottenBookmarks", + forceRefresh: forceRefresh, + }); + + while (container.firstChild) { + container.removeChild(container.firstChild); + } + + if (!bookmarks || bookmarks.length === 0) { + showEmptyState(container, "none"); + return; + } + + for (const bookmark of bookmarks) { + container.appendChild(createBookmarkCard(bookmark)); + } + } catch (error) { + console.error("Failed to load bookmarks:", error); + showError(container); + } +} diff --git a/shared/ui.js b/shared/ui.js index 04968bb..b0160e5 100644 --- a/shared/ui.js +++ b/shared/ui.js @@ -151,30 +151,3 @@ function showError(container) { container.appendChild(emptyState); } - -// Load and display bookmarks -async function loadBookmarks(container) { - showLoading(container); - - try { - const bookmarks = await browser.runtime.sendMessage({ - action: "getForgottenBookmarks", - }); - - while (container.firstChild) { - container.removeChild(container.firstChild); - } - - if (!bookmarks || bookmarks.length === 0) { - showEmptyState(container, "none"); - return; - } - - for (const bookmark of bookmarks) { - container.appendChild(createBookmarkCard(bookmark)); - } - } catch (error) { - console.error("Failed to load bookmarks:", error); - showError(container); - } -}