commit 3dbf791621cbb011e4681ddca58adbb334842af0 Author: jared Date: Mon Jan 19 23:23:15 2026 -0500 Initial commit Add Chrome extension for Read It Later functionality with background script, manifest, and icons. Co-Authored-By: Claude Opus 4.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d865be --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +token diff --git a/background.js b/background.js new file mode 100644 index 0000000..36705c1 --- /dev/null +++ b/background.js @@ -0,0 +1,123 @@ +const API_URL = "https://www.jaredlog.com/readitlater/api/v1/capture"; + +async function getToken() { + return new Promise((resolve) => { + chrome.storage.sync.get(["READITLATER_TOKEN"], (res) => resolve(res.READITLATER_TOKEN || "")); + }); +} + +async function injectBanner(tabId, text, ok = true) { + try { + await chrome.scripting.executeScript({ + target: { tabId }, + world: "MAIN", // we want to touch the page DOM + func: (text, ok) => { + // Create a host and shadow root so site CSS can't break us + const host = document.createElement("div"); + host.setAttribute("id", "readitlater-toast-host"); + host.style.all = "initial"; // reduce leakage in some edge cases + const shadow = host.attachShadow({ mode: "closed" }); + + // Styles + const style = document.createElement("style"); + style.textContent = ` + @keyframes slideDown { + from { transform: translateY(-110%); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + @keyframes fadeOut { + to { opacity: 0; transform: translateY(-110%); } + } + .toast { + position: fixed; + top: 0; left: 50%; + transform: translateX(-50%); + z-index: 2147483647; + margin: 12px auto 0; + padding: 10px 14px; + border-radius: 8px; + font: 14px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + color: #0b1f0b; + background: ${ok ? "#c7f9cc" : "#ffd6d6"}; + border: 1px solid ${ok ? "#94d7a2" : "#ffabab"}; + box-shadow: 0 10px 20px rgba(0,0,0,.12), 0 2px 6px rgba(0,0,0,.08); + animation: slideDown 180ms ease-out; + pointer-events: none; /* don't block page */ + max-width: 90vw; + text-align: center; + } + .toast .text { + white-space: pre-wrap; + } + @media (prefers-reduced-motion: reduce) { + .toast { animation: none; } + } + `; + + // Container + const toast = document.createElement("div"); + toast.className = "toast"; + toast.setAttribute("role", "status"); + toast.setAttribute("aria-live", "polite"); + toast.innerHTML = `${text}`; + + shadow.append(style, toast); + document.documentElement.appendChild(host); + + // Auto-remove after 2 seconds (fade for 200ms) + const remove = () => host.remove(); + setTimeout(() => { + toast.style.animation = "fadeOut 200ms ease-in forwards"; + setTimeout(remove, 220); + }, 2000); + }, + args: [text, ok], + }); + } catch (e) { + // Non-fatal if injection fails (e.g., restricted pages) + console.warn("readitlater: banner inject failed", e); + } +} + +chrome.action.onClicked.addListener(async (tab) => { + if (!tab?.id) return; + + const [result] = await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + func: () => ({ + url: window.location.href, + title: document.title, + html: "" + document.documentElement.outerHTML + }), + }); + + const payload = result?.result || null; + if (!payload) return; + + const token = await getToken(); + + try { + const resp = await fetch(API_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + body: JSON.stringify(payload), + }); + + if (resp.ok) { + // Success banner + await injectBanner(tab.id, "Link saved.", true); + } else { + console.error("Capture failed:", resp.status, await resp.text()); + await injectBanner(tab.id, "Save failed.", false); + } + } catch (e) { + console.error("readitlater: network error", e); + await injectBanner(tab.id, "Network error.", false); + } +}); + +// One-time: set your token in DevTools console on any page: +// chrome.storage.sync.set({ READITLATER_TOKEN: "YOUR_HEX_TOKEN" }); diff --git a/how-to-add-token.txt b/how-to-add-token.txt new file mode 100644 index 0000000..9db89f0 --- /dev/null +++ b/how-to-add-token.txt @@ -0,0 +1,9 @@ +How to add the hex token: +Use the extension’s background page (preferred) +Go to chrome://extensions/. +Enable Developer mode (toggle top right). +Find your readitlater extension. +Click “service worker” link under “Inspect views”. This opens a DevTools console for the extension background. + +Now paste: +chrome.storage.sync.set({ READITLATER_TOKEN: "2cb9f5b875af65b4de7ff7736e384ae9d33e1bf2176c45afac24a713804ef6d4" }); diff --git a/icon128.png b/icon128.png new file mode 100644 index 0000000..31f3b14 Binary files /dev/null and b/icon128.png differ diff --git a/icon16.png b/icon16.png new file mode 100644 index 0000000..107ecec Binary files /dev/null and b/icon16.png differ diff --git a/icon48.png b/icon48.png new file mode 100644 index 0000000..ee6e45d Binary files /dev/null and b/icon48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..324b451 --- /dev/null +++ b/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 3, + "name": "readitlater — Save Page", + "version": "0.1.0", + "action": { "default_title": "Save to readitlater" }, + "permissions": ["activeTab", "scripting", "storage"], + "host_permissions": ["https://www.jaredlog.com/*"], + "background": { "service_worker": "background.js" }, + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + } +}