Write on the margins of the internet. Powered by the AT Protocol.
0
fork

Configure Feed

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

Implement canonical annotations #15

scanash00 44a2bde7 419a4460

+76 -10
+37 -8
extension/background/service-worker.js
··· 205 205 206 206 if (info.menuItemId === "margin-annotate") { 207 207 let selector = null; 208 + let canonicalUrl = null; 208 209 209 210 try { 210 211 const response = await chrome.tabs.sendMessage(tab.id, { ··· 212 213 selectionText: info.selectionText, 213 214 }); 214 215 selector = response?.selector; 216 + canonicalUrl = response?.canonicalUrl; 215 217 } catch { 216 218 /* ignore */ 217 219 } ··· 223 225 }; 224 226 } 225 227 228 + const targetUrl = canonicalUrl || tab.url; 229 + 226 230 if (selector) { 227 231 try { 228 232 await chrome.tabs.sendMessage(tab.id, { 229 233 type: "SHOW_INLINE_ANNOTATE", 230 234 data: { 231 - url: tab.url, 235 + url: targetUrl, 232 236 title: tab.title, 233 237 selector: selector, 234 238 }, ··· 240 244 } 241 245 242 246 if (WEB_BASE) { 243 - let composeUrl = `${WEB_BASE}/new?url=${encodeURIComponent(tab.url)}`; 247 + let composeUrl = `${WEB_BASE}/new?url=${encodeURIComponent(targetUrl)}`; 244 248 if (selector) { 245 249 composeUrl += `&selector=${encodeURIComponent(JSON.stringify(selector))}`; 246 250 } ··· 251 255 252 256 if (info.menuItemId === "margin-highlight") { 253 257 let selector = null; 258 + let canonicalUrl = null; 254 259 255 260 try { 256 261 const response = await chrome.tabs.sendMessage(tab.id, { ··· 259 264 }); 260 265 if (response?.selector) { 261 266 selector = response.selector; 267 + canonicalUrl = response.canonicalUrl; 262 268 } 263 269 if (response && response.success) return; 264 270 } catch { ··· 271 277 exact: info.selectionText, 272 278 }; 273 279 } 280 + 281 + const targetUrl = canonicalUrl || tab.url; 274 282 275 283 if (selector) { 276 284 try { 277 285 await createHighlight({ 278 - url: tab.url, 286 + url: targetUrl, 279 287 title: tab.title, 280 288 selector: selector, 281 289 }); ··· 359 367 : API_BASE; 360 368 361 369 const pageUrl = request.data.url; 362 - const res = await fetch( 363 - `${currentApiUrl}/api/targets?source=${encodeURIComponent(pageUrl)}`, 370 + const citedUrls = request.data.citedUrls || []; 371 + const uniqueUrls = [...new Set([pageUrl, ...citedUrls])]; 372 + 373 + const fetchPromises = uniqueUrls.map((u) => 374 + fetch( 375 + `${currentApiUrl}/api/targets?source=${encodeURIComponent(u)}`, 376 + ).then((r) => r.json().catch(() => ({}))), 364 377 ); 365 - const data = await res.json(); 366 378 367 - const items = [...(data.annotations || []), ...(data.highlights || [])]; 368 - sendResponse({ success: true, data: items }); 379 + const results = await Promise.all(fetchPromises); 380 + let allItems = []; 381 + const seenIds = new Set(); 382 + 383 + results.forEach((data) => { 384 + const items = [ 385 + ...(data.annotations || []), 386 + ...(data.highlights || []), 387 + ]; 388 + items.forEach((item) => { 389 + const id = item.uri || item.id; 390 + if (id && !seenIds.has(id)) { 391 + seenIds.add(id); 392 + allItems.push(item); 393 + } 394 + }); 395 + }); 396 + 397 + sendResponse({ success: true, data: allItems }); 369 398 370 399 if (sender.tab) { 371 400 const count = items.length;
+39 -2
extension/content/content.js
··· 1024 1024 1025 1025 function fetchAnnotations(retryCount = 0) { 1026 1026 if (typeof chrome !== "undefined" && chrome.runtime) { 1027 + const citedUrls = Array.from(document.querySelectorAll("[cite]")) 1028 + .map((el) => el.getAttribute("cite")) 1029 + .filter((url) => url && url.startsWith("http")); 1030 + const uniqueCitedUrls = [...new Set(citedUrls)]; 1031 + 1027 1032 chrome.runtime.sendMessage( 1028 1033 { 1029 1034 type: "GET_ANNOTATIONS", 1030 - data: { url: window.location.href }, 1035 + data: { 1036 + url: window.location.href, 1037 + citedUrls: uniqueCitedUrls, 1038 + }, 1031 1039 }, 1032 1040 (res) => { 1033 1041 if (res && res.success && res.data && res.data.length > 0) { ··· 1043 1051 } 1044 1052 } 1045 1053 1054 + function findCanonicalUrl(range) { 1055 + if (!range) return null; 1056 + let node = range.commonAncestorContainer; 1057 + if (node.nodeType === Node.TEXT_NODE) { 1058 + node = node.parentNode; 1059 + } 1060 + 1061 + while (node && node !== document.body) { 1062 + if ( 1063 + (node.tagName === "BLOCKQUOTE" || node.tagName === "Q") && 1064 + node.hasAttribute("cite") 1065 + ) { 1066 + if (node.contains(range.commonAncestorContainer)) { 1067 + return node.getAttribute("cite"); 1068 + } 1069 + } 1070 + node = node.parentNode; 1071 + } 1072 + return null; 1073 + } 1074 + 1046 1075 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 1047 1076 if (request.type === "GET_SELECTOR_FOR_ANNOTATE_INLINE") { 1048 1077 const sel = window.getSelection(); ··· 1051 1080 return true; 1052 1081 } 1053 1082 const exact = sel.toString().trim(); 1054 - sendResponse({ selector: { type: "TextQuoteSelector", exact } }); 1083 + const canonicalUrl = findCanonicalUrl(sel.getRangeAt(0)); 1084 + 1085 + sendResponse({ 1086 + selector: { type: "TextQuoteSelector", exact }, 1087 + canonicalUrl, 1088 + }); 1055 1089 return true; 1056 1090 } 1057 1091 ··· 1074 1108 return true; 1075 1109 } 1076 1110 const exact = sel.toString().trim(); 1111 + const canonicalUrl = findCanonicalUrl(sel.getRangeAt(0)); 1112 + 1077 1113 sendResponse({ 1078 1114 success: false, 1079 1115 selector: { type: "TextQuoteSelector", exact }, 1116 + canonicalUrl, 1080 1117 }); 1081 1118 return true; 1082 1119 }