fix(banners): save relative storage paths so admin preview survives refresh

Uploads were stored as host-specific URLs and a force-reload could wipe a just-saved image, leaving a broken dark-theme preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 18:01:26 +03:30
co-authored by Cursor
parent 3d63e2cf7f
commit 464995a43e
6 changed files with 142 additions and 26 deletions
+38 -11
View File
@@ -80,36 +80,63 @@ export function preferHttpsFileUrl(url: string): string {
/**
* Turns API paths like "storage/1405/02/27/file.jpg" into a full URL.
* Absolute URLs that already contain /storage/ are rebuilt onto the API base
* so preview/theme never keep a host without /server.
*/
export function toAbsoluteFileUrl(path?: string | null): string | undefined {
if (!path?.trim()) return undefined
const trimmed = path.trim()
const storageMatch = trimmed.match(/(?:^|\/)storage\/(.+)$/i)
const base = getFileBaseUrl()
if (storageMatch && base) {
return preferHttpsFileUrl(`${ensureServerBase(base)}/storage/${storageMatch[1]}`)
}
if (/^https?:\/\//i.test(trimmed)) {
return preferHttpsFileUrl(trimmed)
}
const storageMatch = trimmed.match(/(?:^|\/)storage\/(.+)$/i)
if (storageMatch) {
const base = getFileBaseUrl()
if (base) {
return preferHttpsFileUrl(`${base}/storage/${storageMatch[1]}`)
}
}
if (/^[a-z0-9.-]+(?:\/server)?\/storage\//i.test(trimmed)) {
return `https://${trimmed.replace(/^\/+/, '')}`
}
const base = getFileBaseUrl()
if (!base) return undefined
if (trimmed.startsWith('/')) {
return preferHttpsFileUrl(`${base}${trimmed}`)
return preferHttpsFileUrl(`${ensureServerBase(base)}${trimmed}`)
}
return preferHttpsFileUrl(`${base}/${trimmed.replace(/^\//, '')}`)
return preferHttpsFileUrl(`${ensureServerBase(base)}/${trimmed.replace(/^\//, '')}`)
}
function ensureServerBase(base: string): string {
const trimmed = base.replace(/\/$/, '')
if (/\/(server|api)$/i.test(trimmed) || /\/(server|api)\//i.test(trimmed)) {
return trimmed
}
return `${trimmed}/server`
}
/** مسیر نسبی storage برای ذخیره در const — مستقل از دامنهٔ پنل */
export function toStoredStoragePath(path?: string | null): string | null {
if (!path?.trim()) return null
const match = path.trim().match(/(?:^|\/)(storage\/.+)$/i)
return match ? match[1].replace(/^\/+/, '') : path.trim()
}
/** اگر فایل WebP باشد و URL با .png ذخیره شده باشد، پسوندهای جایگزین را هم امتحان کن. */
export function fileUrlCandidates(url?: string | null): string[] {
const absolute = toAbsoluteFileUrl(url) ?? url?.trim() ?? ''
if (!absolute) return []
const match = absolute.match(/^(.*)\.(png|jpe?g|webp|gif)(\?.*)?$/i)
if (!match) return [absolute]
const stem = match[1]
const current = match[2].toLowerCase()
const query = match[3] ?? ''
const alts = ['webp', 'png', 'jpg', 'jpeg', 'gif']
return [absolute, ...alts.filter((ext) => ext !== current).map((ext) => `${stem}.${ext}${query}`)]
}
function isBareFileName(value: string): boolean {