Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

new features and bug fixes - reimplement canonical annotations - implement DOI urls support

+125 -9
+9 -2
backend/internal/oauth/handler.go
··· 571 571 } 572 572 573 573 func (h *Handler) HandleSession(w http.ResponseWriter, r *http.Request) { 574 + sessionID := "" 574 575 cookie, err := r.Cookie("margin_session") 575 - if err != nil { 576 + if err == nil { 577 + sessionID = cookie.Value 578 + } else { 579 + sessionID = r.Header.Get("X-Session-Token") 580 + } 581 + 582 + if sessionID == "" { 576 583 w.Header().Set("Content-Type", "application/json") 577 584 json.NewEncoder(w).Encode(map[string]interface{}{"authenticated": false}) 578 585 return 579 586 } 580 587 581 - did, handle, _, _, _, err := h.db.GetSession(cookie.Value) 588 + did, handle, _, _, _, err := h.db.GetSession(sessionID) 582 589 if err != nil { 583 590 w.Header().Set("Content-Type", "application/json") 584 591 json.NewEncoder(w).Encode(map[string]interface{}{"authenticated": false})
+32 -3
extension/src/entrypoints/background.ts
··· 53 53 return await checkSession(); 54 54 }); 55 55 56 - onMessage('getAnnotations', async ({ data }) => { 57 - return await getAnnotations(data.url, [], data.cacheBust); 56 + onMessage('getAnnotations', async ({ data, sender }) => { 57 + let citedUrls: string[] = data.citedUrls ?? []; 58 + 59 + if (data.citedUrls === undefined) { 60 + try { 61 + const tabId = 62 + (sender as any)?.tab?.id ?? 63 + (await browser.tabs.query({ active: true, currentWindow: true }))[0]?.id; 64 + if (tabId !== undefined) { 65 + const res = (await browser.tabs.sendMessage(tabId, { type: 'GET_DOI' })) as 66 + | { doiUrl: string | null } 67 + | undefined; 68 + if (res?.doiUrl) citedUrls = [res.doiUrl]; 69 + } 70 + } catch { 71 + // ignore 72 + } 73 + } 74 + 75 + return await getAnnotations(data.url, citedUrls, data.cacheBust); 58 76 }); 59 77 60 78 onMessage('activateOnPdf', async ({ data }) => { ··· 307 325 return; 308 326 } 309 327 328 + let highlightUrl = resolveTabUrl(tab.url); 329 + try { 330 + const res = (await browser.tabs.sendMessage(tab.id!, { 331 + type: 'GET_CITE_URL', 332 + text: info.selectionText, 333 + })) as { citeUrl: string | null } | undefined; 334 + if (res?.citeUrl) highlightUrl = res.citeUrl; 335 + } catch { 336 + /* ignore */ 337 + } 338 + 310 339 const result = await createHighlight({ 311 - url: resolveTabUrl(tab.url), 340 + url: highlightUrl, 312 341 title: tab.title, 313 342 selector: { 314 343 type: 'TextQuoteSelector',
+1 -1
extension/src/utils/messaging.ts
··· 11 11 interface ProtocolMap { 12 12 checkSession(): MarginSession; 13 13 14 - getAnnotations(data: { url: string; cacheBust?: boolean }): Annotation[]; 14 + getAnnotations(data: { url: string; citedUrls?: string[]; cacheBust?: boolean }): Annotation[]; 15 15 activateOnPdf(data: { tabId: number; url: string }): { redirected: boolean }; 16 16 createAnnotation(data: { 17 17 url: string;
+83 -3
extension/src/utils/overlay.ts
··· 69 69 return window.location.href; 70 70 } 71 71 72 + function getPageDOIUrl(): string | null { 73 + try { 74 + if (new URL(window.location.href).hostname === 'doi.org') return null; 75 + } catch { 76 + return null; 77 + } 78 + 79 + const metaDOI = 80 + document.querySelector<HTMLMetaElement>('meta[name="citation_doi"]') || 81 + document.querySelector<HTMLMetaElement>('meta[name="dc.identifier"]') || 82 + document.querySelector<HTMLMetaElement>('meta[name="DC.identifier"]'); 83 + if (metaDOI?.content) { 84 + const doi = metaDOI.content.replace(/^doi:/i, '').trim(); 85 + if (doi.startsWith('10.')) return `https://doi.org/${doi}`; 86 + } 87 + 88 + const canonical = document.querySelector<HTMLLinkElement>('link[rel="canonical"]'); 89 + if (canonical?.href) { 90 + try { 91 + if (new URL(canonical.href).hostname === 'doi.org') return canonical.href; 92 + } catch { 93 + /* ignore */ 94 + } 95 + } 96 + 97 + return null; 98 + } 99 + 100 + function getPageCiteUrls(): string[] { 101 + const urls = new Set<string>(); 102 + document.querySelectorAll<Element>('q[cite], blockquote[cite]').forEach((el) => { 103 + const cite = el.getAttribute('cite'); 104 + if (!cite) return; 105 + try { 106 + const abs = new URL(cite, window.location.href).href; 107 + if (abs !== window.location.href) urls.add(abs); 108 + } catch { 109 + /* ignore */ 110 + } 111 + }); 112 + return Array.from(urls); 113 + } 114 + 115 + function getCiteUrlForText(text: string): string | null { 116 + if (!text) return null; 117 + if (!cachedMatcher) cachedMatcher = new DOMTextMatcher(); 118 + const range = cachedMatcher.findRange(text); 119 + if (!range) return null; 120 + 121 + let node: Node | null = range.commonAncestorContainer; 122 + while (node && node !== document.body) { 123 + if (node.nodeType === Node.ELEMENT_NODE) { 124 + const el = node as Element; 125 + if ((el.tagName === 'Q' || el.tagName === 'BLOCKQUOTE') && el.hasAttribute('cite')) { 126 + const cite = el.getAttribute('cite')!; 127 + try { 128 + return new URL(cite, window.location.href).href; 129 + } catch { 130 + return null; 131 + } 132 + } 133 + } 134 + node = node.parentNode; 135 + } 136 + return null; 137 + } 138 + 72 139 function isPdfContext(): boolean { 73 140 return !!( 74 141 document.querySelector('.pdfViewer') || ··· 382 449 submitBtn.textContent = 'Posting...'; 383 450 384 451 try { 452 + const citeUrl = getCiteUrlForText(quoteText); 385 453 const res = await sendMessage('createAnnotation', { 386 - url: getPageUrl(), 454 + url: citeUrl || getPageUrl(), 387 455 title: document.title, 388 456 text, 389 457 selector: { type: 'TextQuoteSelector', exact: quoteText }, ··· 424 492 const selection = window.getSelection(); 425 493 const text = selection?.toString().trim() || ''; 426 494 return Promise.resolve({ text }); 495 + } 496 + if (message.type === 'GET_DOI') { 497 + return Promise.resolve({ doiUrl: getPageDOIUrl() }); 498 + } 499 + if (message.type === 'GET_CITE_URL') { 500 + return Promise.resolve({ citeUrl: getCiteUrlForText(message.text || '') }); 427 501 } 428 502 }); 429 503 ··· 519 593 } 520 594 521 595 try { 596 + const pageUrl = getPageUrl(); 597 + const doiUrl = getPageDOIUrl(); 598 + const citeUrls = getPageCiteUrls(); 599 + const citedUrls = [...(doiUrl ? [doiUrl] : []), ...citeUrls]; 600 + 522 601 const annotations = await sendMessage('getAnnotations', { 523 - url: getPageUrl(), 602 + url: pageUrl, 603 + citedUrls, 524 604 cacheBust, 525 605 }); 526 606 527 607 sendMessage('updateBadge', { count: annotations?.length || 0 }); 528 608 529 609 if (annotations) { 530 - sendMessage('cacheAnnotations', { url: getPageUrl(), annotations }); 610 + sendMessage('cacheAnnotations', { url: pageUrl, annotations }); 531 611 } 532 612 533 613 if (annotations && annotations.length > 0) {