forked from cms/tibi-svelte-starter
✨ 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.
This commit is contained in:
@@ -198,7 +198,7 @@ The exact shape can vary, but the pattern stays the same: block type first, then
|
||||
|
||||
### Step 3a: Build and wire the block registry
|
||||
|
||||
For this starter, the pagebuilder registry is not implicit. Nova loads it from the admin bundle via `meta.pagebuilder.blockRegistry.file`.
|
||||
In current Tibi/Nova projects, the pagebuilder registry is typically not implicit. Nova loads it from the admin bundle via `meta.pagebuilder.blockRegistry.file`.
|
||||
|
||||
The concrete chain is:
|
||||
|
||||
@@ -207,7 +207,7 @@ The concrete chain is:
|
||||
3. build the admin bundle with `yarn build`
|
||||
4. point the collection field or collection meta to the built module
|
||||
|
||||
The current starter already does this in `frontend/src/admin.ts` and `api/collections/content.yml`.
|
||||
The concrete file names vary by project, but the pattern is the same: registry code lives in the admin bundle and collection config points to the built admin asset.
|
||||
|
||||
Typical starter pattern:
|
||||
|
||||
@@ -238,7 +238,7 @@ meta:
|
||||
file: /_/assets/dist/admin.mjs?v=${ADMIN_ASSET_VERSION}
|
||||
```
|
||||
|
||||
Important constraints for this starter:
|
||||
Important constraints for this setup:
|
||||
|
||||
- the registry module must be part of the admin bundle, not a random standalone file outside the build pipeline
|
||||
- the exported registry keys must match the block type values stored in the collection
|
||||
@@ -246,6 +246,26 @@ Important constraints for this starter:
|
||||
- if the registry file path in YAML and the built admin asset diverge, Nova can still render the schema but the pagebuilder preview/picker loses its real block definitions
|
||||
- in Nova pagebuilder preview, file fields are already normalized by the admin backend to absolute `http(s)://...` URLs when appropriate; preview code must not prepend `apiBase`, `projectBase`, or other frontend URL helpers when the value is already absolute
|
||||
- Nova may also pass preview rows with hydrated `_lookup` data for FK-like fields; the registry/block preview should consume that data directly instead of trying to re-fetch or manually hydrate references inside the admin preview
|
||||
- For medialib-based images, prefer the same shared frontend widget used on the public site rather than preview-only URL logic. The widget/helper must honor `apiBaseOverride` so filter URLs, placeholders, and medialib files keep working inside admin preview.
|
||||
- Nova's `render(container, row, context)` provides API path information. **`context.projectBase`** contains the full project-specific API base including namespace. `context.apiBase` may only contain the generic `/api/` root and is often not sufficient for project-scoped collection endpoints. Blocks that load collection data in preview should therefore use the project-specific base when one is available:
|
||||
|
||||
```ts
|
||||
import { apiBaseOverride } from "./lib/store"
|
||||
import { get } from "svelte/store"
|
||||
const prev = get(apiBaseOverride)
|
||||
if (context?.projectBase) apiBaseOverride.set(String(context.projectBase))
|
||||
// mount(BlockRenderer, ...)
|
||||
// in destroy(): apiBaseOverride.set(prev)
|
||||
```
|
||||
|
||||
When a pagebuilder block renders images from medialib, prefer this pattern:
|
||||
|
||||
1. request lookup-resolved medialib entries in the data load
|
||||
2. pass the resolved entry into the shared image widget
|
||||
3. let that widget decide filter sizing from explicit `filter` or `minWidth`
|
||||
4. rely on `apiBaseOverride` / `context.projectBase` for admin-safe URL resolution
|
||||
|
||||
Do not create a second, preview-only image rendering path that diverges from the public frontend. That usually causes broken placeholders, wrong filter URLs, or SSR/admin mismatches later.
|
||||
|
||||
Use collection-level `meta.pagebuilder.blockRegistry.file` when several pagebuilder fields share the same registry. Override at field level only when one field genuinely needs a different registry.
|
||||
|
||||
@@ -358,6 +378,91 @@ Avoid pushing these concerns into block components unless there is a strong reas
|
||||
- unrelated API fetching
|
||||
- page-level navigation concerns
|
||||
|
||||
### 7a. Admin pagebuilder preview: CSS custom properties in shadow DOM
|
||||
|
||||
The Nova pagebuilder renders block previews in an isolated DOM context (shadow DOM or detached subtree). Tailwind 4's `@theme` directive generates CSS custom properties on `:root`, but these do **not** cascade into shadow DOM contexts.
|
||||
|
||||
**Consequence:** Block previews in the admin can have wrong colors (light text instead of dark, missing brand colors) because `var(--color-ink)` resolves to nothing.
|
||||
|
||||
**Fix:** Add a `:host` selector in the project's CSS file that redeclares the theme variables for the shadow DOM context. Also set a hardcoded `color` fallback on `[data-admin-preview]` since the Nova preview container has this attribute.
|
||||
|
||||
```css
|
||||
:host,
|
||||
[data-admin-preview] {
|
||||
--color-ink: #2c3e45;
|
||||
/* … all theme color variables … */
|
||||
font-family: "Inter Tight", system-ui, sans-serif;
|
||||
color: #2c3e45; /* hardcoded fallback, not var() */
|
||||
}
|
||||
```
|
||||
|
||||
Verify by checking admin pagebuilder block preview after any CSS theme changes.
|
||||
|
||||
### 7b. Admin pagebuilder preview: API calls from dynamic blocks
|
||||
|
||||
Blocks that load data via API (e.g. `CategoryGridBlock` using `getCachedEntries`) need the correct API base URL in the admin preview. The Nova pagebuilder's `render()` callback receives a `context` object with optional `apiBase` and `namespace`, but not all versions provide these.
|
||||
|
||||
The `admin.mjs` is loaded by the pagebuilder via dynamic `import()` — the URL is resolved relative to the admin page, NOT relative to the API. So `import.meta.url` contains the admin's page URL (e.g. `/_/assets/dist/admin.mjs`), not the tibi-server's API URL. Regex extraction from `import.meta.url` for the pattern `/api/_/{namespace}/` does NOT work for this reason.
|
||||
|
||||
**Reliable approach:** use multiple fallbacks in `admin.ts`, with the admin hostname pattern as the most robust:
|
||||
|
||||
1. `context.apiBase` (from Nova when available)
|
||||
2. `context.namespace` (from Nova)
|
||||
3. `import.meta.url` regex (works when admin serves admin.mjs through its own API proxy)
|
||||
4. **Hostname extraction**: admin URL is `{project}-tibiadmin.{domain}` → extract project name
|
||||
5. DOM scan: find any element with `src`/`href` containing `/api/_/{namespace}/`
|
||||
|
||||
```ts
|
||||
const prevApiBase = get(apiBaseOverride)
|
||||
let ns: string | null = null
|
||||
|
||||
if (context?.apiBase) {
|
||||
apiBaseOverride.set(String(context.apiBase))
|
||||
} else {
|
||||
if (context?.namespace) ns = String(context.namespace)
|
||||
if (!ns) {
|
||||
try {
|
||||
ns = ((import.meta as any).url || "").match(/\/api\/_\/([^/]+)\//)?.[1]
|
||||
} catch {}
|
||||
}
|
||||
// Most reliable: admin hostname is always {namespace}-tibiadmin.{domain}
|
||||
if (!ns && typeof window !== "undefined") {
|
||||
const h = window.location.hostname.match(/^(.+?)-tibiadmin\./)
|
||||
if (h) ns = h[1]
|
||||
}
|
||||
// Fallback: scan DOM for API references
|
||||
if (!ns && typeof document !== "undefined") {
|
||||
const el = document.querySelector('[src*="/api/_/"], [href*="/api/_/"]')
|
||||
if (el) {
|
||||
const a = el.getAttribute("src") || el.getAttribute("href") || ""
|
||||
ns = a.match(/\/api\/_\/([^/]+)\//)?.[1] || null
|
||||
}
|
||||
}
|
||||
if (ns) apiBaseOverride.set(`/api/_/${ns}/`)
|
||||
}
|
||||
```
|
||||
|
||||
Set the `apiBaseOverride` store BEFORE mounting the block component so API calls inside `$effect` use the correct base.
|
||||
|
||||
The Nova pagebuilder renders block previews in an isolated DOM context (shadow DOM or detached subtree). Tailwind 4's `@theme` directive generates CSS custom properties on `:root`, but these do **not** cascade into shadow DOM contexts.
|
||||
|
||||
**Consequence:** Block previews in the admin can have wrong colors (light text instead of dark, missing brand colors) because `var(--color-ink)` resolves to nothing.
|
||||
|
||||
**Fix:** Add a `:host` selector in the project's CSS file that redeclares the theme variables for the shadow DOM context. Also set a hardcoded `color` fallback on `[data-admin-preview]` since the Nova preview container has this attribute.
|
||||
|
||||
```css
|
||||
:host,
|
||||
[data-admin-preview] {
|
||||
--color-ink: #2c3e45;
|
||||
--color-ink-2: #3a4d56;
|
||||
/* … all theme color variables … */
|
||||
font-family: "Inter Tight", system-ui, sans-serif;
|
||||
color: #2c3e45; /* hardcoded fallback, not var() */
|
||||
}
|
||||
```
|
||||
|
||||
Verify by checking admin pagebuilder block preview after any CSS theme changes.
|
||||
|
||||
### 8. Prepare for styling consistency across blocks
|
||||
|
||||
A block system works better when blocks share a few stable layout conventions.
|
||||
|
||||
Reference in New Issue
Block a user