Files
apairon 4020ad62c5 ✨ feat: enhance medialib image handling and add asset URL resolution
- Implemented `resolveApiAssetUrl` function to normalize asset URLs based on API base.
- Updated `MedialibImage` component to utilize new asset URL resolution and added support for alt text and class properties.
- Enhanced image loading behavior with improved width measurement and focal point handling.
- Added placeholder image handling and improved accessibility with alt text.
- Introduced new test script for auditing broken links in skill documentation.
- Expanded seeded test content to include medialib entries and updated related tests for pagebuilder previews.
- Improved global setup and teardown logging for clarity on seeded content management.
2026-05-17 00:52:41 +00:00

151 lines
4.5 KiB
TypeScript

import { mount, unmount, type Component, type SvelteComponent } from "svelte"
import { apiBaseOverride } from "./lib/store"
import BlockRenderer from "./blocks/BlockRenderer.svelte"
const previewCssUrl = new URL("./index.css", import.meta.url).toString()
type BlockRenderContext = {
namespace?: string
apiBase?: string
projectBase?: string
}
type BlockHandle = {
update(row: Record<string, any>, context?: BlockRenderContext): void
destroy(): void
}
type BlockDefinition = {
render(container: HTMLElement | ShadowRoot, row: Record<string, any>, context?: BlockRenderContext): BlockHandle
css?: string[]
previewStyles?: Record<string, string>
label?: string
icon?: string
color?: string
}
type BlockPresentation = {
label: string
icon: string
color: string
}
function getAdminPreviewProps(props?: { [key: string]: any }) {
return {
isAdminPreview: true,
...(props || {}),
}
}
function getRenderedElement(
component: typeof SvelteComponent,
options?: { props: { [key: string]: any }; addCss?: string[] },
nestedElements?: { tagName: string; className?: string }[]
) {
const el = document.createElement("div")
el.attachShadow({ mode: "open" })
const body = document.createElement("body")
// build nested divs with css classes
let target: HTMLElement = body
nestedElements?.forEach((e) => {
const newElement = document.createElement(e.tagName)
if (e.className) {
newElement.className = e.className
}
target.appendChild(newElement)
target = newElement
})
el.shadowRoot.appendChild(body)
options?.addCss?.forEach((css) => {
const link = document.createElement("link")
link.rel = "stylesheet"
link.href = css
link.type = "text/css"
el.shadowRoot.appendChild(link)
})
new component({
target: target,
props: getAdminPreviewProps(options?.props),
})
return el
}
function createContentBlockDefinition(presentation: BlockPresentation): BlockDefinition {
return {
css: [previewCssUrl],
label: presentation.label,
icon: presentation.icon,
color: presentation.color,
previewStyles: {
"background-color": "white",
position: "relative",
},
render(container, row, context) {
const previewApiBase = context?.projectBase || context?.apiBase
if (previewApiBase) {
apiBaseOverride.set(String(previewApiBase))
}
const target = document.createElement("div")
target.dataset.adminPreview = "true"
container.appendChild(target)
let mountedComponent = mount(BlockRenderer as Component<any>, {
target,
props: {
blocks: [row as ContentBlockEntry],
isAdminPreview: true,
},
})
return {
update(nextRow, nextContext) {
const nextPreviewApiBase = nextContext?.projectBase || nextContext?.apiBase
if (nextPreviewApiBase) {
apiBaseOverride.set(String(nextPreviewApiBase))
}
unmount(mountedComponent)
target.innerHTML = ""
mountedComponent = mount(BlockRenderer as Component<any>, {
target,
props: {
blocks: [nextRow as ContentBlockEntry],
isAdminPreview: true,
},
})
},
destroy() {
unmount(mountedComponent)
target.remove()
},
}
},
}
}
const blockRegistry = {
hero: createContentBlockDefinition({ label: "Hero", icon: "image", color: "#1d4ed8" }),
features: createContentBlockDefinition({ label: "Features", icon: "view_quilt", color: "#0f766e" }),
richtext: createContentBlockDefinition({ label: "Richtext", icon: "article", color: "#7c3aed" }),
accordion: createContentBlockDefinition({ label: "Accordion", icon: "expand", color: "#b45309" }),
"contact-form": createContentBlockDefinition({
label: "Contact Form",
icon: "mail",
color: "#be185d",
}),
}
export {
getAdminPreviewProps,
getRenderedElement,
blockRegistry,
// pass also required svelte components here
}