document.addEventListener('DOMContentLoaded', () => { /* ===================== Helpers ===================== */ function isDocUrl(href) { if (!href) return false; return String(href).toLowerCase().includes('/reference/'); } const mount = document.querySelector('#ivypf'); if (!mount) return; const QUERY_KEY = 'post'; const MAX_CACHE = 12; const cache = new Map(); const inflight = new Map(); let abortCtl = null; let loaderEl = null; let lastRenderedFetchUrl = null; const splitUrl = (raw) => { const u = new URL(raw, location.href); return { fetchUrl: u.origin + u.pathname + u.search, fullUrl: u.href }; }; const normalizePostUrl = (raw) => { const u = new URL(raw, location.href); let path = u.pathname; if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1); return u.origin + path + u.search; }; const slugFromFullUrl = (raw) => { try { const u = new URL(raw, location.href); const segs = u.pathname.replace(/\/+$/, '').split('/').filter(Boolean); return segs.length ? segs[segs.length - 1] : ''; } catch { return ''; } }; const fullyDecodeParam = (s) => { let cur = s; for (let i = 0; i < 4; i++) { try { const next = decodeURIComponent(cur); if (next === cur) break; cur = next; } catch { break; } } return cur; }; const isSafeSameOriginUrl = (raw) => { try { const u = new URL(raw, window.location.href); return u.origin === window.location.origin; } catch { return false; } }; /* ===================== Prune (overlay) ===================== */ const MIN_PRUNE_OVERLAY_MS = 500; const pruneRoot = document.querySelector('#comp_mnznj46qn7ipy') || document.querySelector('#iva5') || document.querySelector('.archived-posts'); let pruneOverlayEl = null; let pruneHostMinHeightBackup = null; let pruneHostElForMinHeight = null; const getPruneOverlayHost = () => document.querySelector('#iva5') || pruneRoot; const showPruneLoading = () => { const overlayHost = getPruneOverlayHost(); if (!overlayHost) return; overlayHost.setAttribute('data-sgen-pruning', '1'); if (getComputedStyle(overlayHost).position === 'static') { overlayHost.style.position = 'relative'; } const rectH = overlayHost.getBoundingClientRect().height; if (rectH < 80) { pruneHostElForMinHeight = overlayHost; pruneHostMinHeightBackup = overlayHost.style.minHeight || ''; overlayHost.style.minHeight = '160px'; } if (!document.getElementById('sgen-prune-loading-style')) { const s = document.createElement('style'); s.id = 'sgen-prune-loading-style'; s.textContent = ` .sgen-prune-loading{ position:absolute; inset:0; z-index:2147483646; display:flex; align-items:center; justify-content:center; gap:10px; background: rgba(255,255,255,0.92); background: color-mix(in srgb, Canvas 88%, transparent); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); font:600 13px/1.2 system-ui,sans-serif; } .sgen-prune-loading__spin{ width:18px; height:18px; border-radius:999px; border:2px solid rgba(0,0,0,.15); border-top-color: rgba(0,0,0,.55); animation: sgen-prune-spin .6s linear infinite; } @keyframes sgen-prune-spin{ to{ transform:rotate(360deg); } } `; document.head.appendChild(s); } pruneOverlayEl?.remove(); pruneOverlayEl = document.createElement('div'); pruneOverlayEl.className = 'sgen-prune-loading'; pruneOverlayEl.innerHTML = '<span class="sgen-prune-loading__spin" aria-hidden="true"></span>' + '<span></span>'; overlayHost.appendChild(pruneOverlayEl); }; const hidePruneLoading = () => { pruneOverlayEl?.remove(); pruneOverlayEl = null; document.querySelectorAll('[data-sgen-pruning="1"]').forEach((el) => { el.removeAttribute('data-sgen-pruning'); }); if (pruneHostElForMinHeight) { pruneHostElForMinHeight.style.minHeight = pruneHostMinHeightBackup; pruneHostElForMinHeight = null; pruneHostMinHeightBackup = null; } document.documentElement.classList.add('sgen-doc-archive-pruned'); }; const runPrune = () => { const t0 = performance.now(); const pruneScope = document.querySelector('#iva5') || pruneRoot || document; try { pruneScope.querySelectorAll('.article-item').forEach((item) => { const links = item.querySelectorAll('a[href]'); let anyDoc = false; for (let i = 0; i < links.length; i++) { if (isDocUrl(links[i].getAttribute('href'))) { anyDoc = true; break; } } if (!anyDoc) item.remove(); }); } finally { const elapsed = performance.now() - t0; const remaining = Math.max(0, MIN_PRUNE_OVERLAY_MS - elapsed); window.setTimeout(() => hidePruneLoading(), remaining); } }; showPruneLoading(); requestAnimationFrame(() => { requestAnimationFrame(() => runPrune()); }); /* ===================== Shell + TOC + Accordion ===================== */ const extractBlogShell = (doc) => { const article = doc.querySelector('article'); return article?.closest('.container') ?? null; }; const remember = (url, template) => { cache.delete(url); cache.set(url, template); while (cache.size > MAX_CACHE) cache.delete(cache.keys().next().value); }; const getLiveH3List = () => { const article = mount.querySelector('article'); if (!article) return []; return [...article.querySelectorAll('h3')].filter((h) => mount.contains(h)); }; const replaceMorePostsSidebarWithToc = () => { const row = mount.querySelector('.row'); if (!row) return; let sidebarCol = [...mount.querySelectorAll('.col-md-3')].find((col) => col.querySelector('.sidebar-posts, .archived-posts.sidebar-posts') ); if (!sidebarCol) { const mainCol = row.querySelector('.col-md-9'); const next = mainCol?.nextElementSibling; if (next?.classList?.contains('col-md-3')) sidebarCol = next; } if (!sidebarCol) { sidebarCol = [...mount.querySelectorAll('.col-md-3')].find((col) => [...col.querySelectorAll('h6')].some((h) => /more posts/i.test((h.textContent || '').trim())) ); } if (!sidebarCol) return; const h3s = getLiveH3List(); sidebarCol.replaceChildren(); const h6 = document.createElement('h6'); h6.textContent = 'On this page'; const hr = document.createElement('hr'); const nav = document.createElement('nav'); nav.setAttribute('aria-label', 'On this page'); if (!h3s.length) { const p = document.createElement('p'); p.textContent = 'No sections.'; nav.appendChild(p); } else { h3s.forEach((h3, idx) => { const btn = document.createElement('button'); btn.type = 'button'; btn.style.cssText = 'display:block;width:100%;text-align:left;border:0;background:transparent;cursor:pointer;padding:6px 0;font-weight:400;text-transform:capitalize;font-family: var(--headings_2-font-family), sans-serif;'; btn.innerHTML = h3.innerHTML; btn.addEventListener('click', (e) => { e.preventDefault(); requestAnimationFrame(() => { const live = getLiveH3List(); live[idx]?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }); nav.appendChild(btn); }); } sidebarCol.append(h6, hr, nav); }; const stripArchiveAccordions = () => { document.querySelectorAll('.archived-posts .article-item .sgen-archive-jump-nav').forEach((nav) => { const host = nav.parentElement; const titleBlock = nav.querySelector(':scope > summary > .article-title'); if (host && titleBlock) host.insertBefore(titleBlock, nav); nav.remove(); }); }; const syncArchiveAccordion = (fetchUrl) => { stripArchiveAccordions(); if (!fetchUrl) return; const h3s = getLiveH3List(); if (!h3s.length) return; const want = normalizePostUrl(fetchUrl); const item = [...document.querySelectorAll('.archived-posts .article-item')].find((it) => { const l = it.querySelector('a.title-link[href]') || it.querySelector('a.p[href]'); return l && normalizePostUrl(l.getAttribute('href')) === want; }); const detailsHost = item?.querySelector('.details'); const titleBlock = detailsHost?.querySelector(':scope > .article-title'); if (!detailsHost || !titleBlock) return; const nav = document.createElement('details'); nav.className = 'sgen-archive-jump-nav'; nav.open = true; const sum = document.createElement('summary'); sum.className = 'sgen-archive-jump-nav__summary'; detailsHost.insertBefore(nav, titleBlock); sum.appendChild(titleBlock); nav.appendChild(sum); const list = document.createElement('div'); list.className = 'sgen-archive-jump-nav__list'; h3s.forEach((h3, idx) => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'sgen-archive-jump-nav__btn'; btn.innerHTML = h3.innerHTML; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); nav.open = true; requestAnimationFrame(() => { const live = getLiveH3List(); live[idx]?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }); list.appendChild(btn); }); nav.appendChild(list); }; const renderTemplate = (template, fetchUrl) => { mount.replaceChildren(template.cloneNode(true)); replaceMorePostsSidebarWithToc(); lastRenderedFetchUrl = fetchUrl; syncArchiveAccordion(fetchUrl); }; const ensureLoader = () => { if (loaderEl) return loaderEl; loaderEl = document.createElement('div'); loaderEl.className = 'sgen-doc-shell-loader'; loaderEl.setAttribute('role', 'status'); loaderEl.setAttribute('aria-live', 'polite'); loaderEl.setAttribute('aria-busy', 'true'); loaderEl.innerHTML = '<div class="sgen-doc-shell-loader__spinner" aria-hidden="true"></div>' + '<span class="sgen-doc-shell-loader__text"></span>'; const s = document.createElement('style'); s.textContent = ` #ivypf[data-sgen-loading]{ min-height: 12rem; } .sgen-doc-shell-loader{ position:absolute; inset:0; z-index:20; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:0.75rem; background: color-mix(in srgb, var(--sgen-loader-bg, Canvas) 82%, transparent); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); } .sgen-doc-shell-loader__text{ font: 500 0.875rem/1.2 system-ui, sans-serif; color: var(--sgen-loader-fg, CanvasText); opacity: 0.85; } .sgen-doc-shell-loader__spinner{ width: 2.25rem; height: 2.25rem; border-radius: 999px; border: 3px solid color-mix(in srgb, var(--sgen-loader-fg, CanvasText) 22%, transparent); border-top-color: var(--sgen-loader-accent, CanvasText); animation: sgen-doc-spin 0.65s linear infinite; } @keyframes sgen-doc-spin{ to { transform: rotate(360deg); } } #comp_mnznj46qn7ipy .sgen-archive-jump-nav > summary{ display:block; } #comp_mnznj46qn7ipy .sgen-archive-jump-nav > summary .article-title{ margin:0; } #comp_mnznj46qn7ipy .sgen-archive-jump-nav > summary .title-link{ display:flex; align-items:center; justify-content:space-between; gap:0.5rem; width:100%; text-decoration:none; } #comp_mnznj46qn7ipy .archived-posts .sgen-archive-jump-nav[open] .title-link::after{ transform: translateY(-1px) rotate(90deg); transition: transform 0.15s ease; } #comp_mnznj46qn7ipy .archived-posts .sgen-archive-jump-nav:not([open]) .title-link::after{ transition: transform 0.15s ease; } .sgen-archive-jump-nav{ margin:0 0 0.35rem; overflow:hidden; } .sgen-archive-jump-nav > summary{ list-style:none; padding:0.35rem 0.45rem; cursor:pointer; user-select:none; } .sgen-archive-jump-nav > summary::-webkit-details-marker{ display:none; } .sgen-archive-jump-nav__list{ margin:0; padding:0.15rem 0.45rem 0.45rem 0.65rem; display:flex; flex-direction:column; gap:0.1rem; padding-top:15px !important; } .sgen-archive-jump-nav__btn{ text-align:left; width:100%; border:0; background:transparent; font-size:13px; font-weight:semi-bold; text-transform:capitalize; margin-bottom:10px; cursor:pointer;font-family: var(--headings_2-font-family), sans-serif; } .sgen-archive-jump-nav__btn:hover{ background:color-mix(in srgb, CanvasText 6%, transparent); } `; if (!document.getElementById('sgen-doc-shell-loader-styles')) { s.id = 'sgen-doc-shell-loader-styles'; document.head.appendChild(s); } return loaderEl; }; const setBusy = (busy) => { mount.toggleAttribute('data-sgen-loading', busy); if (busy) { if (getComputedStyle(mount).position === 'static') mount.style.position = 'relative'; mount.appendChild(ensureLoader()); } else { loaderEl?.remove(); loaderEl = null; } }; /* ===================== Query ===================== */ const readPostFromQuery = () => { const raw = new URLSearchParams(window.location.search).get(QUERY_KEY); if (!raw) return null; const decoded = fullyDecodeParam(raw.trim()); if (!decoded) return null; if (decoded.includes('://') || /^https?:/i.test(decoded)) { if (!isSafeSameOriginUrl(decoded)) return null; if (!decoded.toLowerCase().includes('/reference/')) return null; return decoded; } const slug = decoded.replace(/^\/+|\/+$/g, ''); if (!slug) return null; const anchors = document.querySelectorAll( '.archived-posts a.title-link[href], .archived-posts a.p[href], .archived-posts a.cta[href]' ); for (const a of anchors) { const href = a.getAttribute('href'); if (!href) continue; if (!isSafeSameOriginUrl(href)) continue; if (!href.toLowerCase().includes('/reference/')) continue; if (slugFromFullUrl(href) === slug) return href; } return null; }; const getFirstRefArchiveHref = () => { for (const item of document.querySelectorAll('.archived-posts .article-item')) { const a = item.querySelector('a.title-link[href]') || item.querySelector('a.p[href]') || item.querySelector('a.cta[href]'); const href = a?.getAttribute('href'); if (!href || !isSafeSameOriginUrl(href)) continue; if (!href.toLowerCase().includes('/reference/')) continue; return href; } return null; }; const writePostToUrl = (fullUrl, mode) => { const slug = slugFromFullUrl(fullUrl); if (!slug) return; const u = new URL(window.location.href); u.searchParams.set(QUERY_KEY, slug); const method = mode === 'replace' ? 'replaceState' : 'pushState'; history[method]({ [QUERY_KEY]: slug }, '', u); }; /* ===================== Fetch + show ===================== */ const loadShellTemplate = async (fetchUrl, signal) => { if (cache.has(fetchUrl)) return cache.get(fetchUrl); if (inflight.has(fetchUrl)) return inflight.get(fetchUrl); const p = (async () => { const res = await fetch(fetchUrl, { signal, credentials: 'same-origin', cache: 'force-cache', headers: { Accept: 'text/html' }, }); if (!res.ok) throw new Error(String(res.status)); const html = await res.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); const shell = extractBlogShell(doc); if (!shell) throw new Error('no shell'); const template = shell.cloneNode(true); remember(fetchUrl, template); return template; })().finally(() => { inflight.delete(fetchUrl); }); inflight.set(fetchUrl, p); return p; }; const showFromUrl = async (url, { syncHistory } = {}) => { if (!url || !isSafeSameOriginUrl(url)) return; if (!String(url).toLowerCase().includes('/reference/')) return; const { fetchUrl, fullUrl } = splitUrl(url); abortCtl?.abort(); abortCtl = new AbortController(); const { signal } = abortCtl; const cached = cache.get(fetchUrl); if (cached) { setBusy(false); renderTemplate(cached, fetchUrl); if (syncHistory === 'push') writePostToUrl(fullUrl, 'push'); if (syncHistory === 'replace') writePostToUrl(fullUrl, 'replace'); return; } setBusy(true); try { const template = await loadShellTemplate(fetchUrl, signal); if (signal.aborted) return; requestAnimationFrame(() => { if (signal.aborted) return; renderTemplate(template, fetchUrl); setBusy(false); if (syncHistory === 'push') writePostToUrl(fullUrl, 'push'); if (syncHistory === 'replace') writePostToUrl(fullUrl, 'replace'); }); } catch { if (signal.aborted) return; setBusy(false); } }; const isReadMoreLike = (a) => { const t = (a.textContent || '').replace(/\s+/g, ' ').trim().toLowerCase(); return a.classList.contains('cta') || /^read more\b/.test(t) || /^see more\b/.test(t); }; const shouldHandle = (a) => a.classList.contains('title-link') || isReadMoreLike(a); document.addEventListener( 'click', (e) => { const a = e.target.closest?.('.archived-posts a[href]'); if (!a || !shouldHandle(a)) return; const url = a.getAttribute('href'); if (!url || url.startsWith('#') || url.toLowerCase().startsWith('javascript:')) return; if (!isSafeSameOriginUrl(url)) return; if (!url.toLowerCase().includes('/reference/')) return; e.preventDefault(); void showFromUrl(url, { syncHistory: 'push' }); }, { passive: false } ); window.addEventListener('popstate', () => { const fromQuery = readPostFromQuery(); if (fromQuery) { void showFromUrl(fromQuery, { syncHistory: null }); return; } const first = getFirstRefArchiveHref(); if (first) { void showFromUrl(first, { syncHistory: 'replace' }); return; } setBusy(false); mount.replaceChildren(); lastRenderedFetchUrl = null; stripArchiveAccordions(); }); /* ===================== Mobile drawer (#iva5) ===================== */ const mqIva5 = window.matchMedia('(max-width: 768px)'); let iva5HomeParent = null; let iva5HomeNext = null; let iva5OpenTrigger = null; let drawer = null; let backdrop = null; let onKeyDownIva5 = null; const getIva5ShellSection = () => document.querySelector('#comp_mnzmh2vkcytdh') || document.querySelector('section[data-component-id="mnzmh2vjds9b9"]') || document.querySelector('section[data-component-id="mo0ntud0yiwwc"]') || mount.closest('section.sgb-component-section'); const setOpenTriggerVisible = (visible) => { if (!iva5OpenTrigger) return; iva5OpenTrigger.toggleAttribute('hidden', !visible); iva5OpenTrigger.setAttribute('aria-hidden', visible ? 'false' : 'true'); iva5OpenTrigger.style.display = visible ? 'inline-flex' : 'none'; }; const removeIva5OpenTrigger = () => { iva5OpenTrigger?.remove(); iva5OpenTrigger = null; }; const ensureIva5MobileStyles = () => { if (document.getElementById('sgen-iva5-mobile-styles')) return; const s = document.createElement('style'); s.id = 'sgen-iva5-mobile-styles'; s.textContent = ` button.sgen-iva5-open-sidebar{ display:inline-flex; align-items:center; justify-content:center; width:44px; height:44px; margin:0 0 10px 20px; padding:0; border-radius:10px; border:1px solid rgba(0,0,0,.12); background:#fff; color:#111; box-shadow:0 4px 14px rgba(0,0,0,.08); touch-action:manipulation; cursor:pointer; } button.sgen-iva5-open-sidebar svg{ display:block; } .sgen-iva5-backdrop{ position:fixed; inset:0; z-index:2147482000; background:rgba(0,0,0,.45); } .sgen-iva5-drawer{ position:fixed; top:0; left:0; bottom:0; z-index:2147482500; width:min(86vw, 360px); background:#fff; overflow:auto; -webkit-overflow-scrolling:touch; transform:translateX(-100%); transition:transform .18s ease; box-shadow:12px 0 30px rgba(0,0,0,.18); padding:14px 12px; } .sgen-iva5-drawer[data-open="1"]{ transform:translateX(0); } .sgen-iva5-close{ width:40px; height:40px; border-radius:10px; border:1px solid rgba(0,0,0,.12); background:#fff; color:#111; display:grid; place-items:center; margin-bottom:10px; font-size:22px; touch-action:manipulation; } `; document.head.appendChild(s); }; const closeIva5Drawer = () => { if (!drawer || !drawer.isConnected) return; drawer.dataset.open = '0'; backdrop?.remove(); backdrop = null; document.documentElement.style.overflow = ''; document.body.style.overflow = ''; setOpenTriggerVisible(true); }; const openIva5Drawer = () => { mountIva5DrawerMobile(); if (!drawer || !drawer.isConnected) return; drawer.dataset.open = '1'; setOpenTriggerVisible(false); if (!backdrop) { backdrop = document.createElement('div'); backdrop.className = 'sgen-iva5-backdrop'; backdrop.addEventListener('click', closeIva5Drawer); document.body.appendChild(backdrop); } document.documentElement.style.overflow = 'hidden'; document.body.style.overflow = 'hidden'; }; const OPEN_SIDEBAR_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"></polyline></svg>'; const ensureIva5OpenTrigger = () => { const section = getIva5ShellSection(); if (!section) return; if (!iva5OpenTrigger || !iva5OpenTrigger.isConnected) { const existing = section.querySelector(':scope > button.sgen-iva5-open-sidebar'); if (existing) { iva5OpenTrigger = existing; } else { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'sgen-iva5-open-sidebar'; btn.setAttribute('aria-label', 'Open sidebar menu'); btn.innerHTML = OPEN_SIDEBAR_SVG; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openIva5Drawer(); }); section.insertBefore(btn, section.firstChild); iva5OpenTrigger = btn; } } else if (iva5OpenTrigger.parentNode !== section) { section.insertBefore(iva5OpenTrigger, section.firstChild); } }; const mountIva5DrawerMobile = () => { if (!mqIva5.matches) return; const iva5 = document.querySelector('#iva5'); if (!iva5) return; if (!iva5HomeParent) { iva5HomeParent = iva5.parentNode; iva5HomeNext = iva5.nextSibling; } ensureIva5MobileStyles(); ensureIva5OpenTrigger(); if (!drawer || !drawer.isConnected) { drawer = document.createElement('aside'); drawer.className = 'sgen-iva5-drawer'; drawer.dataset.open = '0'; drawer.setAttribute('role', 'dialog'); drawer.setAttribute('aria-label', 'Menu'); const closeBtn = document.createElement('button'); closeBtn.type = 'button'; closeBtn.className = 'sgen-iva5-close'; closeBtn.setAttribute('aria-label', 'Close menu'); closeBtn.textContent = '×'; closeBtn.addEventListener('click', (e) => { e.preventDefault(); closeIva5Drawer(); }); drawer.appendChild(closeBtn); document.body.appendChild(drawer); onKeyDownIva5 = (e) => { if (e.key === 'Escape') closeIva5Drawer(); }; document.addEventListener('keydown', onKeyDownIva5); } if (iva5.parentNode !== drawer) drawer.appendChild(iva5); if (!iva5.dataset.sgenIva5NavBound) { iva5.dataset.sgenIva5NavBound = '1'; iva5.addEventListener( 'click', (e) => { const a = e.target.closest?.('a[href]'); if (a) closeIva5Drawer(); }, { passive: true } ); } setOpenTriggerVisible(drawer.dataset.open !== '1'); }; const restoreIva5Desktop = () => { const iva5 = document.querySelector('#iva5'); if (iva5 && iva5HomeParent && drawer && drawer.isConnected && iva5.parentNode === drawer) { if (iva5HomeNext && iva5HomeNext.parentNode === iva5HomeParent) { iva5HomeParent.insertBefore(iva5, iva5HomeNext); } else { iva5HomeParent.appendChild(iva5); } } closeIva5Drawer(); removeIva5OpenTrigger(); drawer?.remove(); drawer = null; backdrop?.remove(); backdrop = null; if (onKeyDownIva5) { document.removeEventListener('keydown', onKeyDownIva5); onKeyDownIva5 = null; } document.documentElement.style.overflow = ''; document.body.style.overflow = ''; }; const applyIva5MobileDrawer = () => { if (mqIva5.matches) mountIva5DrawerMobile(); else restoreIva5Desktop(); }; mqIva5.addEventListener?.('change', applyIva5MobileDrawer); /* ===================== First page load ===================== */ applyIva5MobileDrawer(); let tries = 0; const tick = window.setInterval(() => { tries += 1; applyIva5MobileDrawer(); if (document.querySelector('#iva5 .archived-posts')) window.clearInterval(tick); if (tries > 80) window.clearInterval(tick); }, 150); const initial = readPostFromQuery() || getFirstRefArchiveHref(); if (!initial) return; const hadQuery = Boolean(readPostFromQuery()); void showFromUrl(initial, { syncHistory: hadQuery ? null : 'replace' });});
