Initial commit: Shelf read-later extension
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
// Shelf — background service worker
|
||||
// Works under both the `chrome` namespace (Chrome/Edge) and Firefox's
|
||||
// chrome.* alias for WebExtensions.
|
||||
|
||||
const STORAGE_KEY = "shelfItems";
|
||||
|
||||
function domainOf(url) {
|
||||
try {
|
||||
return new URL(url).hostname.replace(/^www\./, "");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
async function getItems() {
|
||||
const data = await chrome.storage.local.get(STORAGE_KEY);
|
||||
return data[STORAGE_KEY] || [];
|
||||
}
|
||||
|
||||
async function setItems(items) {
|
||||
await chrome.storage.local.set({ [STORAGE_KEY]: items });
|
||||
}
|
||||
|
||||
async function addItem({ url, title, favIconUrl, image }) {
|
||||
if (!url) return;
|
||||
const items = await getItems();
|
||||
|
||||
// De-dupe: if already saved, just bump it to the top and refresh metadata.
|
||||
const existingIndex = items.findIndex((i) => i.url === url);
|
||||
const entry = {
|
||||
id: existingIndex >= 0 ? items[existingIndex].id : `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
url,
|
||||
title: title || url,
|
||||
domain: domainOf(url),
|
||||
favicon: favIconUrl || "",
|
||||
image: image || (existingIndex >= 0 ? items[existingIndex].image : ""),
|
||||
dateAdded: Date.now(),
|
||||
archived: false,
|
||||
};
|
||||
|
||||
if (existingIndex >= 0) items.splice(existingIndex, 1);
|
||||
items.unshift(entry);
|
||||
await setItems(items);
|
||||
return entry;
|
||||
}
|
||||
|
||||
async function flashBadge(tabId, text) {
|
||||
try {
|
||||
await chrome.action.setBadgeText({ text, tabId });
|
||||
await chrome.action.setBadgeBackgroundColor({ color: "#C47A4A", tabId });
|
||||
setTimeout(() => {
|
||||
chrome.action.setBadgeText({ text: "", tabId }).catch(() => {});
|
||||
}, 1400);
|
||||
} catch {
|
||||
// tabId may be gone; ignore.
|
||||
}
|
||||
}
|
||||
|
||||
// Try to grab a screenshot of the *currently active* tab. This only works
|
||||
// for the page the user is actually looking at (browser security), so it's
|
||||
// used when saving the current page, not for arbitrary right-clicked links.
|
||||
async function captureScreenshot(windowId) {
|
||||
try {
|
||||
const dataUrl = await chrome.tabs.captureVisibleTab(windowId, {
|
||||
format: "jpeg",
|
||||
quality: 55,
|
||||
});
|
||||
return dataUrl;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Toolbar icon click: save the current page ---
|
||||
chrome.action.onClicked.addListener(async (tab) => {
|
||||
if (!tab || !tab.url || tab.url.startsWith("chrome://") || tab.url.startsWith("about:")) {
|
||||
return;
|
||||
}
|
||||
const image = await captureScreenshot(tab.windowId);
|
||||
await addItem({ url: tab.url, title: tab.title, favIconUrl: tab.favIconUrl, image });
|
||||
flashBadge(tab.id, "✓");
|
||||
});
|
||||
|
||||
// --- Context menus: right-click a page or a link ---
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
chrome.contextMenus.create({
|
||||
id: "save-page-later",
|
||||
title: "Save page for later",
|
||||
contexts: ["page"],
|
||||
});
|
||||
chrome.contextMenus.create({
|
||||
id: "save-link-later",
|
||||
title: "Save link for later",
|
||||
contexts: ["link"],
|
||||
});
|
||||
});
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
||||
if (info.menuItemId === "save-page-later") {
|
||||
if (!tab || !tab.url) return;
|
||||
const image = await captureScreenshot(tab.windowId);
|
||||
await addItem({ url: tab.url, title: tab.title, favIconUrl: tab.favIconUrl, image });
|
||||
flashBadge(tab.id, "✓");
|
||||
}
|
||||
|
||||
if (info.menuItemId === "save-link-later") {
|
||||
if (!info.linkUrl) return;
|
||||
const linkTitle = info.linkText && info.linkText.trim() ? info.linkText.trim() : info.linkUrl;
|
||||
// No screenshot for links we haven't navigated to — favicon/title only.
|
||||
await addItem({ url: info.linkUrl, title: linkTitle, favIconUrl: "", image: "" });
|
||||
if (tab && tab.id) flashBadge(tab.id, "✓");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user