v1.0.3: Add refresh behavior setting (every time, daily, manual)
This commit is contained in:
parent
b679d436ca
commit
d278388afd
7 changed files with 148 additions and 50 deletions
13
README.md
13
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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
shared/ui.js
27
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue