chore: add media migration scripts, migrate media.

This commit is contained in:
syntaxbullet
2026-06-16 16:54:02 +02:00
parent cde2164773
commit 647967482b
10 changed files with 466 additions and 50 deletions

View File

@@ -2,9 +2,11 @@
import React, { useEffect, useState } from 'react'
import CmsMediaRewriter from '@/spa/components/CmsMediaRewriter'
import Layout from '@/spa/components/layout/Layout'
import ScrollToTop from '@/spa/components/layout/ScrollToTop'
import type { SpaFooterData, SpaHeaderData } from '@/spa/cmsNavigation'
import type { SpaMediaMap } from '@/spa/cmsMedia'
import About from '@/spa/pages/About'
import BlogDetail from '@/spa/pages/BlogDetail'
import Contact from '@/spa/pages/Contact'
@@ -46,10 +48,12 @@ export default function NextApp({
pathname,
header,
footer,
media,
}: {
pathname: string
header?: SpaHeaderData
footer?: SpaFooterData
media?: SpaMediaMap
}) {
const [loading, setLoading] = useState(true)
@@ -78,6 +82,7 @@ export default function NextApp({
return (
<>
<CmsMediaRewriter media={media} />
<ScrollToTop />
<Layout header={header} footer={footer}>
<Page />

48
src/spa/cmsMedia.ts Normal file
View File

@@ -0,0 +1,48 @@
import type { Media } from '@/payload-types'
export type SpaMediaEntry = {
source: string
media?: { url?: string } | string | number | Media
}
export type SpaMediaMap = Record<string, string>
export const defaultSpaMediaSources = [
'/bmp-logo.png',
'/logo.png',
'/gala-preistraeger.jpg',
'/munich-skyline.jpg',
'/bavarian-flag.jpg',
'/rauten.webp',
'/website-template-OG.webp',
'/images/bmp-banner.jpg',
'/images/buehne-gewinner.jpg',
'/images/buehne-moderatoren.jpg',
'/images/ewif-logo.png',
'/images/gala-dinner.jpg',
'/images/gala-saal-overview.jpg',
'/images/gewinner-gruppenfoto.jpg',
'/images/mitglied-hero.jpg',
'/images/netzwerk-hero.jpg',
'/images/networking-innenhof.jpg',
'/images/partner-logos.png',
'/images/preisuebergabe.jpg',
'/images/preistraeger-award.jpg',
'/images/preistraeger-gruppe.jpg',
'/images/preistraeger-jubel.jpg',
'/images/saal-gedeckt.jpg',
'/images/saal-leer-atmosphaere.jpg',
'/images/tisch-detail.jpg',
]
const isMedia = (value: unknown): value is Media => Boolean(value && typeof value === 'object' && 'url' in value)
export function normalizeSpaMediaMap(global: any): SpaMediaMap {
const entries: SpaMediaEntry[] = Array.isArray(global?.assets) ? global.assets : []
return entries.reduce((acc: SpaMediaMap, entry: SpaMediaEntry) => {
if (!entry?.source) return acc
if (isMedia(entry.media) && entry.media.url) acc[entry.source] = entry.media.url
return acc
}, {})
}

View File

@@ -0,0 +1,79 @@
'use client'
import { useEffect } from 'react'
import type { SpaMediaMap } from '@/spa/cmsMedia'
const CSS_URL_RE = /url\((['"]?)(.*?)\1\)/g
function canonical(value: string) {
if (!value) return value
try {
const url = new URL(value, window.location.origin)
return url.origin === window.location.origin ? `${url.pathname}${url.search}` : value
} catch {
return value
}
}
function rewriteCssUrls(value: string, media: SpaMediaMap) {
return value.replace(CSS_URL_RE, (_match, quote: string, url: string) => {
const replacement = media[url] || media[canonical(url)]
return replacement ? `url(${quote}${replacement}${quote})` : _match
})
}
function rewrite(root: ParentNode, media: SpaMediaMap) {
root.querySelectorAll('img, source, video').forEach((node) => {
const el = node as HTMLImageElement | HTMLSourceElement | HTMLVideoElement
const current = el.getAttribute('src')
if (current) {
const replacement = media[current] || media[canonical(current)]
if (replacement && current !== replacement) el.setAttribute('src', replacement)
}
const poster = el.getAttribute('poster')
if (poster) {
const replacement = media[poster] || media[canonical(poster)]
if (replacement && poster !== replacement) el.setAttribute('poster', replacement)
}
})
root.querySelectorAll<HTMLElement>('[style*="url("]').forEach((el) => {
const backgroundImage = el.style.backgroundImage
if (backgroundImage) {
const rewritten = rewriteCssUrls(backgroundImage, media)
if (rewritten !== backgroundImage) el.style.backgroundImage = rewritten
}
})
}
export default function CmsMediaRewriter({ media }: { media?: SpaMediaMap }) {
useEffect(() => {
if (!media || Object.keys(media).length === 0) return
rewrite(document, media)
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) rewrite(node as Element, media)
})
if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
rewrite((mutation.target as Element).parentNode || document, media)
}
}
})
observer.observe(document.body, {
attributes: true,
attributeFilter: ['src', 'poster', 'style'],
childList: true,
subtree: true,
})
return () => observer.disconnect()
}, [media])
return null
}