|
741 | 741 | // GitHub Octicon "upload" — additive and not wired to a control yet. |
742 | 742 | // Mirrors upload.svg / _SVG_UPLOAD in _static/__init__.py. |
743 | 743 | upload: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"/><path d="M11.78 4.72a.749.749 0 1 1-1.06 1.06L8.75 3.811V9.5a.75.75 0 0 1-1.5 0V3.811L5.28 5.78a.749.749 0 1 1-1.06-1.06l3.25-3.25a.749.749 0 0 1 1.06 0l3.25 3.25Z"/></svg>', |
| 744 | + // GitHub Octicon "database" — additive and not wired to a control yet. |
| 745 | + // Mirrors database.svg / _SVG_DATABASE in _static/__init__.py. |
| 746 | + database: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M1 3.5c0-.626.292-1.165.7-1.59.406-.422.956-.767 1.579-1.041C4.525.32 6.195 0 8 0c1.805 0 3.475.32 4.722.869.622.274 1.172.62 1.578 1.04.408.426.7.965.7 1.591v9c0 .626-.292 1.165-.7 1.59-.406.422-.956.767-1.579 1.041C11.476 15.68 9.806 16 8 16c-1.805 0-3.475-.32-4.721-.869-.623-.274-1.173-.62-1.579-1.04-.408-.426-.7-.965-.7-1.591Zm1.5 0c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 5.205 6.353 5.5 8 5.5c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55 0-.133-.058-.318-.282-.551-.227-.237-.591-.483-1.101-.707C11.102 1.795 9.647 1.5 8 1.5c-1.646 0-3.101.295-4.118.742-.508.224-.873.471-1.1.708-.224.232-.282.417-.282.55Zm0 4.5c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 9.705 6.353 10 8 10c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55V5.724c-.241.15-.503.286-.778.407C11.475 6.68 9.805 7 8 7c-1.805 0-3.475-.32-4.721-.869a6.15 6.15 0 0 1-.779-.407Zm0 2.225V12.5c0 .133.058.318.282.55.227.237.592.484 1.1.708 1.016.447 2.471.742 4.118.742 1.647 0 3.102-.295 4.117-.742.51-.224.874-.47 1.101-.707.224-.233.282-.418.282-.551v-2.275c-.241.15-.503.285-.778.406-1.247.549-2.917.869-4.722.869-1.805 0-3.475-.32-4.721-.869a6.327 6.327 0 0 1-.779-.406Z"/></svg>', |
744 | 747 | // Octicon-style printer — used by the inline PDF method switch and |
745 | 748 | // mirrored by _SVG_PRINTER / printer.svg. |
746 | 749 | printer: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M4 2.75C4 1.784 4.784 1 5.75 1h4.5C11.216 1 12 1.784 12 2.75V5h.25A2.75 2.75 0 0 1 15 7.75v3.5A1.75 1.75 0 0 1 13.25 13H12v.25A1.75 1.75 0 0 1 10.25 15h-4.5A1.75 1.75 0 0 1 4 13.25V13H2.75A1.75 1.75 0 0 1 1 11.25v-3.5A2.75 2.75 0 0 1 3.75 5H4V2.75Zm1.5 0V5h5V2.75a.25.25 0 0 0-.25-.25h-4.5a.25.25 0 0 0-.25.25ZM3.75 6.5A1.25 1.25 0 0 0 2.5 7.75v3.5c0 .138.112.25.25.25H4v-.75C4 9.784 4.784 9 5.75 9h4.5c.966 0 1.75.784 1.75 1.75v.75h1.25a.25.25 0 0 0 .25-.25v-3.5a1.25 1.25 0 0 0-1.25-1.25h-8.5Zm1.75 4.25v2.5c0 .138.112.25.25.25h4.5a.25.25 0 0 0 .25-.25v-2.5a.25.25 0 0 0-.25-.25h-4.5a.25.25 0 0 0-.25.25ZM12 8a.75.75 0 1 1 1.5 0A.75.75 0 0 1 12 8Z"/></svg>', |
|
974 | 977 | return document.createTextNode('🕺'); |
975 | 978 | } |
976 | 979 | const img = document.createElement('img'); |
977 | | - // const version = window.location.pathname.split("/")[1]; // "dev" or "stable" |
978 | | - // const root = window.location.pathname.match(/^\/[^/]+/)[0]; |
979 | | - // if the script itself is inside _static, even better because |
980 | | - // DOCUMENTATION_OPTIONS.URL_ROOT points back to the documentation root.: |
981 | | - // img.src = new URL("animated_gif/dancer_anim.gif", import.meta.url).href; |
982 | | - // img.src = new URL("_static/animated_gif/dancer_anim.gif", document.baseURI).href; |
983 | | - const urlRoot = window.DOCUMENTATION_OPTIONS?.URL_ROOT ?? "/dev/"; |
984 | | - img.src = `${urlRoot}_static/animated_gif/dancer_anim.gif`; |
| 980 | + // getStaticAssetUrl() derives _static/ from the loaded <script>/<link> |
| 981 | + // tag itself (see "Static path detection" below), so this resolves |
| 982 | + // correctly under /dev/, /stable/, a version tag, or a ReadTheDocs |
| 983 | + // /en/latest/-style prefix — unlike a URL_ROOT fallback hardcoded to |
| 984 | + // one deployment. |
| 985 | + img.src = getStaticAssetUrl('animated_gif/dancer_anim.gif'); |
985 | 986 | img.className = 'ai-assistant-panel-dancer-gif'; |
986 | 987 | img.height = 11; |
987 | 988 | img.alt = ''; |
|
1904 | 1905 | mainBtn.title = 'Copy this page as Markdown for AI / LLMs'; |
1905 | 1906 |
|
1906 | 1907 | var mainIcon = document.createElement('img'); |
1907 | | - mainIcon.src = staticPath + '/copy-to-clipboard.svg'; |
| 1908 | + mainIcon.src = getStaticAssetUrl('copy-to-clipboard.svg', staticPath); |
1908 | 1909 | mainIcon.className = 'ai-assistant-icon'; |
1909 | 1910 | mainIcon.setAttribute('aria-hidden', 'true'); |
1910 | 1911 | mainIcon.alt = ''; |
|
1928 | 1929 | dropBtn.setAttribute('aria-haspopup', 'true'); |
1929 | 1930 |
|
1930 | 1931 | var dropIcon = document.createElement('img'); |
1931 | | - dropIcon.src = staticPath + '/arrow-down.svg'; |
| 1932 | + dropIcon.src = getStaticAssetUrl('arrow-down.svg', staticPath); |
1932 | 1933 | dropIcon.className = 'ai-assistant-dropdown-icon'; |
1933 | 1934 | dropIcon.setAttribute('aria-hidden', 'true'); |
1934 | 1935 | dropIcon.alt = ''; |
|
1956 | 1957 |
|
1957 | 1958 | // 1. Markdown export |
1958 | 1959 | if (features.markdown_export) { |
1959 | | - dropdown.appendChild(createMenuItem('copy-markdown', 'Copy page', 'Copy this page as Markdown for LLMs.', staticPath + '/copy-to-clipboard.svg')); |
| 1960 | + dropdown.appendChild(createMenuItem('copy-markdown', 'Copy page', 'Copy this page as Markdown for LLMs.', getStaticAssetUrl('copy-to-clipboard.svg', staticPath))); |
1960 | 1961 | hasItems = true; |
1961 | 1962 | } |
1962 | 1963 |
|
1963 | 1964 | if (features.view_markdown) { |
1964 | | - dropdown.appendChild(createMenuItem('view-markdown', 'View as Markdown', 'View this page as Markdown.', staticPath + '/markdown.svg')); |
| 1965 | + dropdown.appendChild(createMenuItem('view-markdown', 'View as Markdown', 'View this page as Markdown.', getStaticAssetUrl('markdown.svg', staticPath))); |
1965 | 1966 | hasItems = true; |
1966 | 1967 | } |
1967 | 1968 |
|
|
1974 | 1975 | enabledProviders.forEach(function (kv) { |
1975 | 1976 | var key = kv[0], provider = kv[1]; |
1976 | 1977 | var icon = provider.icon || 'comment-discussion.svg'; |
1977 | | - var iconPath = _ABSOLUTE_ICON_RE.test(icon) ? icon : (staticPath + '/' + icon); |
| 1978 | + var iconPath = getStaticAssetUrl(icon, staticPath); |
1978 | 1979 | var item = createMenuItem('ai-chat-' + key, provider.label, provider.description || 'Open AI chat with this page context.', iconPath); |
1979 | 1980 | item.dataset.provider = key; |
1980 | 1981 | dropdown.appendChild(item); |
|
1992 | 1993 | enabledTools.forEach(function (kv) { |
1993 | 1994 | var key = kv[0], tool = kv[1]; |
1994 | 1995 | var icon = tool.icon || 'ai-tools.svg'; |
1995 | | - var iconPath = _ABSOLUTE_ICON_RE.test(icon) ? icon : (staticPath + '/' + icon); |
| 1996 | + var iconPath = getStaticAssetUrl(icon, staticPath); |
1996 | 1997 | var item = createMenuItem('mcp-' + key, tool.label, tool.description || 'Install MCP server.', iconPath); |
1997 | 1998 | item.dataset.mcpTool = key; |
1998 | 1999 | dropdown.appendChild(item); |
|
2012 | 2013 | if (features.ai_panel) { |
2013 | 2014 | var panelTitle = cfg.panelTitle || 'AI Assistant'; |
2014 | 2015 | if (hasItems) dropdown.appendChild(createSeparator()); |
2015 | | - var panelItem = createMenuItem('ai-panel-open', panelTitle, 'Ask ' + panelTitle + ' about this page', staticPath + '/ai-panel.svg'); |
| 2016 | + var panelItem = createMenuItem('ai-panel-open', panelTitle, 'Ask ' + panelTitle + ' about this page', getStaticAssetUrl('ai-panel.svg', staticPath)); |
2016 | 2017 | dropdown.appendChild(panelItem); |
2017 | 2018 | } |
2018 | 2019 |
|
|
2312 | 2313 |
|
2313 | 2314 | function _pdfIconSource(staticPath, mode) { |
2314 | 2315 | var def = _PDF_MODE_DEFS[mode] || _PDF_MODE_DEFS.print; |
2315 | | - return staticPath.replace(/\/$/, '') + '/' + def.iconFile; |
| 2316 | + return getStaticAssetUrl(def.iconFile, staticPath); |
2316 | 2317 | } |
2317 | 2318 |
|
2318 | 2319 | function _pdfSwitchAccessibleLabel(mode, target) { |
|
2623 | 2624 | return '_static'; |
2624 | 2625 | } |
2625 | 2626 |
|
| 2627 | + /** |
| 2628 | + * Resolve `asset` to a URL safe to assign to `img.src` regardless of page |
| 2629 | + * depth or deployment prefix (`/dev/`, `/stable/`, a version tag, |
| 2630 | + * ReadTheDocs `/en/latest/`, a custom `html_baseurl`, ...). |
| 2631 | + * |
| 2632 | + * Values already matched by `_ABSOLUTE_ICON_RE` (an `https:`/`data:`/ |
| 2633 | + * `blob:` URL, or a root-relative `/path`) are returned unchanged — this |
| 2634 | + * is what lets a Python-supplied `data:image/svg+xml;base64,...` icon |
| 2635 | + * (see `_static/__init__.py`, `_ICON_META` / `_PROVIDER_META`) skip |
| 2636 | + * `_static/` and avoid a network request entirely. |
| 2637 | + * |
| 2638 | + * Otherwise `asset` is treated as a bare filename under the resolved |
| 2639 | + * static directory and joined with `new URL()`, the same normalization |
| 2640 | + * `_getSphinxDocsRootUrl()` already uses — one canonical way to build a |
| 2641 | + * `_static/`-relative URL instead of each call site re-deriving it. |
| 2642 | + * |
| 2643 | + * @param {string} asset - Bare filename ('checked.svg') or an |
| 2644 | + * already-absolute src. |
| 2645 | + * @param {string} [base] - Static-dir base to resolve against. Defaults |
| 2646 | + * to `getStaticPath()`; pass an already-computed local `staticPath` |
| 2647 | + * to skip recomputing it (cheap either way — `getStaticPath()` is |
| 2648 | + * memoized). |
| 2649 | + * @returns {string} An absolute (or already-absolute) URL, or `''` if |
| 2650 | + * `asset` is empty. |
| 2651 | + */ |
| 2652 | + function getStaticAssetUrl(asset, base) { |
| 2653 | + asset = String(asset || ''); |
| 2654 | + if (!asset) { return ''; } |
| 2655 | + if (_ABSOLUTE_ICON_RE.test(asset)) { return asset; } |
| 2656 | + |
| 2657 | + var staticBase = String(base || getStaticPath() || '_static').replace(/\/?$/, '/'); |
| 2658 | + try { |
| 2659 | + return new URL(asset.replace(/^\/+/, ''), new URL(staticBase, document.baseURI)).href; |
| 2660 | + } catch (_e) { |
| 2661 | + return staticBase + asset.replace(/^\/+/, ''); |
| 2662 | + } |
| 2663 | + } |
| 2664 | + |
2626 | 2665 | // ── Widget placement ────────────────────────────────────────────────────── |
2627 | 2666 |
|
2628 | 2667 | function insertContainer(container, position) { |
|
3032 | 3071 | if (!iconEl || !textSpan) return; |
3033 | 3072 | var origSrc = iconEl.src; |
3034 | 3073 | var origText = textSpan.textContent; |
3035 | | - iconEl.src = getStaticPath() + '/checked.svg'; |
| 3074 | + iconEl.src = getStaticAssetUrl('checked.svg'); |
3036 | 3075 | textSpan.textContent = 'Copied'; |
3037 | 3076 | mainButton.classList.add('ai-assistant-button-success'); |
3038 | 3077 | setTimeout(function () { |
@@ -22578,12 +22617,13 @@ opts.jsonPayload + '\n' + |
22578 | 22617 | var ns = window.AI_ASSISTANT = window.AI_ASSISTANT || {}; |
22579 | 22618 | if (ns._wired) { return; } // idempotent across re-injection |
22580 | 22619 | ns._wired = true; |
22581 | | - ns.config = function () { return _cfg(); }; |
22582 | | - ns.getStaticPath = getStaticPath; // asset base URL resolver |
22583 | | - ns.icons = ICONS; // inline SVG map |
22584 | | - ns.fetch = _fetch; // AI_COMPAT-aware fetch |
22585 | | - ns.hapticFeedback = _hapticFeedback; // no-op where unsupported |
22586 | | - ns.attachLongPress = _attachLongPress; // pointer long-press helper |
| 22620 | + ns.config = function () { return _cfg(); }; |
| 22621 | + ns.getStaticPath = getStaticPath; // _static/ base path (no trailing slash) |
| 22622 | + ns.getStaticAssetUrl = getStaticAssetUrl; // filename or absolute src -> resolved URL |
| 22623 | + ns.icons = ICONS; // inline SVG map |
| 22624 | + ns.fetch = _fetch; // AI_COMPAT-aware fetch |
| 22625 | + ns.hapticFeedback = _hapticFeedback; // no-op where unsupported |
| 22626 | + ns.attachLongPress = _attachLongPress; // pointer long-press helper |
22587 | 22627 | }()); |
22588 | 22628 |
|
22589 | 22629 | // ── Bootstrap ───────────────────────────────────────────────────────────── |
|
0 commit comments