v1.0.3: Add refresh behavior setting (every time, daily, manual)

This commit is contained in:
Bastian Gruber 2026-01-04 22:36:05 -04:00
parent b679d436ca
commit d278388afd
Signed by: gruberb
GPG key ID: 426AF1CBA0530691
7 changed files with 148 additions and 50 deletions

View file

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

View file

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

View file

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

View file

@ -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"
/>
</div>
<div class="setting-row">
<div class="setting-label">
Refresh behavior
<span>When to show new bookmarks</span>
</div>
<select id="refreshBehavior">
<option value="always">Every time</option>
<option value="daily">Once a day</option>
<option value="manual">Manual only</option>
</select>
</div>
</div>
<div class="section">

View file

@ -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(() => {

View file

@ -1,14 +1,42 @@
// Embermarks - Popup Script
document.addEventListener('DOMContentLoaded', async () => {
const container = document.getElementById('bookmarks');
const refreshBtn = document.getElementById('refresh');
document.addEventListener("DOMContentLoaded", async () => {
const container = document.getElementById("bookmarks");
const refreshBtn = document.getElementById("refresh");
// Load bookmarks
await loadBookmarks(container);
// Load bookmarks (respects cache settings)
await loadBookmarks(container, false);
// Refresh button
refreshBtn.addEventListener('click', async () => {
await loadBookmarks(container);
// 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);
}
}

View file

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