(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #12 from icorbrey-contrib/icorbrey/firefox-highlights

Fix the highlight context menu action in Firefox

authored by

Scan and committed by
GitHub
013e2373 31ab2e92

+98 -72
+78 -72
extension/background/service-worker.js
··· 39 39 } 40 40 } 41 41 42 + async function createHighlight(payload) { 43 + if (!API_BASE) { 44 + throw new Error("API URL not configured"); 45 + } 46 + 47 + const cookie = await chrome.cookies.get({ 48 + url: API_BASE, 49 + name: "margin_session", 50 + }); 51 + 52 + if (!cookie) { 53 + throw new Error("Not authenticated"); 54 + } 55 + 56 + const res = await fetch(`${API_BASE}/api/highlights`, { 57 + method: "POST", 58 + headers: { 59 + "Content-Type": "application/json", 60 + "X-Session-Token": cookie.value, 61 + }, 62 + credentials: "include", 63 + body: JSON.stringify(payload), 64 + }); 65 + 66 + if (!res.ok) { 67 + const errText = await res.text(); 68 + throw new Error(`Failed to create highlight: ${res.status} ${errText}`); 69 + } 70 + 71 + return res.json(); 72 + } 73 + 74 + function refreshTabAnnotations(tabId) { 75 + if (!tabId) return; 76 + chrome.tabs.sendMessage(tabId, { type: "REFRESH_ANNOTATIONS" }).catch(() => { 77 + /* ignore missing content script */ 78 + }); 79 + } 80 + 42 81 async function openAnnotationUI(tabId, windowId) { 43 82 if (hasSidePanel) { 44 83 try { ··· 75 114 updateBaseUrls("https://margin.at"); 76 115 } 77 116 117 + await ensureContextMenus(); 118 + 119 + if (hasSidebarAction) { 120 + try { 121 + await browser.sidebarAction.close(); 122 + } catch { 123 + /* ignore */ 124 + } 125 + } 126 + }); 127 + 128 + chrome.runtime.onStartup.addListener(async () => { 129 + await ensureContextMenus(); 130 + }); 131 + 132 + async function ensureContextMenus() { 78 133 await chrome.contextMenus.removeAll(); 79 134 80 135 chrome.contextMenus.create({ ··· 100 155 title: "Open Margin Sidebar", 101 156 contexts: ["page", "selection", "link"], 102 157 }); 103 - 104 - if (hasSidebarAction) { 105 - try { 106 - await browser.sidebarAction.close(); 107 - } catch { 108 - /* ignore */ 109 - } 110 - } 111 - }); 158 + } 112 159 113 160 chrome.action.onClicked.addListener(async () => { 114 161 const stored = await chrome.storage.local.get(["apiUrl"]); ··· 210 257 type: "GET_SELECTOR_FOR_HIGHLIGHT", 211 258 selectionText: info.selectionText, 212 259 }); 260 + if (response?.selector) { 261 + selector = response.selector; 262 + } 213 263 if (response && response.success) return; 214 264 } catch { 215 265 /* ignore */ 216 266 } 217 267 218 - if (info.selectionText) { 268 + if (!selector && info.selectionText) { 219 269 selector = { 220 270 type: "TextQuoteSelector", 221 271 exact: info.selectionText, 222 272 }; 273 + } 223 274 275 + if (selector) { 224 276 try { 225 - const cookie = await chrome.cookies.get({ 226 - url: API_BASE, 227 - name: "margin_session", 277 + await createHighlight({ 278 + url: tab.url, 279 + title: tab.title, 280 + selector: selector, 228 281 }); 229 - 230 - if (!cookie) { 282 + showNotification("Margin", "Text highlighted!"); 283 + refreshTabAnnotations(tab.id); 284 + } catch (err) { 285 + console.error("Highlight API error:", err); 286 + if (err?.message === "Not authenticated") { 231 287 showNotification("Margin", "Please sign in to create highlights"); 232 288 return; 233 289 } 234 - 235 - const res = await fetch(`${API_BASE}/api/highlights`, { 236 - method: "POST", 237 - headers: { 238 - "Content-Type": "application/json", 239 - }, 240 - credentials: "include", 241 - body: JSON.stringify({ 242 - url: tab.url, 243 - title: tab.title, 244 - selector: selector, 245 - }), 246 - }); 247 - 248 - if (res.ok) { 249 - showNotification("Margin", "Text highlighted!"); 250 - } else { 251 - const errText = await res.text(); 252 - console.error("Highlight API error:", res.status, errText); 253 - showNotification("Margin", "Failed to create highlight"); 254 - } 255 - } catch (err) { 256 - console.error("Highlight API error:", err); 257 290 showNotification("Margin", "Error creating highlight"); 258 291 } 259 292 } else { ··· 482 515 } 483 516 484 517 case "CREATE_HIGHLIGHT": { 485 - if (!API_BASE) { 486 - sendResponse({ success: false, error: "API URL not configured" }); 487 - return; 488 - } 489 - 490 - const cookie = await chrome.cookies.get({ 491 - url: API_BASE, 492 - name: "margin_session", 493 - }); 494 - 495 - if (!cookie) { 496 - sendResponse({ success: false, error: "Not authenticated" }); 497 - return; 498 - } 499 - 500 - const highlightRes = await fetch(`${API_BASE}/api/highlights`, { 501 - method: "POST", 502 - credentials: "include", 503 - headers: { 504 - "Content-Type": "application/json", 505 - "X-Session-Token": cookie.value, 506 - }, 507 - body: JSON.stringify({ 518 + try { 519 + const highlightData = await createHighlight({ 508 520 url: request.data.url, 509 521 title: request.data.title, 510 522 selector: request.data.selector, 511 523 color: request.data.color || "yellow", 512 - }), 513 - }); 514 - 515 - if (!highlightRes.ok) { 516 - const errorText = await highlightRes.text(); 517 - throw new Error( 518 - `Failed to create highlight: ${highlightRes.status} ${errorText}`, 519 - ); 524 + }); 525 + sendResponse({ success: true, data: highlightData }); 526 + refreshTabAnnotations(sender.tab?.id); 527 + } catch (error) { 528 + sendResponse({ success: false, error: error.message }); 520 529 } 521 - 522 - const highlightData = await highlightRes.json(); 523 - sendResponse({ success: true, data: highlightData }); 524 530 break; 525 531 } 526 532
+20
extension/content/content.js
··· 972 972 return true; 973 973 } 974 974 975 + if (request.type === "GET_SELECTOR_FOR_HIGHLIGHT") { 976 + const sel = window.getSelection(); 977 + if (!sel || !sel.toString().trim()) { 978 + sendResponse({ success: false, selector: null }); 979 + return true; 980 + } 981 + const exact = sel.toString().trim(); 982 + sendResponse({ 983 + success: false, 984 + selector: { type: "TextQuoteSelector", exact }, 985 + }); 986 + return true; 987 + } 988 + 989 + if (request.type === "REFRESH_ANNOTATIONS") { 990 + fetchAnnotations(); 991 + sendResponse({ success: true }); 992 + return true; 993 + } 994 + 975 995 if (request.type === "UPDATE_OVERLAY_VISIBILITY") { 976 996 if (sidebarHost) { 977 997 sidebarHost.style.display = request.show ? "block" : "none";