const COLLECTIONS_URL = "./data/collections.json"; const PANEL_TITLE = "Collections"; const LINK_CLASS = "nv3-collection-link"; const ROW_CLASS = "nv3-collection-link-row"; const TOOLTIP_LINK_CLASS = "nv3-collection-tooltip-link"; const TOOLTIP_ITEM_CLASS = "nv3-collection-tooltip-item"; const TOOLTIP_LIST_CLASS = "nv3-collection-tooltip-list"; const TOOLTIP_ITEM_LABEL_CLASS = "nv3-collection-tooltip-item-label"; let collectionList = []; let collectionsByName = new Map(); let observer = null; let scanQueued = false; const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim(); function injectStyles() { if (document.getElementById("nv3-collection-link-styles")) return; const style = document.createElement("style"); style.id = "nv3-collection-link-styles"; style.textContent = ` .${ROW_CLASS} { position: relative; } .${LINK_CLASS} { display: inline-flex; flex: 0 0 auto; width: 1rem; height: 1rem; margin-left: 0.35rem; align-items: center; justify-content: center; border-radius: 0.25rem; color: rgb(100 116 139); opacity: 0.8; text-decoration: none; vertical-align: -0.125rem; } .${LINK_CLASS}:hover { background: rgb(226 232 240); color: rgb(37 99 235); opacity: 1; } .dark .${LINK_CLASS}:hover { background: rgb(51 65 85); color: rgb(147 197 253); } .${LINK_CLASS} svg { width: 0.75rem; height: 0.75rem; pointer-events: none; } .${TOOLTIP_LINK_CLASS} { display: inline-flex; flex: 0 0 auto; width: 1rem; height: 1rem; margin-left: 0.2rem; align-items: center; justify-content: center; border-radius: 0.25rem; color: rgb(100 116 139); opacity: 0.8; text-decoration: none; vertical-align: -0.125rem; } .${TOOLTIP_LINK_CLASS}:hover { background: rgb(226 232 240); color: rgb(37 99 235); opacity: 1; } .dark .${TOOLTIP_LINK_CLASS}:hover { background: rgb(51 65 85); color: rgb(147 197 253); } .${TOOLTIP_LINK_CLASS} svg { width: 0.75rem; height: 0.75rem; flex: 0 0 auto; pointer-events: none; } .${TOOLTIP_ITEM_CLASS} { display: inline-flex; max-width: 100%; margin: 0.125rem 0.15rem 0.125rem 0; padding: 0.125rem 0.35rem; align-items: center; gap: 0.25rem; border: 1px solid rgb(203 213 225); border-radius: 0.375rem; background: rgb(241 245 249 / 0.35); color: rgb(51 65 85); white-space: normal; } .dark .${TOOLTIP_ITEM_CLASS} { border-color: rgb(51 65 85); background: rgb(51 65 85 / 0.28); color: rgb(203 213 225); } .${TOOLTIP_LIST_CLASS} { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.125rem; } .${TOOLTIP_ITEM_LABEL_CLASS} { color: rgb(100 116 139); font-weight: 600; } .dark .${TOOLTIP_ITEM_LABEL_CLASS} { color: rgb(148 163 184); } `; document.head.append(style); } function makeIcon() { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("fill", "none"); svg.setAttribute("stroke", "currentColor"); svg.setAttribute("stroke-width", "2"); svg.setAttribute("stroke-linecap", "round"); svg.setAttribute("stroke-linejoin", "round"); svg.setAttribute("aria-hidden", "true"); const paths = [ "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", "M15 3h6v6", "M10 14 21 3", ]; for (const d of paths) { const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", d); svg.append(path); } return svg; } function collectionForText(text) { const normalized = normalizeText(text); return collectionsByName.get(normalized) ?? null; } function titleTextNodes() { if (!document.body) return []; const nodes = []; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (isInsideTooltip(node.parentElement)) return NodeFilter.FILTER_REJECT; return normalizeText(node.nodeValue) === PANEL_TITLE ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, }); while (walker.nextNode()) nodes.push(walker.currentNode); return nodes; } function collectionHitCount(text) { return collectionList.reduce( (count, collection) => count + (text.includes(collection.display_name) ? 1 : 0), 0, ); } function findPanelFromTitle(titleNode) { for ( let element = titleNode.parentElement, depth = 0; element && element !== document.body && depth < 12; element = element.parentElement, depth += 1 ) { const text = normalizeText(element.textContent); if (!text.includes(PANEL_TITLE)) continue; if (text.length > 5000) continue; if (collectionHitCount(text) > 0) return element; } return null; } function findRowForLabel(labelElement, displayName) { let best = labelElement; for ( let element = labelElement; element && element !== document.body; element = element.parentElement ) { const text = normalizeText(element.textContent); if (!text.includes(displayName)) continue; if (text.includes(PANEL_TITLE)) break; if (text.length > displayName.length + 160) break; best = element; } return best; } function createLink(collection) { if (!collection.url) return null; const link = document.createElement("a"); link.className = LINK_CLASS; link.href = collection.url; link.target = "_blank"; link.rel = "noopener noreferrer"; link.title = `Open ${collection.display_name} on Hugging Face`; link.setAttribute("aria-label", link.title); link.dataset.collectionId = collection.id; link.append(makeIcon()); for (const eventName of ["pointerdown", "mousedown", "click"]) { link.addEventListener(eventName, (event) => { event.stopPropagation(); }); } return link; } function createTooltipLink(collection) { if (!collection.url) return null; const link = document.createElement("a"); link.className = TOOLTIP_LINK_CLASS; link.href = collection.url; link.target = "_blank"; link.rel = "noopener noreferrer"; link.title = `Open ${collection.display_name} on Hugging Face`; link.setAttribute("aria-label", link.title); link.dataset.collectionId = collection.id; link.append(makeIcon()); for (const eventName of ["pointerdown", "mousedown", "click"]) { link.addEventListener(eventName, (event) => { event.stopPropagation(); }); } return link; } function createTooltipItem(collection) { const item = document.createElement("span"); item.className = TOOLTIP_ITEM_CLASS; item.dataset.collectionId = collection.id; const label = document.createElement("span"); label.className = TOOLTIP_ITEM_LABEL_CLASS; label.textContent = "Collection:"; const name = document.createElement("span"); name.textContent = collection.display_name; item.append(label, name); const link = createTooltipLink(collection); if (link) item.append(link); return item; } function attachLink(labelElement, collection) { const row = findRowForLabel(labelElement, collection.display_name); if (!row || row.querySelector(`.${LINK_CLASS}[data-collection-id="${collection.id}"]`)) { return; } const link = createLink(collection); if (!link) return; row.classList.add(ROW_CLASS); row.append(link); } function enhancePanel(panel) { const walker = document.createTreeWalker(panel, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (node.parentElement?.closest(`.${LINK_CLASS}`)) return NodeFilter.FILTER_REJECT; return collectionForText(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, }); const matches = []; while (walker.nextNode()) matches.push(walker.currentNode); for (const node of matches) { const collection = collectionForText(node.nodeValue); if (collection && node.parentElement) { attachLink(node.parentElement, collection); } } for (const element of panel.querySelectorAll("[title]")) { const collection = collectionForText(element.getAttribute("title")); if (collection) attachLink(element, collection); } } function collectionMatchInText(text, startIndex) { let match = null; for (const collection of collectionList) { const index = text.indexOf(collection.display_name, startIndex); if (index < 0) continue; if ( match == null || index < match.index || (index === match.index && collection.display_name.length > match.collection.display_name.length) ) { match = { index, collection }; } } return match; } function textIncludesCollection(text) { return collectionMatchInText(text, 0) != null; } function collectionsInText(text) { const collections = []; const seen = new Set(); let index = 0; while (index < text.length) { const match = collectionMatchInText(text, index); if (match == null) break; if (!seen.has(match.collection.id)) { collections.push(match.collection); seen.add(match.collection.id); } index = match.index + match.collection.display_name.length; } return collections; } function sameCollectionOrder(valueElement, collections) { const list = valueElement.querySelector( `.${TOOLTIP_LIST_CLASS}[data-collection-tooltip-format="split"]`, ); if (!list) return false; const existingIds = [...list.querySelectorAll(`.${TOOLTIP_ITEM_CLASS}`)].map( (item) => item.dataset.collectionId, ); return ( existingIds.length === collections.length && existingIds.every((id, index) => id === collections[index].id) ); } function enhanceTooltipCollectionFields(container) { const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode(node) { return normalizeText(node.nodeValue) === PANEL_TITLE ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, }); const labels = []; while (walker.nextNode()) labels.push(walker.currentNode.parentElement); for (const labelElement of labels) { const valueElement = labelElement?.nextElementSibling; if (!valueElement) continue; const collections = collectionsInText(valueElement.textContent ?? ""); if (collections.length === 0 || sameCollectionOrder(valueElement, collections)) { continue; } const list = document.createElement("div"); list.className = TOOLTIP_LIST_CLASS; list.dataset.collectionTooltipFormat = "split"; for (const collection of collections) list.append(createTooltipItem(collection)); valueElement.replaceChildren(list); } } function enhanceTooltipTextNode(node) { if ( node.parentElement?.closest( `.${LINK_CLASS}, .${TOOLTIP_LINK_CLASS}, .${TOOLTIP_ITEM_CLASS}`, ) ) { return; } const text = node.nodeValue ?? ""; let index = 0; const fragment = document.createDocumentFragment(); while (index < text.length) { const match = collectionMatchInText(text, index); if (match == null) { fragment.append(document.createTextNode(text.slice(index))); break; } if (match.index > index) { fragment.append(document.createTextNode(text.slice(index, match.index))); } fragment.append(createTooltipItem(match.collection)); index = match.index + match.collection.display_name.length; } node.replaceWith(fragment); } function isTooltipContainer(element) { return ( element.classList.contains("backdrop-blur-sm") && element.classList.contains("overflow-y-scroll") ); } function tooltipContainers() { return [...document.querySelectorAll(".embedding-atlas-root div")].filter(isTooltipContainer); } function isInsideTooltip(element) { return Boolean(element?.closest(".backdrop-blur-sm.overflow-y-scroll")); } function enhanceTooltip(container) { enhanceTooltipCollectionFields(container); const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode(node) { if ( node.parentElement?.closest( `.${LINK_CLASS}, .${TOOLTIP_LINK_CLASS}, .${TOOLTIP_ITEM_CLASS}`, ) ) { return NodeFilter.FILTER_REJECT; } return textIncludesCollection(node.nodeValue ?? "") ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, }); const matches = []; while (walker.nextNode()) matches.push(walker.currentNode); for (const node of matches) enhanceTooltipTextNode(node); } function scan() { scanQueued = false; if (collectionList.length === 0) return; const panels = new Set( titleTextNodes() .map(findPanelFromTitle) .filter(Boolean), ); for (const panel of panels) enhancePanel(panel); for (const tooltip of tooltipContainers()) enhanceTooltip(tooltip); } function queueScan() { if (scanQueued) return; scanQueued = true; window.requestAnimationFrame(scan); } async function init() { try { const response = await fetch(COLLECTIONS_URL, { cache: "no-store" }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); collectionList = Array.isArray(data.collections) ? data.collections : []; collectionsByName = new Map( collectionList.map((collection) => [normalizeText(collection.display_name), collection]), ); } catch (error) { console.warn("Collection links unavailable:", error); return; } injectStyles(); queueScan(); observer = new MutationObserver(queueScan); observer.observe(document.body, { childList: true, subtree: true, characterData: true, }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init, { once: true }); } else { init(); }