Initial commit
Add Chrome extension for Read It Later functionality with background script, manifest, and icons. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
token
|
||||||
123
background.js
Normal file
123
background.js
Normal file
@@ -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 = `<span class="text">${text}</span>`;
|
||||||
|
|
||||||
|
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: "<!doctype 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" });
|
||||||
9
how-to-add-token.txt
Normal file
9
how-to-add-token.txt
Normal file
@@ -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" });
|
||||||
BIN
icon128.png
Normal file
BIN
icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
icon16.png
Normal file
BIN
icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
icon48.png
Normal file
BIN
icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
14
manifest.json
Normal file
14
manifest.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user