feat: add tags/labels for saved items
- Add free-form tags per item: chip display with remove, inline add (comma-separated, grid view only), and a toolbar tag-filter popover listing every tag in use - Search now also matches tags; tags sync across devices like every other item field - Fix Firefox manifest.json compliance for AMO submission: replace the placeholder gecko id with a real UUID, add the now-required data_collection_permissions (bookmarksInfo, since synced items are personal browsing/bookmark data), and bump strict_min_version to 140.0 (142.0 for gecko_android) to match - Add description.md (elevator pitch / short / detailed descriptions for store listings), PRIVACY.md, and .gitignore for the dist/ packaging output - Reword all em-dash punctuation across docs and code comments per user preference
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
// and newtab.html (via <script>, Firefox event page + the shelf UI).
|
||||
//
|
||||
// Sync storage only ever holds a lightweight metadata projection of each
|
||||
// item (no screenshot thumbnails — chrome.storage.sync caps a single key at
|
||||
// item (no screenshot thumbnails; chrome.storage.sync caps a single key at
|
||||
// 8KB), one key per item ("m_<id>") so the per-key cap is never a problem;
|
||||
// the 512-key / ~100KB total quota is the real ceiling. All sync writes are
|
||||
// best-effort: if the user isn't signed in, is offline, or is over quota,
|
||||
@@ -13,6 +13,7 @@
|
||||
const SYNC_KEY_PREFIX = "m_";
|
||||
const SYNCED_IDS_KEY = "shelfSyncedIds";
|
||||
const FAVICON_SYNC_MAX_LEN = 1000;
|
||||
const TAGS_SYNC_MAX_COUNT = 20;
|
||||
|
||||
function syncKeyFor(id) {
|
||||
return SYNC_KEY_PREFIX + id;
|
||||
@@ -20,7 +21,7 @@ function syncKeyFor(id) {
|
||||
|
||||
// Shared by the reminder highlight and the auto-delete sweep, in both
|
||||
// background.js and newtab.js, so the two periods can never drift apart.
|
||||
// Months are approximated as 30 days — no need for calendar precision here.
|
||||
// Months are approximated as 30 days; no need for calendar precision here.
|
||||
const UNIT_MS = { days: 86400000, weeks: 604800000, months: 2592000000 };
|
||||
|
||||
function periodMs(amount, unit) {
|
||||
@@ -32,7 +33,7 @@ function periodMs(amount, unit) {
|
||||
// invariant as the settings-panel validation, as a hard safety net: even if
|
||||
// prefs somehow end up misconfigured (e.g. edited directly in storage), this
|
||||
// refuses to delete anything until the auto-delete period genuinely exceeds
|
||||
// the reminder period — so a user is always reminded before something is
|
||||
// the reminder period, so a user is always reminded before something is
|
||||
// removed, never surprised by it.
|
||||
function isOverdueForDeletion(item, prefs) {
|
||||
if (!prefs || !prefs.autoDeleteEnabled || item.archived) return false;
|
||||
@@ -54,16 +55,17 @@ function metaFromItem(item) {
|
||||
updatedAt: item.updatedAt || item.dateAdded,
|
||||
surfaceCount: item.surfaceCount || 0,
|
||||
lastSurfacedAt: item.lastSurfacedAt || 0,
|
||||
tags: (item.tags || []).slice(0, TAGS_SYNC_MAX_COUNT),
|
||||
};
|
||||
}
|
||||
|
||||
// "Worth a second look" spotlight: proactively resurfaces a few old,
|
||||
// forgotten, unarchived items on the new tab page. The more times an item
|
||||
// has been surfaced without being acted on, the longer before it's shown
|
||||
// again — so it never nags forever on the same links, but never fully goes
|
||||
// again, so it never nags forever on the same links, but never fully goes
|
||||
// silent either (it always comes back eventually).
|
||||
const SPOTLIGHT_BACKOFF_DAYS = [3, 7, 14, 30, 60];
|
||||
// Used when the reminder feature is off (its default state) — otherwise
|
||||
// Used when the reminder feature is off (its default state). Otherwise
|
||||
// "old enough to matter" would have no threshold at all and the spotlight
|
||||
// would silently do nothing for most users.
|
||||
const SPOTLIGHT_FALLBACK_MIN_AGE_MS = periodMs(14, "days");
|
||||
@@ -122,7 +124,7 @@ async function writeSyncedIds(idSet) {
|
||||
|
||||
// Pulls remote adds/edits into `items` and pushes local adds/edits/deletes
|
||||
// out to chrome.storage.sync. Returns the merged items array; does not
|
||||
// persist it — callers are responsible for saving the result locally.
|
||||
// persist it. Callers are responsible for saving the result locally.
|
||||
async function reconcileWithSync(items) {
|
||||
let syncItems, previouslySynced;
|
||||
try {
|
||||
@@ -135,7 +137,7 @@ async function reconcileWithSync(items) {
|
||||
const localById = new Map(items.map((i) => [i.id, i]));
|
||||
|
||||
// Items that were synced before but are no longer in sync storage were
|
||||
// deleted on another device — drop them here too.
|
||||
// deleted on another device, so drop them here too.
|
||||
let merged = items.filter((item) => !(previouslySynced.has(item.id) && !syncById.has(item.id)));
|
||||
|
||||
// Pull in remote adds/edits.
|
||||
@@ -143,8 +145,8 @@ async function reconcileWithSync(items) {
|
||||
const local = localById.get(meta.id);
|
||||
if (!local) {
|
||||
// Absent locally but previously synced means *this* device deleted it;
|
||||
// don't resurrect it from a remote copy that hasn't caught up yet —
|
||||
// let the deletion below propagate out instead.
|
||||
// don't resurrect it from a remote copy that hasn't caught up yet.
|
||||
// Let the deletion below propagate out instead.
|
||||
if (previouslySynced.has(meta.id)) continue;
|
||||
merged.push({
|
||||
id: meta.id,
|
||||
@@ -158,6 +160,7 @@ async function reconcileWithSync(items) {
|
||||
updatedAt: meta.updatedAt,
|
||||
surfaceCount: meta.surfaceCount || 0,
|
||||
lastSurfacedAt: meta.lastSurfacedAt || 0,
|
||||
tags: meta.tags || [],
|
||||
});
|
||||
} else if ((meta.updatedAt || 0) > (local.updatedAt || local.dateAdded || 0)) {
|
||||
local.title = meta.title;
|
||||
@@ -168,6 +171,7 @@ async function reconcileWithSync(items) {
|
||||
local.updatedAt = meta.updatedAt;
|
||||
local.surfaceCount = meta.surfaceCount || 0;
|
||||
local.lastSurfacedAt = meta.lastSurfacedAt || 0;
|
||||
local.tags = meta.tags || [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +202,7 @@ async function reconcileWithSync(items) {
|
||||
if (removals.length) await chrome.storage.sync.remove(removals);
|
||||
await writeSyncedIds(nextSyncedIds);
|
||||
} catch {
|
||||
// Best effort — local storage remains the source of truth for this device.
|
||||
// Best effort: local storage remains the source of truth for this device.
|
||||
}
|
||||
|
||||
return merged;
|
||||
|
||||
Reference in New Issue
Block a user