diff --git a/README.md b/README.md index cc83aa3..c1bd7e5 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ A Firefox extension that surfaces your forgotten bookmarks — the ones you save ## Features - **Discover forgotten bookmarks** — Shows bookmarks you've rarely or never visited +- **Stale bookmark detection** — Find bookmarks you visited a few times long ago but haven't opened in months or years - **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 +- **Configurable criteria** — Set maximum visit count, minimum age, and "not visited in X days" threshold - **Exclude folders** — Keep certain bookmark folders out of suggestions ## Screenshots diff --git a/background.js b/background.js index 21245a3..0d16d79 100644 --- a/background.js +++ b/background.js @@ -12,6 +12,7 @@ const DEFAULT_SETTINGS = { numBookmarks: 5, maxVisitCount: 2, // Consider "forgotten" if visited 2 or fewer times minAgeDays: 7, // Only show bookmarks older than 7 days + notVisitedInDays: 0, // Consider "forgotten" if not visited in X days (0 = disabled) refreshBehavior: "always", // "always", "daily", or "manual" }; @@ -96,13 +97,18 @@ async function getAllBookmarks(excludedFolderIds = []) { return bookmarks; } -// Get visit count for a URL -async function getVisitCount(url) { +// Get visit count and last visit time for a URL +async function getVisitInfo(url) { try { const visits = await browser.history.getVisits({ url }); - return visits.length; + const lastVisitTime = + visits.length > 0 ? Math.max(...visits.map((v) => v.visitTime)) : null; + return { + visitCount: visits.length, + lastVisitTime: lastVisitTime, + }; } catch (e) { - return 0; + return { visitCount: 0, lastVisitTime: null }; } } @@ -116,6 +122,7 @@ async function fetchFreshBookmarks() { // Filter by age and get visit counts const candidates = []; + const notVisitedInMs = settings.notVisitedInDays * 24 * 60 * 60 * 1000; for (const bookmark of bookmarks) { // Skip if too new @@ -131,16 +138,29 @@ async function fetchFreshBookmarks() { continue; } - const visitCount = await getVisitCount(bookmark.url); + const { visitCount, lastVisitTime } = await getVisitInfo(bookmark.url); - if (visitCount <= settings.maxVisitCount) { + // Check if bookmark qualifies as "forgotten" + // Either: low visit count OR hasn't been visited in X days (if enabled) + const isLowVisitCount = visitCount <= settings.maxVisitCount; + const isStale = + settings.notVisitedInDays > 0 && + (lastVisitTime === null || now - lastVisitTime > notVisitedInMs); + + if (isLowVisitCount || isStale) { // Check if dateAdded is valid (not corrupted/too old) const hasValidDate = bookmark.dateAdded && bookmark.dateAdded >= MIN_VALID_TIMESTAMP; + const daysSinceLastVisit = lastVisitTime + ? Math.floor((now - lastVisitTime) / (24 * 60 * 60 * 1000)) + : null; + candidates.push({ ...bookmark, visitCount, + lastVisitTime, + daysSinceLastVisit, daysSinceAdded: hasValidDate ? Math.floor((now - bookmark.dateAdded) / (24 * 60 * 60 * 1000)) : null, diff --git a/manifest.json b/manifest.json index 936f376..a7151a3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Embermarks", - "version": "1.0.3", + "version": "1.0.4", "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 b32f3e6..0b01493 100644 --- a/options/options.html +++ b/options/options.html @@ -277,6 +277,23 @@ value="7" /> + +
+
+ Not visited in (days) + Also show bookmarks not visited in this many days, + even if visited before (0 = disabled) +
+ +
diff --git a/options/options.js b/options/options.js index d6a916e..cc0887a 100644 --- a/options/options.js +++ b/options/options.js @@ -5,6 +5,7 @@ document.addEventListener("DOMContentLoaded", async () => { const refreshBehaviorSelect = document.getElementById("refreshBehavior"); const maxVisitCountInput = document.getElementById("maxVisitCount"); const minAgeDaysInput = document.getElementById("minAgeDays"); + const notVisitedInDaysInput = document.getElementById("notVisitedInDays"); const folderList = document.getElementById("folderList"); const saveBtn = document.getElementById("saveBtn"); const saveStatus = document.getElementById("saveStatus"); @@ -16,6 +17,7 @@ document.addEventListener("DOMContentLoaded", async () => { refreshBehaviorSelect.value = settings.refreshBehavior || "always"; maxVisitCountInput.value = settings.maxVisitCount; minAgeDaysInput.value = settings.minAgeDays; + notVisitedInDaysInput.value = settings.notVisitedInDays || 0; // Load folder list const folders = await browser.runtime.sendMessage({ @@ -60,6 +62,7 @@ document.addEventListener("DOMContentLoaded", async () => { refreshBehavior: refreshBehaviorSelect.value, maxVisitCount: parseInt(maxVisitCountInput.value, 10), minAgeDays: parseInt(minAgeDaysInput.value, 10), + notVisitedInDays: parseInt(notVisitedInDaysInput.value, 10), excludedFolders, }; diff --git a/screenshots/embermarks_menu.png b/screenshots/embermarks_menu.png index cd2ca36..2950adb 100644 Binary files a/screenshots/embermarks_menu.png and b/screenshots/embermarks_menu.png differ diff --git a/shared/ui.js b/shared/ui.js index b0160e5..543b40b 100644 --- a/shared/ui.js +++ b/shared/ui.js @@ -55,7 +55,15 @@ function createBookmarkCard(bookmark) { meta.className = "bookmark-meta"; const visitText = bookmark.visitCount === 1 ? "1 visit" : `${bookmark.visitCount} visits`; - meta.textContent = `${visitText} · Added ${formatDaysAgo(bookmark.daysSinceAdded)}`; + + let metaText = `${visitText} · Added ${formatDaysAgo(bookmark.daysSinceAdded)}`; + + // Show last visited info if the bookmark has been visited + if (bookmark.daysSinceLastVisit !== null && bookmark.visitCount > 0) { + metaText += ` · Last visit ${formatDaysAgo(bookmark.daysSinceLastVisit)}`; + } + + meta.textContent = metaText; info.appendChild(title); info.appendChild(meta);