From 92b468a39e119f5d37718162fc5d5e246a6073b8 Mon Sep 17 00:00:00 2001 From: Johngenri Date: Sun, 8 Mar 2026 15:53:11 +0100 Subject: [PATCH] fix: broaden icon basename detection --- src/App.tsx | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 93d7cfd5..0371cd70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,9 @@ import { PluginsProvider } from './contexts/PluginsContext'; import AppContent from './components/app/AppContent'; import i18n from './i18n/config.js'; +/** + * Detect the router basename from explicit runtime config or deployment hints. + */ function detectRouterBasename() { const explicitBasename = typeof window !== 'undefined' ? window.__ROUTER_BASENAME__ || '' : ''; if (explicitBasename) { @@ -20,22 +23,46 @@ function detectRouterBasename() { } const candidatePaths = [ - document.querySelector('link[rel="manifest"]')?.getAttribute('href'), - document.querySelector('script[type="module"][src]')?.getAttribute('src'), - document.querySelector('link[rel="icon"][href]')?.getAttribute('href'), - ].filter((value): value is string => Boolean(value)); + { kind: 'manifest' as const, value: document.querySelector('link[rel="manifest"]')?.getAttribute('href') }, + { kind: 'script' as const, value: document.querySelector('script[type="module"][src]')?.getAttribute('src') }, + ...Array.from( + document.querySelectorAll( + 'link[rel~="icon"][href], link[rel="apple-touch-icon"][href], link[rel="apple-touch-icon-precomposed"][href], link[rel="mask-icon"][href]' + ) + ).map((node) => ({ + kind: 'icon' as const, + value: node.getAttribute('href'), + })), + ].filter((candidate): candidate is { kind: 'manifest' | 'script' | 'icon'; value: string } => Boolean(candidate.value)); let detectedBasename = ''; for (const candidate of candidatePaths) { try { - const pathname = new URL(candidate, document.baseURI || window.location.href).pathname; - const match = pathname.match(/^(.*)\/(?:assets\/|manifest\.json$|favicon\.(?:svg|png)$)/); - if (match) { - const normalized = match[1] ? match[1].replace(/\/+$/, '') : ''; - if (normalized.length > detectedBasename.length) { - detectedBasename = normalized; + const pathname = new URL(candidate.value, document.baseURI || window.location.href).pathname; + const normalizedPathname = pathname.replace(/\/+$/, ''); + + let normalized = ''; + if (candidate.kind === 'script') { + const match = normalizedPathname.match(/^(.*)\/assets\//); + normalized = match?.[1] ? match[1].replace(/\/+$/, '') : ''; + } else { + const manifestMatch = normalizedPathname.match(/^(.*)\/(?:manifest\.json|site\.webmanifest)$/); + const iconMatch = normalizedPathname.match( + /^(.*)\/(?:favicon(?:\.[^/]+)?|apple-touch-icon(?:-[^/]+)?(?:\.[^/]+)?|mask-icon(?:\.[^/]+)?|[^/]*icon[^/]*)$/ + ); + const match = candidate.kind === 'manifest' ? manifestMatch : iconMatch; + if (match?.[1]) { + const segments = match[1].split('/').filter(Boolean); + while (segments.length > 0 && ['assets', 'static', 'icons', 'images'].includes(segments[segments.length - 1])) { + segments.pop(); + } + normalized = segments.length > 0 ? `/${segments.join('/')}` : ''; } } + + if (normalized.length > detectedBasename.length) { + detectedBasename = normalized; + } } catch { // Ignore invalid candidate URLs and continue checking other hints. }