(() => { let sidebarHost = null; let sidebarShadow = null; let popoverEl = null; let activeItems = []; let currentSelection = null; const OVERLAY_STYLES = ` :host { all: initial; --bg-primary: #09090b; --bg-secondary: #0f0f12; --bg-tertiary: #18181b; --bg-card: #09090b; --bg-elevated: #18181b; --bg-hover: #27272a; --text-primary: #e4e4e7; --text-secondary: #a1a1aa; --border: #27272a; --accent: #6366f1; --accent-hover: #4f46e5; } :host(.light) { --bg-primary: #ffffff; --bg-secondary: #f4f4f5; --bg-tertiary: #e4e4e7; --bg-card: #ffffff; --bg-elevated: #f4f4f5; --bg-hover: #e4e4e7; --text-primary: #18181b; --text-secondary: #52525b; --border: #e4e4e7; --accent: #4f46e5; --accent-hover: #4338ca; } .margin-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } .margin-popover { position: absolute; width: 320px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 0; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.2); display: flex; flex-direction: column; pointer-events: auto; z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: var(--text-primary); opacity: 0; transform: scale(0.95); animation: popover-in 0.15s forwards; max-height: 480px; overflow: hidden; } @keyframes popover-in { to { opacity: 1; transform: scale(1); } } .popover-header { padding: 12px 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; background: var(--bg-secondary); border-radius: 12px 12px 0 0; font-weight: 600; font-size: 13px; color: var(--text-primary); } .popover-scroll-area { overflow-y: auto; max-height: 400px; } .popover-item-block { border-bottom: 1px solid var(--border); margin-bottom: 0; animation: fade-in 0.2s; } .popover-item-block:last-child { border-bottom: none; } .popover-item-header { padding: 12px 16px 4px; display: flex; align-items: center; gap: 8px; } .popover-avatar { width: 24px; height: 24px; border-radius: 50%; background: var(--bg-hover); display: flex; align-items: center; justify-content: center; font-size: 10px; color: var(--text-secondary); } .popover-handle { font-size: 12px; font-weight: 600; color: var(--text-primary); } .popover-close { background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; } .popover-close:hover { color: var(--text-primary); } .popover-content { padding: 4px 16px 12px; font-size: 13px; line-height: 1.5; color: var(--text-primary); } .popover-quote { margin-top: 8px; padding: 6px 10px; background: var(--bg-tertiary); border-left: 2px solid var(--accent); border-radius: 4px; font-size: 11px; color: var(--text-secondary); font-style: italic; } .popover-actions { padding: 8px 16px; display: flex; justify-content: flex-end; gap: 8px; } .btn-action { background: none; border: 1px solid var(--border); border-radius: 4px; padding: 4px 8px; color: var(--text-secondary); font-size: 11px; cursor: pointer; } .btn-action:hover { background: var(--bg-hover); color: var(--text-primary); } .margin-selection-popup { position: fixed; display: flex; gap: 4px; padding: 6px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 16px rgba(0,0,0,0.4); z-index: 2147483647; pointer-events: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; animation: popover-in 0.15s forwards; } .selection-btn { display: flex; align-items: center; gap: 6px; padding: 6px 12px; background: transparent; border: none; border-radius: 6px; color: var(--text-primary); font-size: 12px; font-weight: 500; cursor: pointer; transition: background 0.15s; } .selection-btn:hover { background: var(--bg-hover); } .selection-btn svg { width: 14px; height: 14px; } .inline-compose-modal { position: fixed; width: 340px; max-width: calc(100vw - 40px); background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; box-sizing: border-box; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5); z-index: 2147483647; pointer-events: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: var(--text-primary); animation: popover-in 0.15s forwards; overflow: hidden; } .inline-compose-modal * { box-sizing: border-box; } .inline-compose-quote { padding: 8px 12px; background: var(--bg-tertiary); border-left: 3px solid var(--accent); border-radius: 4px; font-size: 12px; color: var(--text-secondary); font-style: italic; margin-bottom: 12px; max-height: 60px; overflow: hidden; word-break: break-word; } .inline-compose-textarea { width: 100%; min-height: 80px; padding: 10px 12px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary); font-family: inherit; font-size: 13px; resize: vertical; margin-bottom: 12px; box-sizing: border-box; } .inline-compose-textarea:focus { outline: none; border-color: var(--accent); } .inline-compose-actions { display: flex; justify-content: flex-end; gap: 8px; } .btn-cancel { padding: 8px 16px; background: transparent; border: 1px solid var(--border); border-radius: 6px; color: var(--text-secondary); font-size: 13px; cursor: pointer; } .btn-cancel:hover { background: var(--bg-hover); color: var(--text-primary); } .btn-submit { padding: 8px 16px; background: var(--accent); border: none; border-radius: 6px; color: white; font-size: 13px; font-weight: 500; cursor: pointer; } .btn-submit:hover { background: var(--accent-hover); } .btn-submit:disabled { opacity: 0.5; cursor: not-allowed; } .reply-section { border-top: 1px solid var(--border); padding: 12px 16px; background: var(--bg-secondary); border-radius: 0 0 12px 12px; } .reply-textarea { width: 100%; min-height: 60px; padding: 8px 10px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; color: var(--text-primary); font-family: inherit; font-size: 12px; resize: none; margin-bottom: 8px; } .reply-textarea:focus { outline: none; border-color: var(--accent); } .reply-submit { padding: 6px 12px; background: var(--accent); border: none; border-radius: 4px; color: white; font-size: 11px; font-weight: 500; cursor: pointer; float: right; } .reply-submit:disabled { opacity: 0.5; } .reply-item { padding: 8px 0; border-top: 1px solid var(--border); } .reply-item:first-child { border-top: none; } .reply-author { font-size: 11px; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px; } .reply-text { font-size: 12px; color: var(--text-primary); line-height: 1.4; } `; class DOMTextMatcher { constructor() { this.textNodes = []; this.corpus = ""; this.indices = []; this.buildMap(); } buildMap() { const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { if (!node.parentNode) return NodeFilter.FILTER_REJECT; const tag = node.parentNode.tagName; if ( ["SCRIPT", "STYLE", "NOSCRIPT", "TEXTAREA", "INPUT"].includes(tag) ) return NodeFilter.FILTER_REJECT; if (node.textContent.trim().length === 0) return NodeFilter.FILTER_SKIP; if (node.parentNode.offsetParent === null) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; }, }, ); let currentNode; let index = 0; while ((currentNode = walker.nextNode())) { const text = currentNode.textContent; this.textNodes.push(currentNode); this.corpus += text; this.indices.push({ start: index, node: currentNode, length: text.length, }); index += text.length; } } findRange(searchText) { if (!searchText) return null; let matchIndex = this.corpus.indexOf(searchText); if (matchIndex === -1) { const normalizedSearch = searchText.replace(/\s+/g, " ").trim(); matchIndex = this.corpus.indexOf(normalizedSearch); if (matchIndex === -1) { const fuzzyMatch = this.fuzzyFindInCorpus(searchText); if (fuzzyMatch) { const start = this.mapIndexToPoint(fuzzyMatch.start); const end = this.mapIndexToPoint(fuzzyMatch.end); if (start && end) { const range = document.createRange(); range.setStart(start.node, start.offset); range.setEnd(end.node, end.offset); return range; } } return null; } } const start = this.mapIndexToPoint(matchIndex); const end = this.mapIndexToPoint(matchIndex + searchText.length); if (start && end) { const range = document.createRange(); range.setStart(start.node, start.offset); range.setEnd(end.node, end.offset); return range; } return null; } fuzzyFindInCorpus(searchText) { const searchWords = searchText .trim() .split(/\s+/) .filter((w) => w.length > 0); if (searchWords.length === 0) return null; const corpusLower = this.corpus.toLowerCase(); const firstWord = searchWords[0].toLowerCase(); let searchStart = 0; while (searchStart < corpusLower.length) { const wordStart = corpusLower.indexOf(firstWord, searchStart); if (wordStart === -1) break; let corpusPos = wordStart; let matched = true; let lastMatchEnd = wordStart; for (const word of searchWords) { const wordLower = word.toLowerCase(); while ( corpusPos < corpusLower.length && /\s/.test(this.corpus[corpusPos]) ) { corpusPos++; } const corpusSlice = corpusLower.slice( corpusPos, corpusPos + wordLower.length, ); if (corpusSlice !== wordLower) { matched = false; break; } corpusPos += wordLower.length; lastMatchEnd = corpusPos; } if (matched) { return { start: wordStart, end: lastMatchEnd }; } searchStart = wordStart + 1; } return null; } mapIndexToPoint(corpusIndex) { for (const info of this.indices) { if ( corpusIndex >= info.start && corpusIndex < info.start + info.length ) { return { node: info.node, offset: corpusIndex - info.start }; } } if (this.indices.length > 0) { const last = this.indices[this.indices.length - 1]; if (corpusIndex === last.start + last.length) { return { node: last.node, offset: last.length }; } } return null; } } function applyTheme(theme) { if (!sidebarHost) return; sidebarHost.classList.remove("light", "dark"); if (theme === "system" || !theme) { if (window.matchMedia("(prefers-color-scheme: light)").matches) { sidebarHost.classList.add("light"); } } else { sidebarHost.classList.add(theme); } } window .matchMedia("(prefers-color-scheme: light)") .addEventListener("change", (e) => { chrome.storage.local.get(["theme"], (result) => { if (!result.theme || result.theme === "system") { if (e.matches) { sidebarHost?.classList.add("light"); } else { sidebarHost?.classList.remove("light"); } } }); }); function initOverlay() { sidebarHost = document.createElement("div"); sidebarHost.id = "margin-overlay-host"; sidebarHost.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: 0; overflow: visible; pointer-events: none; z-index: 2147483647; `; document.body?.appendChild(sidebarHost) || document.documentElement.appendChild(sidebarHost); sidebarShadow = sidebarHost.attachShadow({ mode: "open" }); const styleEl = document.createElement("style"); styleEl.textContent = OVERLAY_STYLES; sidebarShadow.appendChild(styleEl); const container = document.createElement("div"); container.className = "margin-overlay"; container.id = "margin-overlay-container"; sidebarShadow.appendChild(container); if (typeof chrome !== "undefined" && chrome.storage) { chrome.storage.local.get(["showOverlay", "theme"], (result) => { applyTheme(result.theme); if (result.showOverlay === false) { sidebarHost.style.display = "none"; } else { fetchAnnotations(); } }); } else { fetchAnnotations(); } document.addEventListener("mousemove", handleMouseMove); document.addEventListener("click", handleDocumentClick, true); chrome.storage.onChanged.addListener((changes, area) => { if (area === "local") { if (changes.theme) { applyTheme(changes.theme.newValue); } if (changes.showOverlay) { if (changes.showOverlay.newValue === false) { sidebarHost.style.display = "none"; activeItems = []; if (typeof CSS !== "undefined" && CSS.highlights) { CSS.highlights.clear(); } } else { sidebarHost.style.display = ""; fetchAnnotations(); } } } }); } function showInlineComposeModal() { if (!sidebarShadow || !currentSelection) return; const container = sidebarShadow.getElementById("margin-overlay-container"); if (!container) return; const existingModal = container.querySelector(".inline-compose-modal"); if (existingModal) existingModal.remove(); const modal = document.createElement("div"); modal.className = "inline-compose-modal"; modal.style.left = `${Math.max(20, (window.innerWidth - 340) / 2)}px`; modal.style.top = `${Math.min(200, window.innerHeight / 4)}px`; const truncatedQuote = currentSelection.text.length > 100 ? currentSelection.text.substring(0, 100) + "..." : currentSelection.text; modal.innerHTML = `
"${truncatedQuote}"
`; const textarea = modal.querySelector("textarea"); const submitBtn = modal.querySelector(".btn-submit"); const cancelBtn = modal.querySelector(".btn-cancel"); cancelBtn.addEventListener("click", () => { modal.remove(); }); submitBtn.addEventListener("click", async () => { const text = textarea.value.trim(); if (!text) return; submitBtn.disabled = true; submitBtn.textContent = "Posting..."; chrome.runtime.sendMessage( { type: "CREATE_ANNOTATION", data: { url: currentSelection.url || window.location.href, title: currentSelection.title || document.title, text: text, selector: currentSelection.selector, }, }, (res) => { if (res && res.success) { modal.remove(); fetchAnnotations(); } else { submitBtn.disabled = false; submitBtn.textContent = "Post Annotation"; alert( "Failed to create annotation: " + (res?.error || "Unknown error"), ); } }, ); }); container.appendChild(modal); textarea.focus(); const handleEscape = (e) => { if (e.key === "Escape") { modal.remove(); document.removeEventListener("keydown", handleEscape); } }; document.addEventListener("keydown", handleEscape); } let hoverIndicator = null; function handleMouseMove(e) { const x = e.clientX; const y = e.clientY; if (sidebarHost && sidebarHost.style.display === "none") return; let foundItems = []; let firstRange = null; for (const { range, item } of activeItems) { const rects = range.getClientRects(); for (const rect of rects) { if ( x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom ) { let container = range.commonAncestorContainer; if (container.nodeType === Node.TEXT_NODE) { container = container.parentNode; } if ( container && (e.target.contains(container) || container.contains(e.target)) ) { if (!firstRange) firstRange = range; if (!foundItems.some((f) => f.item === item)) { foundItems.push({ range, item, rect }); } } break; } } } if (foundItems.length > 0) { document.body.style.cursor = "pointer"; if (!hoverIndicator && sidebarShadow) { const container = sidebarShadow.getElementById( "margin-overlay-container", ); if (container) { hoverIndicator = document.createElement("div"); hoverIndicator.className = "margin-hover-indicator"; hoverIndicator.style.cssText = ` position: fixed; display: flex; align-items: center; pointer-events: none; z-index: 2147483647; opacity: 0; transition: opacity 0.15s, transform 0.15s; transform: scale(0.8); `; container.appendChild(hoverIndicator); } } if (hoverIndicator) { const authorsMap = new Map(); foundItems.forEach(({ item }) => { const author = item.author || item.creator || {}; const id = author.did || author.handle || "unknown"; if (!authorsMap.has(id)) { authorsMap.set(id, author); } }); const uniqueAuthors = Array.from(authorsMap.values()); const maxShow = 3; const displayAuthors = uniqueAuthors.slice(0, maxShow); const overflow = uniqueAuthors.length - maxShow; let html = displayAuthors .map((author, i) => { const avatar = author.avatar; const handle = author.handle || "U"; const marginLeft = i === 0 ? "0" : "-8px"; if (avatar) { return ``; } else { return `
${handle[0]?.toUpperCase() || "U"}
`; } }) .join(""); if (overflow > 0) { html += `
+${overflow}
`; } hoverIndicator.innerHTML = html; const firstRect = firstRange.getClientRects()[0]; const totalWidth = Math.min(uniqueAuthors.length, maxShow + (overflow > 0 ? 1 : 0)) * 18 + 8; const leftPos = firstRect.left - totalWidth; const topPos = firstRect.top + firstRect.height / 2 - 12; hoverIndicator.style.left = `${leftPos}px`; hoverIndicator.style.top = `${topPos}px`; hoverIndicator.style.opacity = "1"; hoverIndicator.style.transform = "scale(1)"; } } else { document.body.style.cursor = ""; if (hoverIndicator) { hoverIndicator.style.opacity = "0"; hoverIndicator.style.transform = "scale(0.8)"; } } } function handleDocumentClick(e) { const x = e.clientX; const y = e.clientY; if (sidebarHost && sidebarHost.style.display === "none") return; if (popoverEl && sidebarShadow) { const rect = popoverEl.getBoundingClientRect(); if ( x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom ) { return; } } let clickedItems = []; for (const { range, item } of activeItems) { const rects = range.getClientRects(); for (const rect of rects) { if ( x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom ) { let container = range.commonAncestorContainer; if (container.nodeType === Node.TEXT_NODE) { container = container.parentNode; } if ( container && (e.target.contains(container) || container.contains(e.target)) ) { if (!clickedItems.includes(item)) { clickedItems.push(item); } } break; } } } if (clickedItems.length > 0) { e.preventDefault(); e.stopPropagation(); if (popoverEl) { const currentIds = popoverEl.dataset.itemIds; const newIds = clickedItems .map((i) => i.uri || i.id) .sort() .join(","); if (currentIds === newIds) { popoverEl.remove(); popoverEl = null; return; } } const firstItem = clickedItems[0]; const match = activeItems.find((x) => x.item === firstItem); if (match) { const rects = match.range.getClientRects(); if (rects.length > 0) { const rect = rects[0]; const top = rect.top + window.scrollY; const left = rect.left + window.scrollX; showPopover(clickedItems, top, left); } } } else { if (popoverEl) { popoverEl.remove(); popoverEl = null; } } } function renderBadges(annotations) { if (!sidebarShadow) return; const itemsToRender = annotations || []; activeItems = []; const rangesByColor = {}; const matcher = new DOMTextMatcher(); itemsToRender.forEach((item) => { const selector = item.target?.selector || item.selector; if (!selector?.exact) return; const range = matcher.findRange(selector.exact); if (range) { activeItems.push({ range, item }); const color = item.color || "#6366f1"; if (!rangesByColor[color]) rangesByColor[color] = []; rangesByColor[color].push(range); } }); if (typeof CSS !== "undefined" && CSS.highlights) { CSS.highlights.clear(); for (const [color, ranges] of Object.entries(rangesByColor)) { const highlight = new Highlight(...ranges); const safeColor = color.replace(/[^a-zA-Z0-9]/g, ""); const name = `margin-hl-${safeColor}`; CSS.highlights.set(name, highlight); injectHighlightStyle(name, color); } } } const injectedStyles = new Set(); function injectHighlightStyle(name, color) { if (injectedStyles.has(name)) return; const style = document.createElement("style"); style.textContent = ` ::highlight(${name}) { text-decoration: underline; text-decoration-color: ${color}; text-decoration-thickness: 2px; text-underline-offset: 2px; cursor: pointer; } `; document.head.appendChild(style); injectedStyles.add(name); } function showPopover(items, top, left) { if (popoverEl) popoverEl.remove(); const container = sidebarShadow.getElementById("margin-overlay-container"); popoverEl = document.createElement("div"); popoverEl.className = "margin-popover"; const ids = items .map((i) => i.uri || i.id) .sort() .join(","); popoverEl.dataset.itemIds = ids; const popWidth = 320; const screenWidth = window.innerWidth; let finalLeft = left; if (left + popWidth > screenWidth) finalLeft = screenWidth - popWidth - 20; popoverEl.style.top = `${top + 20}px`; popoverEl.style.left = `${finalLeft}px`; const hasHighlights = items.some((item) => item.type === "Highlight"); const hasAnnotations = items.some((item) => item.type !== "Highlight"); let title; if (items.length > 1) { if (hasHighlights && hasAnnotations) { title = `${items.length} Items`; } else if (hasHighlights) { title = `${items.length} Highlights`; } else { title = `${items.length} Annotations`; } } else { title = items[0]?.type === "Highlight" ? "Highlight" : "Annotation"; } let contentHtml = items .map((item) => { const author = item.author || item.creator || {}; const handle = author.handle || "User"; const avatar = author.avatar; const text = item.body?.value || item.text || ""; const quote = item.target?.selector?.exact || item.selector?.exact || ""; const id = item.id || item.uri; let avatarHtml = `
${handle[0]?.toUpperCase() || "U"}
`; if (avatar) { avatarHtml = ``; } const isHighlight = item.type === "Highlight"; let bodyHtml = ""; if (isHighlight) { bodyHtml = `
"${quote}"
`; } else { bodyHtml = `
${text}
`; if (quote) { bodyHtml += `
"${quote}"
`; } } return `
${avatarHtml} @${handle}
${bodyHtml}
${!isHighlight ? `` : ""}
`; }) .join(""); popoverEl.innerHTML = `
${title}
${contentHtml}
`; popoverEl.querySelector(".popover-close").addEventListener("click", (e) => { e.stopPropagation(); popoverEl.remove(); popoverEl = null; }); const replyBtns = popoverEl.querySelectorAll(".btn-reply"); replyBtns.forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = btn.getAttribute("data-id"); if (id) { chrome.runtime.sendMessage({ type: "OPEN_APP_URL", data: { path: `/annotation/${encodeURIComponent(id)}` }, }); } }); }); const shareBtns = popoverEl.querySelectorAll(".btn-share"); shareBtns.forEach((btn) => { btn.addEventListener("click", async () => { const id = btn.getAttribute("data-id"); const text = btn.getAttribute("data-text"); const quote = btn.getAttribute("data-quote"); const u = `https://margin.at/annotation/${encodeURIComponent(id)}`; const shareText = `${text ? text + "\n" : ""}${quote ? `"${quote}"\n` : ""}${u}`; try { await navigator.clipboard.writeText(shareText); const originalText = btn.innerText; btn.innerText = "Copied!"; setTimeout(() => (btn.innerText = originalText), 2000); } catch (e) { console.error("Failed to copy", e); } }); }); container.appendChild(popoverEl); setTimeout(() => { document.addEventListener("click", closePopoverOutside); }, 0); } function closePopoverOutside() { if (popoverEl) { popoverEl.remove(); popoverEl = null; document.removeEventListener("click", closePopoverOutside); } } 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, citedUrls: uniqueCitedUrls, }, }, (res) => { if (res && res.success && res.data && res.data.length > 0) { renderBadges(res.data); } else if (retryCount < 3) { setTimeout( () => fetchAnnotations(retryCount + 1), 1000 * (retryCount + 1), ); } }, ); } } 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(); if (!sel || !sel.toString()) { sendResponse({ selector: null }); return true; } const exact = sel.toString().trim(); const canonicalUrl = findCanonicalUrl(sel.getRangeAt(0)); sendResponse({ selector: { type: "TextQuoteSelector", exact }, canonicalUrl, }); return true; } if (request.type === "SHOW_INLINE_ANNOTATE") { currentSelection = { text: request.data.selector?.exact || "", selector: request.data.selector, url: request.data.url, title: request.data.title, }; showInlineComposeModal(); sendResponse({ success: true }); return true; } if (request.type === "GET_SELECTOR_FOR_HIGHLIGHT") { const sel = window.getSelection(); if (!sel || !sel.toString().trim()) { sendResponse({ success: false, selector: null }); return true; } const exact = sel.toString().trim(); const canonicalUrl = findCanonicalUrl(sel.getRangeAt(0)); sendResponse({ success: false, selector: { type: "TextQuoteSelector", exact }, canonicalUrl, }); return true; } if (request.type === "REFRESH_ANNOTATIONS") { fetchAnnotations(); sendResponse({ success: true }); return true; } if (request.type === "UPDATE_OVERLAY_VISIBILITY") { if (sidebarHost) { sidebarHost.style.display = request.show ? "block" : "none"; } if (request.show) { fetchAnnotations(); } else { activeItems = []; if (typeof CSS !== "undefined" && CSS.highlights) { CSS.highlights.clear(); } } sendResponse({ success: true }); return true; } if (request.type === "SCROLL_TO_TEXT") { const selector = request.selector; if (selector?.exact) { const matcher = new DOMTextMatcher(); const range = matcher.findRange(selector.exact); if (range) { const rect = range.getBoundingClientRect(); window.scrollTo({ top: window.scrollY + rect.top - window.innerHeight / 3, behavior: "smooth", }); const highlight = new Highlight(range); CSS.highlights.set("margin-scroll-flash", highlight); injectHighlightStyle("margin-scroll-flash", "#8b5cf6"); setTimeout(() => CSS.highlights.delete("margin-scroll-flash"), 2000); } } } return true; }); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initOverlay); } else { initOverlay(); } window.addEventListener("load", () => { if (typeof chrome !== "undefined" && chrome.storage) { chrome.storage.local.get(["showOverlay"], (result) => { if (result.showOverlay !== false) { setTimeout(() => fetchAnnotations(), 500); } }); } else { setTimeout(() => fetchAnnotations(), 500); } }); let lastUrl = window.location.href; function checkUrlChange() { if (window.location.href !== lastUrl) { lastUrl = window.location.href; onUrlChange(); } } function onUrlChange() { if (typeof CSS !== "undefined" && CSS.highlights) { CSS.highlights.clear(); } activeItems = []; if (typeof chrome !== "undefined" && chrome.storage) { chrome.storage.local.get(["showOverlay"], (result) => { if (result.showOverlay !== false) { fetchAnnotations(); } }); } else { fetchAnnotations(); } } window.addEventListener("popstate", onUrlChange); const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(this, args); checkUrlChange(); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); checkUrlChange(); }; setInterval(checkUrlChange, 1000); })();