diff --git a/extension/background/service-worker.js b/extension/background/service-worker.js index 407d05f..c3c6871 100644 --- a/extension/background/service-worker.js +++ b/extension/background/service-worker.js @@ -205,6 +205,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { if (info.menuItemId === "margin-annotate") { let selector = null; + let canonicalUrl = null; try { const response = await chrome.tabs.sendMessage(tab.id, { @@ -212,6 +213,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { selectionText: info.selectionText, }); selector = response?.selector; + canonicalUrl = response?.canonicalUrl; } catch { /* ignore */ } @@ -223,12 +225,14 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { }; } + const targetUrl = canonicalUrl || tab.url; + if (selector) { try { await chrome.tabs.sendMessage(tab.id, { type: "SHOW_INLINE_ANNOTATE", data: { - url: tab.url, + url: targetUrl, title: tab.title, selector: selector, }, @@ -240,7 +244,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { } if (WEB_BASE) { - let composeUrl = `${WEB_BASE}/new?url=${encodeURIComponent(tab.url)}`; + let composeUrl = `${WEB_BASE}/new?url=${encodeURIComponent(targetUrl)}`; if (selector) { composeUrl += `&selector=${encodeURIComponent(JSON.stringify(selector))}`; } @@ -251,6 +255,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { if (info.menuItemId === "margin-highlight") { let selector = null; + let canonicalUrl = null; try { const response = await chrome.tabs.sendMessage(tab.id, { @@ -259,6 +264,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { }); if (response?.selector) { selector = response.selector; + canonicalUrl = response.canonicalUrl; } if (response && response.success) return; } catch { @@ -272,10 +278,12 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { }; } + const targetUrl = canonicalUrl || tab.url; + if (selector) { try { await createHighlight({ - url: tab.url, + url: targetUrl, title: tab.title, selector: selector, }); @@ -359,13 +367,34 @@ async function handleMessage(request, sender, sendResponse) { : API_BASE; const pageUrl = request.data.url; - const res = await fetch( - `${currentApiUrl}/api/targets?source=${encodeURIComponent(pageUrl)}`, + const citedUrls = request.data.citedUrls || []; + const uniqueUrls = [...new Set([pageUrl, ...citedUrls])]; + + const fetchPromises = uniqueUrls.map((u) => + fetch( + `${currentApiUrl}/api/targets?source=${encodeURIComponent(u)}`, + ).then((r) => r.json().catch(() => ({}))), ); - const data = await res.json(); - const items = [...(data.annotations || []), ...(data.highlights || [])]; - sendResponse({ success: true, data: items }); + const results = await Promise.all(fetchPromises); + let allItems = []; + const seenIds = new Set(); + + results.forEach((data) => { + const items = [ + ...(data.annotations || []), + ...(data.highlights || []), + ]; + items.forEach((item) => { + const id = item.uri || item.id; + if (id && !seenIds.has(id)) { + seenIds.add(id); + allItems.push(item); + } + }); + }); + + sendResponse({ success: true, data: allItems }); if (sender.tab) { const count = items.length; diff --git a/extension/content/content.js b/extension/content/content.js index ceda258..3f0056d 100644 --- a/extension/content/content.js +++ b/extension/content/content.js @@ -1024,10 +1024,18 @@ function fetchAnnotations(retryCount = 0) { if (typeof chrome !== "undefined" && chrome.runtime) { + const citedUrls = Array.from(document.querySelectorAll("[cite]")) + .map((el) => el.getAttribute("cite")) + .filter((url) => url && url.startsWith("http")); + const uniqueCitedUrls = [...new Set(citedUrls)]; + chrome.runtime.sendMessage( { type: "GET_ANNOTATIONS", - data: { url: window.location.href }, + data: { + url: window.location.href, + citedUrls: uniqueCitedUrls, + }, }, (res) => { if (res && res.success && res.data && res.data.length > 0) { @@ -1043,6 +1051,27 @@ } } + function findCanonicalUrl(range) { + if (!range) return null; + let node = range.commonAncestorContainer; + if (node.nodeType === Node.TEXT_NODE) { + node = node.parentNode; + } + + while (node && node !== document.body) { + if ( + (node.tagName === "BLOCKQUOTE" || node.tagName === "Q") && + node.hasAttribute("cite") + ) { + if (node.contains(range.commonAncestorContainer)) { + return node.getAttribute("cite"); + } + } + node = node.parentNode; + } + return null; + } + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === "GET_SELECTOR_FOR_ANNOTATE_INLINE") { const sel = window.getSelection(); @@ -1051,7 +1080,12 @@ return true; } const exact = sel.toString().trim(); - sendResponse({ selector: { type: "TextQuoteSelector", exact } }); + const canonicalUrl = findCanonicalUrl(sel.getRangeAt(0)); + + sendResponse({ + selector: { type: "TextQuoteSelector", exact }, + canonicalUrl, + }); return true; } @@ -1074,9 +1108,12 @@ return true; } const exact = sel.toString().trim(); + const canonicalUrl = findCanonicalUrl(sel.getRangeAt(0)); + sendResponse({ success: false, selector: { type: "TextQuoteSelector", exact }, + canonicalUrl, }); return true; }