Add "not visited in x days" as an option

This commit is contained in:
Bastian Gruber 2026-01-06 12:03:57 -04:00
parent 537cc1f724
commit 5cf6f68d61
Signed by: gruberb
GPG key ID: 426AF1CBA0530691
7 changed files with 58 additions and 9 deletions

View file

@ -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

View file

@ -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,

View file

@ -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",

View file

@ -277,6 +277,23 @@
value="7"
/>
</div>
<div class="setting-row">
<div class="setting-label">
Not visited in (days)
<span
>Also show bookmarks not visited in this many days,
even if visited before (0 = disabled)</span
>
</div>
<input
type="number"
id="notVisitedInDays"
min="0"
max="3650"
value="0"
/>
</div>
</div>
<div class="section">

View file

@ -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,
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View file

@ -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);