From 071ecbe13b93a9f8180f836f21c542ee52913131 Mon Sep 17 00:00:00 2001 From: scanash00 Date: Tue, 3 Mar 2026 04:06:30 -0900 Subject: [PATCH] new features and bug fixes - reimplement canonical annotations - implement DOI urls support --- backend/internal/oauth/handler.go | 11 +++- extension/src/entrypoints/background.ts | 35 +++++++++- extension/src/utils/messaging.ts | 2 +- extension/src/utils/overlay.ts | 86 ++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 9 deletions(-) diff --git a/backend/internal/oauth/handler.go b/backend/internal/oauth/handler.go index 73903d6..d27891d 100644 --- a/backend/internal/oauth/handler.go +++ b/backend/internal/oauth/handler.go @@ -571,14 +571,21 @@ func (h *Handler) HandleLogout(w http.ResponseWriter, r *http.Request) { } func (h *Handler) HandleSession(w http.ResponseWriter, r *http.Request) { + sessionID := "" cookie, err := r.Cookie("margin_session") - if err != nil { + if err == nil { + sessionID = cookie.Value + } else { + sessionID = r.Header.Get("X-Session-Token") + } + + if sessionID == "" { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"authenticated": false}) return } - did, handle, _, _, _, err := h.db.GetSession(cookie.Value) + did, handle, _, _, _, err := h.db.GetSession(sessionID) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"authenticated": false}) diff --git a/extension/src/entrypoints/background.ts b/extension/src/entrypoints/background.ts index 14e5817..5bddd90 100644 --- a/extension/src/entrypoints/background.ts +++ b/extension/src/entrypoints/background.ts @@ -53,8 +53,26 @@ export default defineBackground(() => { return await checkSession(); }); - onMessage('getAnnotations', async ({ data }) => { - return await getAnnotations(data.url, [], data.cacheBust); + onMessage('getAnnotations', async ({ data, sender }) => { + let citedUrls: string[] = data.citedUrls ?? []; + + if (data.citedUrls === undefined) { + try { + const tabId = + (sender as any)?.tab?.id ?? + (await browser.tabs.query({ active: true, currentWindow: true }))[0]?.id; + if (tabId !== undefined) { + const res = (await browser.tabs.sendMessage(tabId, { type: 'GET_DOI' })) as + | { doiUrl: string | null } + | undefined; + if (res?.doiUrl) citedUrls = [res.doiUrl]; + } + } catch { + // ignore + } + } + + return await getAnnotations(data.url, citedUrls, data.cacheBust); }); onMessage('activateOnPdf', async ({ data }) => { @@ -307,8 +325,19 @@ export default defineBackground(() => { return; } + let highlightUrl = resolveTabUrl(tab.url); + try { + const res = (await browser.tabs.sendMessage(tab.id!, { + type: 'GET_CITE_URL', + text: info.selectionText, + })) as { citeUrl: string | null } | undefined; + if (res?.citeUrl) highlightUrl = res.citeUrl; + } catch { + /* ignore */ + } + const result = await createHighlight({ - url: resolveTabUrl(tab.url), + url: highlightUrl, title: tab.title, selector: { type: 'TextQuoteSelector', diff --git a/extension/src/utils/messaging.ts b/extension/src/utils/messaging.ts index 3626fa1..7b2af48 100644 --- a/extension/src/utils/messaging.ts +++ b/extension/src/utils/messaging.ts @@ -11,7 +11,7 @@ import type { interface ProtocolMap { checkSession(): MarginSession; - getAnnotations(data: { url: string; cacheBust?: boolean }): Annotation[]; + getAnnotations(data: { url: string; citedUrls?: string[]; cacheBust?: boolean }): Annotation[]; activateOnPdf(data: { tabId: number; url: string }): { redirected: boolean }; createAnnotation(data: { url: string; diff --git a/extension/src/utils/overlay.ts b/extension/src/utils/overlay.ts index 0bff322..a5c9601 100644 --- a/extension/src/utils/overlay.ts +++ b/extension/src/utils/overlay.ts @@ -69,6 +69,73 @@ export async function initContentScript(ctx: { onInvalidated: (cb: () => void) = return window.location.href; } + function getPageDOIUrl(): string | null { + try { + if (new URL(window.location.href).hostname === 'doi.org') return null; + } catch { + return null; + } + + const metaDOI = + document.querySelector('meta[name="citation_doi"]') || + document.querySelector('meta[name="dc.identifier"]') || + document.querySelector('meta[name="DC.identifier"]'); + if (metaDOI?.content) { + const doi = metaDOI.content.replace(/^doi:/i, '').trim(); + if (doi.startsWith('10.')) return `https://doi.org/${doi}`; + } + + const canonical = document.querySelector('link[rel="canonical"]'); + if (canonical?.href) { + try { + if (new URL(canonical.href).hostname === 'doi.org') return canonical.href; + } catch { + /* ignore */ + } + } + + return null; + } + + function getPageCiteUrls(): string[] { + const urls = new Set(); + document.querySelectorAll('q[cite], blockquote[cite]').forEach((el) => { + const cite = el.getAttribute('cite'); + if (!cite) return; + try { + const abs = new URL(cite, window.location.href).href; + if (abs !== window.location.href) urls.add(abs); + } catch { + /* ignore */ + } + }); + return Array.from(urls); + } + + function getCiteUrlForText(text: string): string | null { + if (!text) return null; + if (!cachedMatcher) cachedMatcher = new DOMTextMatcher(); + const range = cachedMatcher.findRange(text); + if (!range) return null; + + let node: Node | null = range.commonAncestorContainer; + while (node && node !== document.body) { + if (node.nodeType === Node.ELEMENT_NODE) { + const el = node as Element; + if ((el.tagName === 'Q' || el.tagName === 'BLOCKQUOTE') && el.hasAttribute('cite')) { + const cite = el.getAttribute('cite')!; + try { + return new URL(cite, window.location.href).href; + } catch { + return null; + } + } + } + node = node.parentNode; + } + return null; + } + function isPdfContext(): boolean { return !!( document.querySelector('.pdfViewer') || @@ -382,8 +449,9 @@ export async function initContentScript(ctx: { onInvalidated: (cb: () => void) = submitBtn.textContent = 'Posting...'; try { + const citeUrl = getCiteUrlForText(quoteText); const res = await sendMessage('createAnnotation', { - url: getPageUrl(), + url: citeUrl || getPageUrl(), title: document.title, text, selector: { type: 'TextQuoteSelector', exact: quoteText }, @@ -425,6 +493,12 @@ export async function initContentScript(ctx: { onInvalidated: (cb: () => void) = const text = selection?.toString().trim() || ''; return Promise.resolve({ text }); } + if (message.type === 'GET_DOI') { + return Promise.resolve({ doiUrl: getPageDOIUrl() }); + } + if (message.type === 'GET_CITE_URL') { + return Promise.resolve({ citeUrl: getCiteUrlForText(message.text || '') }); + } }); function scrollToText(text: string) { @@ -519,15 +593,21 @@ export async function initContentScript(ctx: { onInvalidated: (cb: () => void) = } try { + const pageUrl = getPageUrl(); + const doiUrl = getPageDOIUrl(); + const citeUrls = getPageCiteUrls(); + const citedUrls = [...(doiUrl ? [doiUrl] : []), ...citeUrls]; + const annotations = await sendMessage('getAnnotations', { - url: getPageUrl(), + url: pageUrl, + citedUrls, cacheBust, }); sendMessage('updateBadge', { count: annotations?.length || 0 }); if (annotations) { - sendMessage('cacheAnnotations', { url: getPageUrl(), annotations }); + sendMessage('cacheAnnotations', { url: pageUrl, annotations }); } if (annotations && annotations.length > 0) { -- 2.51.2