Add "not visited in x days" as an option
This commit is contained in:
parent
537cc1f724
commit
5cf6f68d61
7 changed files with 58 additions and 9 deletions
|
|
@ -11,9 +11,10 @@ A Firefox extension that surfaces your forgotten bookmarks — the ones you save
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Discover forgotten bookmarks** — Shows bookmarks you've rarely or never visited
|
- **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
|
- **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
|
- **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
|
- **Exclude folders** — Keep certain bookmark folders out of suggestions
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const DEFAULT_SETTINGS = {
|
||||||
numBookmarks: 5,
|
numBookmarks: 5,
|
||||||
maxVisitCount: 2, // Consider "forgotten" if visited 2 or fewer times
|
maxVisitCount: 2, // Consider "forgotten" if visited 2 or fewer times
|
||||||
minAgeDays: 7, // Only show bookmarks older than 7 days
|
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"
|
refreshBehavior: "always", // "always", "daily", or "manual"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -96,13 +97,18 @@ async function getAllBookmarks(excludedFolderIds = []) {
|
||||||
return bookmarks;
|
return bookmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get visit count for a URL
|
// Get visit count and last visit time for a URL
|
||||||
async function getVisitCount(url) {
|
async function getVisitInfo(url) {
|
||||||
try {
|
try {
|
||||||
const visits = await browser.history.getVisits({ url });
|
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) {
|
} catch (e) {
|
||||||
return 0;
|
return { visitCount: 0, lastVisitTime: null };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,6 +122,7 @@ async function fetchFreshBookmarks() {
|
||||||
|
|
||||||
// Filter by age and get visit counts
|
// Filter by age and get visit counts
|
||||||
const candidates = [];
|
const candidates = [];
|
||||||
|
const notVisitedInMs = settings.notVisitedInDays * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
for (const bookmark of bookmarks) {
|
for (const bookmark of bookmarks) {
|
||||||
// Skip if too new
|
// Skip if too new
|
||||||
|
|
@ -131,16 +138,29 @@ async function fetchFreshBookmarks() {
|
||||||
continue;
|
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)
|
// Check if dateAdded is valid (not corrupted/too old)
|
||||||
const hasValidDate =
|
const hasValidDate =
|
||||||
bookmark.dateAdded && bookmark.dateAdded >= MIN_VALID_TIMESTAMP;
|
bookmark.dateAdded && bookmark.dateAdded >= MIN_VALID_TIMESTAMP;
|
||||||
|
|
||||||
|
const daysSinceLastVisit = lastVisitTime
|
||||||
|
? Math.floor((now - lastVisitTime) / (24 * 60 * 60 * 1000))
|
||||||
|
: null;
|
||||||
|
|
||||||
candidates.push({
|
candidates.push({
|
||||||
...bookmark,
|
...bookmark,
|
||||||
visitCount,
|
visitCount,
|
||||||
|
lastVisitTime,
|
||||||
|
daysSinceLastVisit,
|
||||||
daysSinceAdded: hasValidDate
|
daysSinceAdded: hasValidDate
|
||||||
? Math.floor((now - bookmark.dateAdded) / (24 * 60 * 60 * 1000))
|
? Math.floor((now - bookmark.dateAdded) / (24 * 60 * 60 * 1000))
|
||||||
: null,
|
: null,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Embermarks",
|
"name": "Embermarks",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "Rediscover your forgotten bookmarks. Surface the ones you never visit.",
|
"description": "Rediscover your forgotten bookmarks. Surface the ones you never visit.",
|
||||||
"author": "Bastian Gruber",
|
"author": "Bastian Gruber",
|
||||||
"homepage_url": "https://github.com/gruberb/embermarks",
|
"homepage_url": "https://github.com/gruberb/embermarks",
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,23 @@
|
||||||
value="7"
|
value="7"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||||
const refreshBehaviorSelect = document.getElementById("refreshBehavior");
|
const refreshBehaviorSelect = document.getElementById("refreshBehavior");
|
||||||
const maxVisitCountInput = document.getElementById("maxVisitCount");
|
const maxVisitCountInput = document.getElementById("maxVisitCount");
|
||||||
const minAgeDaysInput = document.getElementById("minAgeDays");
|
const minAgeDaysInput = document.getElementById("minAgeDays");
|
||||||
|
const notVisitedInDaysInput = document.getElementById("notVisitedInDays");
|
||||||
const folderList = document.getElementById("folderList");
|
const folderList = document.getElementById("folderList");
|
||||||
const saveBtn = document.getElementById("saveBtn");
|
const saveBtn = document.getElementById("saveBtn");
|
||||||
const saveStatus = document.getElementById("saveStatus");
|
const saveStatus = document.getElementById("saveStatus");
|
||||||
|
|
@ -16,6 +17,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||||
refreshBehaviorSelect.value = settings.refreshBehavior || "always";
|
refreshBehaviorSelect.value = settings.refreshBehavior || "always";
|
||||||
maxVisitCountInput.value = settings.maxVisitCount;
|
maxVisitCountInput.value = settings.maxVisitCount;
|
||||||
minAgeDaysInput.value = settings.minAgeDays;
|
minAgeDaysInput.value = settings.minAgeDays;
|
||||||
|
notVisitedInDaysInput.value = settings.notVisitedInDays || 0;
|
||||||
|
|
||||||
// Load folder list
|
// Load folder list
|
||||||
const folders = await browser.runtime.sendMessage({
|
const folders = await browser.runtime.sendMessage({
|
||||||
|
|
@ -60,6 +62,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||||
refreshBehavior: refreshBehaviorSelect.value,
|
refreshBehavior: refreshBehaviorSelect.value,
|
||||||
maxVisitCount: parseInt(maxVisitCountInput.value, 10),
|
maxVisitCount: parseInt(maxVisitCountInput.value, 10),
|
||||||
minAgeDays: parseInt(minAgeDaysInput.value, 10),
|
minAgeDays: parseInt(minAgeDaysInput.value, 10),
|
||||||
|
notVisitedInDays: parseInt(notVisitedInDaysInput.value, 10),
|
||||||
excludedFolders,
|
excludedFolders,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 131 KiB |
10
shared/ui.js
10
shared/ui.js
|
|
@ -55,7 +55,15 @@ function createBookmarkCard(bookmark) {
|
||||||
meta.className = "bookmark-meta";
|
meta.className = "bookmark-meta";
|
||||||
const visitText =
|
const visitText =
|
||||||
bookmark.visitCount === 1 ? "1 visit" : `${bookmark.visitCount} visits`;
|
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(title);
|
||||||
info.appendChild(meta);
|
info.appendChild(meta);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue