@uniweb/runtime 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ssr.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isRichSchema, singularize, createUniweb } from "@uniweb/core";
2
2
  import React from "react";
3
3
  import { renderToString } from "react-dom/server";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { buildSectionOverrides } from "@uniweb/theming";
5
6
  function guaranteeItemStructure(item) {
6
7
  return {
@@ -259,6 +260,146 @@ function getComponentMeta(componentName) {
259
260
  function getComponentDefaults(componentName) {
260
261
  return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
261
262
  }
263
+ const HUMANITIES = {
264
+ figure: { label: "Figure", labelPlural: "Figures", counter: "arabic", sep: " " },
265
+ equation: { label: "Equation", labelPlural: "Equations", counter: "arabic", sep: " " },
266
+ section: { label: "§", labelPlural: "§§", counter: "hierarchical", sep: "" },
267
+ table: { label: "Table", labelPlural: "Tables", counter: "arabic", sep: " " }
268
+ };
269
+ const ENGINEERING = {
270
+ figure: { label: "Fig.", labelPlural: "Figs.", counter: "arabic", sep: " " },
271
+ equation: { label: "Eq.", labelPlural: "Eqs.", counter: "arabic", sep: " " },
272
+ section: { label: "Sec.", labelPlural: "Secs.", counter: "hierarchical", sep: " " },
273
+ table: { label: "Tab.", labelPlural: "Tabs.", counter: "arabic", sep: " " }
274
+ };
275
+ const GERMAN = {
276
+ figure: { label: "Abb.", labelPlural: "Abb.", counter: "arabic", sep: " " },
277
+ equation: { label: "Gl.", labelPlural: "Gl.", counter: "arabic", sep: " " },
278
+ section: { label: "Abschn.", labelPlural: "Abschn.", counter: "hierarchical", sep: " " },
279
+ table: { label: "Tab.", labelPlural: "Tab.", counter: "arabic", sep: " " }
280
+ };
281
+ const PLAIN = {
282
+ figure: { label: "", labelPlural: "", counter: "arabic", sep: "" },
283
+ equation: { label: "", labelPlural: "", counter: "arabic", sep: "" },
284
+ section: { label: "", labelPlural: "", counter: "hierarchical", sep: "" },
285
+ table: { label: "", labelPlural: "", counter: "arabic", sep: "" }
286
+ };
287
+ const XREF_STYLES = {
288
+ humanities: HUMANITIES,
289
+ engineering: ENGINEERING,
290
+ german: GERMAN,
291
+ plain: PLAIN
292
+ };
293
+ const DEFAULT_XREF_STYLE = "humanities";
294
+ function resolveXrefStyle(presetName, config) {
295
+ const preset = XREF_STYLES[presetName] || XREF_STYLES[DEFAULT_XREF_STYLE];
296
+ const merged = { ...preset };
297
+ const foundationKinds = globalThis.uniweb?.foundationConfig?.xref?.kinds;
298
+ if (foundationKinds && typeof foundationKinds === "object") {
299
+ for (const [kind, meta] of Object.entries(foundationKinds)) {
300
+ merged[kind] = { ...merged[kind], ...meta };
301
+ }
302
+ }
303
+ const docOverrides = config?.book?.xref || config?.xref || null;
304
+ if (docOverrides && typeof docOverrides === "object") {
305
+ for (const [kind, meta] of Object.entries(docOverrides)) {
306
+ if (meta && typeof meta === "object") {
307
+ merged[kind] = { ...merged[kind], ...meta };
308
+ }
309
+ }
310
+ }
311
+ return merged;
312
+ }
313
+ function getKindMeta(style, kind) {
314
+ return style?.[kind] || null;
315
+ }
316
+ function splitKeys(raw) {
317
+ return String(raw || "").split(";").map((k) => k.trim().replace(/^#/, "")).filter(Boolean);
318
+ }
319
+ function formatLocator(params) {
320
+ const { page, locator, label = "page" } = params || {};
321
+ const value = page || locator;
322
+ if (!value) return "";
323
+ const labels = {
324
+ page: "p.",
325
+ chapter: "chap.",
326
+ section: "§",
327
+ paragraph: "¶"
328
+ };
329
+ const lab = labels[label] || `${label}.`;
330
+ return ` (${lab} ${value})`;
331
+ }
332
+ function renderEntry(entry, kindMeta) {
333
+ const label = kindMeta?.label || "";
334
+ const sep = kindMeta?.sep ?? " ";
335
+ const counter = entry.counterText;
336
+ return label ? `${label}${sep}${counter}` : counter;
337
+ }
338
+ function renderGroupSameKind(entries, kindMeta) {
339
+ if (entries.length === 1) {
340
+ return renderEntry(entries[0], kindMeta);
341
+ }
342
+ const label = kindMeta?.labelPlural || kindMeta?.label || "";
343
+ const sep = kindMeta?.sep ?? " ";
344
+ const counters = entries.map((e) => e.counterText);
345
+ let body;
346
+ if (counters.length === 2) {
347
+ body = counters.join(" and ");
348
+ } else {
349
+ body = counters.slice(0, -1).join(", ") + ", and " + counters[counters.length - 1];
350
+ }
351
+ return label ? `${label}${sep}${body}` : body;
352
+ }
353
+ function Ref({ params, block }) {
354
+ const website = block?.website;
355
+ const xref = website?.xref || website?.config?.xref || null;
356
+ const entries = xref?.entries || {};
357
+ const styleName = website?.config?.book?.xrefStyle || "humanities";
358
+ const style = resolveXrefStyle(styleName, website?.config);
359
+ const ids = splitKeys(params?.key);
360
+ if (ids.length === 0) {
361
+ return /* @__PURE__ */ jsx("span", { className: "xref xref--missing", title: "No id", children: "[?]" });
362
+ }
363
+ const resolved = ids.map((id) => {
364
+ const entry = entries[id];
365
+ return entry ? { id, entry, kindMeta: getKindMeta(style, entry.kind) } : { id, missing: true };
366
+ });
367
+ if (resolved.length === 1 && resolved[0].missing) {
368
+ return /* @__PURE__ */ jsxs("span", { className: "xref xref--missing", title: `Missing label: ${resolved[0].id}`, children: [
369
+ "[?",
370
+ resolved[0].id,
371
+ "]"
372
+ ] });
373
+ }
374
+ const allKinds = resolved.filter((r) => !r.missing).map((r) => r.entry.kind);
375
+ const sameKind = allKinds.every((k) => k === allKinds[0]);
376
+ const locator = formatLocator(params);
377
+ if (!sameKind) {
378
+ if (typeof console !== "undefined") {
379
+ console.warn(
380
+ `[xref] mixed-kind cluster (${[...new Set(allKinds)].join(", ")}) — falling back to comma-separated rendering`
381
+ );
382
+ }
383
+ const parts = resolved.map(
384
+ (r) => r.missing ? `[?${r.id}]` : renderEntry(r.entry, r.kindMeta)
385
+ );
386
+ return /* @__PURE__ */ jsxs("span", { className: "xref", children: [
387
+ parts.join(", "),
388
+ locator
389
+ ] });
390
+ }
391
+ const onlyResolved = resolved.filter((r) => !r.missing);
392
+ const text = onlyResolved.length > 0 ? renderGroupSameKind(
393
+ onlyResolved.map((r) => r.entry),
394
+ onlyResolved[0].kindMeta
395
+ ) : "";
396
+ const missingTail = resolved.filter((r) => r.missing).map((r) => `[?${r.id}]`).join(", ");
397
+ const body = [text, missingTail].filter(Boolean).join(", ");
398
+ return /* @__PURE__ */ jsxs("span", { className: "xref", children: [
399
+ body,
400
+ locator
401
+ ] });
402
+ }
262
403
  function default404Html(basePath = "") {
263
404
  const homeHref = basePath ? `${basePath}/` : "/";
264
405
  return `<div class="page-not-found" style="min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center"><h1 style="font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem">404</h1><p style="color:#64748b;margin-bottom:2rem">Page not found</p><a href="${homeHref}" style="color:#3b82f6;text-decoration:underline">Go to homepage</a></div>`;
@@ -526,6 +667,7 @@ function initPrerender(content, foundation, extensionsOrOptions, maybeOptions) {
526
667
  )
527
668
  );
528
669
  };
670
+ uniweb.defaultInsets = { Ref };
529
671
  const website = uniweb.activeWebsite;
530
672
  uniweb.routingComponents = {
531
673
  useLocation: () => {
package/dist/ssr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/default-404.js","../src/ssr-renderer.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n * - Entity-shape guarantees on `content.data` when `data.entity` is declared\n * and a cascade match exists (see applyEntityShape below)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\nimport { singularize, isRichSchema } from '@uniweb/core'\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n images: item.images || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n snippets: item.snippets || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n ...(item.math && item.math.length ? { math: item.math } : {}),\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n images: content.images || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n insets: content.insets || [],\n snippets: content.snippets || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Rare collections — surfaced only when present so pages that don't\n // use them don't pay the allocation cost. Foundations that need them\n // should check for presence (content.math?.length) or use\n // content.sequence for in-order rendering.\n ...(content.math && content.math.length ? { math: content.math } : {}),\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply field defaults from a rich form `fields` array to an object.\n *\n * Recurses into `type: 'form'` (composite arrays with childSchema) and\n * `type: 'nestedObject'` / `type: 'object'` (single nested objects).\n *\n * Conditional visibility (`field.condition`) is not yet applied here —\n * components receive all fields the author filled plus defaults; hiding\n * is a later pass that requires the shared evaluateCondition util.\n *\n * @param {Object} obj - Row data (object keyed by field id)\n * @param {Array} fields - Rich field definitions\n * @returns {Object} - obj with defaults filled in\n */\nfunction applyRichFieldDefaults(obj, fields) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj\n if (!Array.isArray(fields)) return obj\n\n const result = { ...obj }\n\n for (const field of fields) {\n if (!field || typeof field !== 'object' || !field.id) continue\n const id = field.id\n\n if (result[id] === undefined && field.default !== undefined) {\n result[id] = field.default\n }\n\n if (field.type === 'form' && field.childSchema && Array.isArray(result[id])) {\n result[id] = result[id].map(item =>\n applyRichFieldDefaults(item, field.childSchema.fields)\n )\n } else if (\n (field.type === 'nestedObject' || field.type === 'object') &&\n Array.isArray(field.fields) &&\n result[id] &&\n typeof result[id] === 'object'\n ) {\n result[id] = applyRichFieldDefaults(result[id], field.fields)\n }\n }\n\n return result\n}\n\n/**\n * Apply a rich form schema to its stored value.\n *\n * Shape rules:\n * - composite (isComposite=true) → value is array of childSchema rows\n * - when `childCollection` is set, value may be `{ [childCollection]: [...] }`\n * - non-composite → value is a single object keyed by field id\n */\nfunction applyRichSchemaToValue(value, schema) {\n if (value == null) return value\n\n if (schema.isComposite && schema.childSchema) {\n const childFields = schema.childSchema.fields\n const collectionKey = schema.childCollection\n\n if (collectionKey && value && typeof value === 'object' && !Array.isArray(value)) {\n const arr = Array.isArray(value[collectionKey]) ? value[collectionKey] : []\n return {\n ...value,\n [collectionKey]: arr.map(row => applyRichFieldDefaults(row, childFields)),\n }\n }\n\n if (Array.isArray(value)) {\n return value.map(row => applyRichFieldDefaults(row, childFields))\n }\n\n return value\n }\n\n if (Array.isArray(schema.fields)) {\n return applyRichFieldDefaults(value, schema.fields)\n }\n\n return value\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = isRichSchema(schema)\n ? applyRichSchemaToValue(rawValue, schema)\n : applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply entity-shape guarantees based on `data.entity` declaration.\n *\n * When a component declares `data: { entity: 'articles' }` **and** the\n * cascade produced a match for that schema (`content.data.articles` is\n * present), normalize:\n * - `content.data.articles` to an array (missing → `[]` is *not* added;\n * absence is preserved as a signal of \"no source\"). This only shapes\n * when a cascade match exists — if the key is missing entirely, it\n * stays missing.\n * - On template pages, `content.data[singular(entity)]` is guaranteed\n * to exist (defaulting to `null`) so components can do\n * `if (!article) return <NotFound />` without a `?.` chain.\n *\n * The `undefined` vs `[]` vs `null` distinctions are load-bearing:\n * - `content.data.articles === undefined` → no query for this entity\n * - `content.data.articles === []` → query ran, returned empty\n * - `content.data.article === null` → on template page, item not found\n * - `content.data.article === {...}` → on template page, item resolved\n *\n * @param {Object} data - content.data (already merged with entity data)\n * @param {Object|null} entityMeta - runtime data meta (`{ type, limit }`)\n * @param {Object|null} dynamicContext - set when block is on a template page\n * @returns {Object} data with entity-shape guarantees applied\n */\nexport function applyEntityShape(data, entityMeta, dynamicContext) {\n if (!entityMeta?.type || !data) return data || {}\n\n const plural = entityMeta.type\n const result = { ...data }\n\n // Only shape the collection when it was delivered. Absence stays absence.\n if (plural in result && !Array.isArray(result[plural]) && result[plural] != null) {\n // Delivered but wrong shape — coerce to array (single item → [item]).\n result[plural] = [result[plural]]\n }\n\n // On template pages, guarantee the singular key exists (null = not found).\n if (dynamicContext) {\n const singular = singularize(plural) || plural\n if (singular !== plural && !(singular in result)) {\n result[singular] = null\n }\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Merge entity data onto a block's parsedContent.data.\n *\n * Section-level data already on the block (from prerender fetches via\n * blockData.parsedContent.data in the Block constructor) takes priority;\n * entity data only fills missing keys. Mutates `block.parsedContent.data`\n * in place so the vanilla JS layer holds the assembled data and\n * subsequent reads see the same shape.\n */\nfunction mergeEntityData(block, entityData) {\n if (!entityData) return\n const current = block.parsedContent.data || {}\n let changed = false\n const merged = { ...current }\n for (const key of Object.keys(entityData)) {\n if (merged[key] === undefined) {\n merged[key] = entityData[key]\n changed = true\n }\n }\n if (changed) {\n block.parsedContent.data = merged\n }\n}\n\n/**\n * Run the foundation-level data handler on a block, if one is\n * registered. Runs after entity data merge and before the content\n * handler — the handler sees the fully assembled data and can filter,\n * reshape, or augment it before Loom (or any content transform) runs.\n *\n * The handler receives `(data, block)` where data is\n * `block.parsedContent.data`. It returns a new data object, or\n * null/undefined for no change. The returned data replaces\n * `block.parsedContent.data` for all downstream processing — both\n * the content handler and the component see the transformed data.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), or when no handler is registered.\n * Errors are logged and the original data is preserved.\n */\nfunction runDataHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.data\n if (typeof handler !== 'function') return\n\n try {\n const result = handler(block.parsedContent.data, block)\n if (result != null && result !== block.parsedContent.data) {\n block.parsedContent.data = result\n }\n } catch (err) {\n console.error('Foundation data handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level content handler on a block, if one is\n * registered. Runs at prop-preparation time — after the data handler\n * has had a chance to filter/reshape the data — so the handler sees\n * the fully assembled (and possibly filtered) data. Replaces\n * `block.parsedContent` in place with the re-parsed, instantiated\n * form. The handler receives `(data, block)` and reads raw\n * ProseMirror from `block.rawContent`.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), when no handler is registered, when the\n * block has no raw content, when the handler returns a no-change\n * signal (undefined, null, or the same reference as rawContent), or\n * when the handler throws. Errors are logged via `console.error`.\n */\nfunction runContentHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.content\n if (typeof handler !== 'function') return\n if (!block.rawContent || Object.keys(block.rawContent).length === 0) return\n\n try {\n const transformed = handler(block.parsedContent.data, block)\n if (!transformed || transformed === block.rawContent) return\n const reparsed = block.parseContent(transformed)\n reparsed.data = block.parsedContent.data\n block.parsedContent = reparsed\n block.items = reparsed.items || []\n } catch (err) {\n console.error('Foundation content handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level props handler on the final { content, params }\n * before they reach the component. Runs after content parsing, param\n * defaults, content guarantees, and schema application — the handler\n * sees the exact shape the component would receive and can modify it.\n *\n * The handler receives `(content, params, block)` and returns a new\n * `{ content, params }` object, or null/undefined for no change.\n *\n * Use cases: post-parse content reshaping, computed fields derived\n * from both content and params, param-driven content reorganization.\n * Errors are logged and the original props are preserved.\n */\nfunction runPropsHandler(content, params, block) {\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.props\n if (typeof handler !== 'function') return null\n\n try {\n const result = handler(content, params, block)\n if (result && typeof result === 'object') return result\n } catch (err) {\n console.error('Foundation props handler failed:', err)\n }\n return null\n}\n\n/**\n * Prepare props for a component with runtime guarantees.\n *\n * Does the full content-assembly pipeline in one place so both\n * renderers (`BlockRenderer.jsx` CSR and `ssr-renderer.js` SSG) share\n * the same code path:\n *\n * 1. Merge entity data (resolved by EntityStore) onto\n * `block.parsedContent.data`.\n * 2. Run the foundation data handler (if registered) to filter or\n * reshape the assembled data.\n * 3. Run the foundation content handler (if registered) on the\n * block. This may replace `block.parsedContent` with a re-parsed,\n * instantiated version.\n * 4. Apply param defaults from meta.\n * 5. Build the guaranteed content structure.\n * 6. Apply schemas to content.data.\n * 7. Run the foundation props handler (if registered) for\n * post-processing of the final { content, params }.\n *\n * Steps 1–3 mutate the block (vanilla JS layer). Steps 4–7 are\n * pure derivations of the block's now-assembled state.\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @param {Object|null} [entityData] - Entity data resolved by EntityStore (null if none)\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta, entityData = null) {\n mergeEntityData(block, entityData)\n runDataHandler(block)\n runContentHandler(block)\n\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n let content = guaranteeContentStructure(block.parsedContent)\n\n // Apply entity-shape guarantees when the component declared `data.entity`\n // and a cascade match exists. Preserves `undefined` vs `[]` vs `null`\n // distinctions so components can differentiate \"no source\" from\n // \"empty source\" from \"template item not found.\"\n const entityMeta = meta?.data || null\n if (entityMeta && content.data) {\n const dynamicContext = block.dynamicContext || block.page?.dynamicContext || null\n content.data = applyEntityShape(content.data, entityMeta, dynamicContext)\n }\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n // Post-process hook\n const adjusted = runPropsHandler(content, params, block)\n if (adjusted) {\n return {\n content: adjusted.content || content,\n params: adjusted.params || params,\n }\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Default 404 Page Content\n *\n * Single source of truth for the fallback 404 page shown when a site\n * has no custom 404 page defined. Used by:\n * - PageRenderer.jsx (client-side, as React elements)\n * - ssr-renderer.js generate404Html (build-time, as HTML string)\n *\n * The wrapper uses min-height + flex centering so the 404 content\n * renders at the same position regardless of parent layout context.\n * This prevents a visible flash when React hydrates over the SSR content.\n */\n\nimport React from 'react'\n\nconst styles = {\n wrapper: {\n minHeight: '80vh',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '2rem',\n textAlign: 'center',\n },\n heading: { fontSize: '3rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' },\n message: { color: '#64748b', marginBottom: '2rem' },\n link: { color: '#3b82f6', textDecoration: 'underline' },\n}\n\n/**\n * React element for client-side rendering (PageRenderer).\n * Reads basePath from the runtime so the homepage link works\n * in subdirectory deployments (e.g., /sites/testproject).\n */\nexport function Default404() {\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n const homeHref = basePath ? `${basePath}/` : '/'\n return React.createElement('div', { className: 'page-not-found', style: styles.wrapper },\n React.createElement('h1', { style: styles.heading }, '404'),\n React.createElement('p', { style: styles.message }, 'Page not found'),\n React.createElement('a', { href: homeHref, style: styles.link }, 'Go to homepage')\n )\n}\n\n/**\n * Static HTML string for SSR injection (generate404Html).\n *\n * @param {string} [basePath] - Base path prefix for the homepage link (e.g., '/sites/testproject')\n */\nexport function default404Html(basePath = '') {\n const homeHref = basePath ? `${basePath}/` : '/'\n return (\n `<div class=\"page-not-found\" style=\"min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center\">` +\n `<h1 style=\"font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem\">404</h1>` +\n `<p style=\"color:#64748b;margin-bottom:2rem\">Page not found</p>` +\n `<a href=\"${homeHref}\" style=\"color:#3b82f6;text-decoration:underline\">Go to homepage</a>` +\n `</div>`\n )\n}\n","/**\n * SSR Renderer\n *\n * Hook-free rendering pipeline for SSG (build) and cloud SSR (unicloud).\n * Mirrors BlockRenderer.jsx + Background.jsx using React.createElement\n * directly — no hooks, no JSX, no browser APIs.\n *\n * This is the single source of truth for how blocks render during prerender.\n * When modifying BlockRenderer.jsx or Background.jsx, update this file to match.\n *\n * Exports three layers:\n * 1. Rendering functions (renderBlock, renderBlocks, renderLayout, renderBackground)\n * 2. Initialization (initPrerender, prefetchIcons)\n * 3. Per-page rendering (renderPage, classifyRenderError, injectPageContent, escapeHtml)\n */\n\nimport React from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { createUniweb } from '@uniweb/core'\nimport { buildSectionOverrides } from '@uniweb/theming'\nimport { prepareProps, getComponentMeta } from './prepare-props.js'\nimport { default404Html } from './default-404.js'\n\n// ============================================================================\n// Layer 1: Rendering functions\n// ============================================================================\n\n/**\n * Valid color contexts for section theming\n */\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n/**\n * Build wrapper props from block configuration.\n * Mirrors getWrapperProps in BlockRenderer.jsx.\n */\nexport function getWrapperProps(block) {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n // Empty themeName = Auto → no context class → inherits tokens from :root\n // Non-empty = Pinned → context class sets tokens directly on the element\n let contextClass = ''\n if (theme && VALID_CONTEXTS.includes(theme)) {\n contextClass = `context-${theme}`\n }\n\n let className = contextClass\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {} } = block.standardOptions\n const style = {}\n\n // If background has content, ensure relative positioning and a stacking context\n // so the background's z-index stays contained within this section.\n if (background.mode) {\n style.position = 'relative'\n style.isolation = 'isolate'\n }\n\n // Apply context overrides as inline CSS custom properties\n if (block.contextOverrides) {\n for (const [key, value] of Object.entries(block.contextOverrides)) {\n style[`--${key}`] = value\n }\n }\n\n // Use stableId for DOM ID if available (stable across reordering)\n const sectionId = block.stableId || block.id\n\n return { id: `section-${sectionId}`, style, className, background }\n}\n\n/**\n * Convert hex/rgb color to rgba with opacity.\n * Mirrors withOpacity() in Background.jsx.\n */\nfunction withOpacity(color, opacity) {\n if (color.startsWith('#')) {\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n return `rgba(${r}, ${g}, ${b}, ${opacity})`\n }\n if (color.startsWith('rgb')) {\n const match = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (match) {\n return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`\n }\n }\n return color\n}\n\n/**\n * Resolve a URL against the site's base path.\n * Mirrors resolveUrl() in Background.jsx.\n */\nfunction resolveUrl(url) {\n if (!url || !url.startsWith('/')) return url\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n if (!basePath) return url\n if (url.startsWith(basePath + '/') || url === basePath) return url\n return basePath + url\n}\n\n/**\n * Render a background element for SSR.\n * Mirrors Background.jsx (color, gradient, image — not video).\n * Video backgrounds require JS for autoplay and are skipped during SSR.\n */\nexport function renderBackground(background) {\n if (!background?.mode) return null\n\n const containerStyle = {\n position: 'absolute',\n inset: '0',\n overflow: 'hidden',\n zIndex: 0,\n }\n\n const children = []\n\n // Color background\n if (background.mode === 'color' && background.color) {\n children.push(\n React.createElement('div', {\n key: 'bg-color',\n className: 'background-color',\n style: { position: 'absolute', inset: '0', backgroundColor: background.color },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Gradient background (supports string or object with opacity)\n if (background.mode === 'gradient' && background.gradient) {\n const g = background.gradient\n\n let bgValue\n if (typeof g === 'string') {\n bgValue = g\n } else {\n const {\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 1,\n endOpacity = 1,\n } = g\n const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start\n const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end\n bgValue = `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-gradient',\n className: 'background-gradient',\n style: { position: 'absolute', inset: '0', background: bgValue },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Image background\n if (background.mode === 'image' && background.image?.src) {\n const img = background.image\n children.push(\n React.createElement('div', {\n key: 'bg-image',\n className: 'background-image',\n style: {\n position: 'absolute',\n inset: '0',\n backgroundImage: `url(${resolveUrl(img.src)})`,\n backgroundPosition: img.position || 'center',\n backgroundSize: img.size || 'cover',\n backgroundRepeat: 'no-repeat',\n },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Overlay (gradient or solid)\n if (background.overlay?.enabled) {\n const ov = background.overlay\n let overlayStyle\n\n if (ov.gradient) {\n const g = ov.gradient\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n background: `linear-gradient(${g.angle || 180}deg, ${g.start || 'rgba(0,0,0,0.7)'} ${g.startPosition || 0}%, ${g.end || 'rgba(0,0,0,0)'} ${g.endPosition || 100}%)`,\n opacity: ov.opacity ?? 0.5,\n }\n } else {\n const baseColor = ov.type === 'light' ? '255, 255, 255' : '0, 0, 0'\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n backgroundColor: `rgba(${baseColor}, ${ov.opacity ?? 0.5})`,\n }\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-overlay',\n className: ov.gradient ? 'background-overlay background-overlay--gradient' : 'background-overlay background-overlay--solid',\n style: overlayStyle,\n 'aria-hidden': 'true',\n })\n )\n }\n\n if (children.length === 0) return null\n\n return React.createElement('div', {\n className: `background background--${background.mode}`,\n style: containerStyle,\n 'aria-hidden': 'true',\n }, ...children)\n}\n\n/**\n * Render a single block for SSR.\n * Mirrors BlockRenderer.jsx but without hooks (no runtime data fetching).\n *\n * Two modes (mirrors client BlockRenderer):\n * - Bare (as=null/false): component only, no wrapper\n * - Section (as='section'/'div'/etc.): full treatment with wrapper, context, background\n *\n * @param {Block} block - Block instance to render\n * @param {Object} [options]\n * @param {string|null} [options.as='section'] - Wrapper element tag, or null/false for bare mode\n * @returns {React.ReactElement}\n */\nexport function renderBlock(block, { as = 'section' } = {}) {\n const Component = block.initComponent()\n\n if (!Component) {\n return React.createElement('div', {\n className: 'block-error',\n style: { padding: '1rem', background: '#fef2f2', color: '#dc2626' },\n }, `Component not found: ${block.type}`)\n }\n\n // Resolve inherited entity data synchronously (SSG has no async).\n // EntityStore walks page/site hierarchy to find data matching meta.inheritData.\n const meta = getComponentMeta(block.type)\n const entityStore = block.website?.entityStore\n let entityData = null\n if (entityStore) {\n const resolved = entityStore.resolve(block, meta)\n if (resolved.status === 'ready') entityData = resolved.data\n }\n\n // Build content and params with runtime guarantees.\n // prepareProps handles the full pipeline: entity data merge,\n // foundation content handler invocation, guaranteed content\n // structure, schema application, and param defaults.\n // See prepare-props.js for the pipeline details.\n const prepared = prepareProps(block, meta, entityData)\n const params = prepared.params\n const content = { ...prepared.content, ...block.properties }\n\n const componentProps = { content, params, block }\n\n // Bare mode: component only, no wrapper or section chrome.\n // Used by ChildBlocks for grid cells, tab panels, inline children, insets.\n if (!as) {\n return React.createElement(Component, componentProps)\n }\n\n // Section mode: full treatment with wrapper, context classes, background.\n const { background, ...wrapperProps } = getWrapperProps(block)\n\n // Merge Component.className (static classes declared on the component function)\n const componentClassName = Component.className\n if (componentClassName) {\n wrapperProps.className = wrapperProps.className\n ? `${wrapperProps.className} ${componentClassName}`\n : componentClassName\n }\n\n // Check if component handles its own background\n const hasBackground = background?.mode && meta?.background !== 'self'\n block.hasBackground = hasBackground\n\n // Determine wrapper element:\n // - Explicit as (not 'section') → use as prop directly\n // - Component.as → use component's declared tag (e.g., Header.as = 'header')\n // - fallback → 'section'\n const wrapperTag = as !== 'section' ? as : (Component.as || 'section')\n\n if (hasBackground) {\n return React.createElement(wrapperTag, wrapperProps,\n renderBackground(background),\n React.createElement('div', { style: { position: 'relative', zIndex: 10 } },\n React.createElement(Component, componentProps)\n )\n )\n }\n\n return React.createElement(wrapperTag, wrapperProps,\n React.createElement(Component, componentProps)\n )\n}\n\n/**\n * Render an array of blocks for SSR.\n */\nexport function renderBlocks(blocks) {\n if (!blocks || blocks.length === 0) return null\n return blocks.map((block, index) =>\n React.createElement(React.Fragment, { key: block.id || index },\n renderBlock(block)\n )\n )\n}\n\n/**\n * Render page layout for SSR.\n * Mirrors Layout.jsx but without hooks.\n */\nexport function renderLayout(page, website) {\n const layoutName = page.getLayoutName()\n const RemoteLayout = website.getRemoteLayout(layoutName)\n const layoutMeta = website.getLayoutMeta(layoutName)\n\n const bodyBlocks = page.getBodyBlocks()\n const areas = page.getLayoutAreas()\n\n const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null\n const areaElements = {}\n for (const [name, blocks] of Object.entries(areas)) {\n areaElements[name] = renderBlocks(blocks)\n }\n\n if (RemoteLayout) {\n const params = { ...(layoutMeta?.defaults || {}), ...(page.getLayoutParams() || {}) }\n return React.createElement(RemoteLayout, {\n page, website, params,\n body: bodyElement,\n ...areaElements,\n })\n }\n\n // Default layout\n return React.createElement(React.Fragment, null,\n areaElements.header && React.createElement('header', null, areaElements.header),\n bodyElement && React.createElement('main', null, bodyElement),\n areaElements.footer && React.createElement('footer', null, areaElements.footer)\n )\n}\n\n// ============================================================================\n// Layer 2: Initialization\n// ============================================================================\n\n/**\n * Create and configure the Uniweb runtime for prerendering.\n *\n * Handles the full initialization sequence in the correct order:\n * createUniweb → setFoundation → capabilities → layoutMeta → basePath → childBlockRenderer.\n *\n * Returns the configured uniweb instance. Consumers can add extras after:\n * - Build: pre-populate DataStore, load extensions\n * - Unicloud: (none needed — payload is complete)\n *\n * NOTE: Does NOT clone content. Cloning is the consumer's responsibility\n * (build modifies content before init; unicloud clones upfront).\n *\n * @param {Object} content - Site content JSON (pages, config, hierarchy)\n * @param {Object} foundation - Loaded foundation module\n * @param {Object} [options]\n * @param {function} [options.onProgress] - Progress callback\n * @returns {Object} Configured uniweb instance\n */\nexport function initPrerender(content, foundation, extensionsOrOptions, maybeOptions) {\n // Backwards-compatible arg shape: (content, foundation, options) or\n // (content, foundation, extensions, options). Extensions must be passed at\n // construction so the Website's FetcherDispatcher sees their routes.\n let extensions = []\n let options = {}\n if (Array.isArray(extensionsOrOptions)) {\n extensions = extensionsOrOptions\n options = maybeOptions || {}\n } else {\n options = extensionsOrOptions || {}\n }\n const { onProgress = () => {} } = options\n\n onProgress('Initializing runtime...')\n // Uniweb constructor wires foundation, capabilities, layoutMeta, handlers,\n // and extensions at construction time — see framework/core/src/uniweb.js.\n const uniweb = createUniweb(content, foundation, extensions)\n\n // Set base path from site config for subdirectory deployments\n if (content.config?.base && uniweb.activeWebsite?.setBasePath) {\n uniweb.activeWebsite.setBasePath(content.config.base)\n }\n\n // Set childBlockRenderer so ChildBlocks/Visual/Render work during prerender.\n // Mirrors the client's ChildBlocks component in PageRenderer.jsx:\n // - default bare rendering (no wrapAs) — component only, no wrapper\n // - pass wrapAs to opt into full section treatment\n uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, wrapAs }) {\n const blockList = blocks || from?.childBlocks || []\n return blockList.map((childBlock, index) =>\n React.createElement(React.Fragment, { key: childBlock.id || index },\n renderBlock(childBlock, { as: wrapAs || null })\n )\n )\n }\n\n // Register SSR-safe routing so useRouting()/useActiveRoute() work during prerender.\n // renderPage() calls website.setActivePage() before rendering each page,\n // so activePage.route always reflects the page being rendered.\n const website = uniweb.activeWebsite\n uniweb.routingComponents = {\n useLocation: () => {\n const route = website?.activePage?.route || ''\n return { pathname: '/' + route, search: '', hash: '', state: null, key: 'default' }\n },\n useParams: () => ({}),\n useNavigate: () => () => {},\n }\n\n return uniweb\n}\n\n/**\n * Pre-fetch icons from CDN and populate the Uniweb icon cache.\n * Stores the cache on siteContent._iconCache for embedding in HTML.\n *\n * @param {Object} siteContent - Site content JSON (mutated: _iconCache added)\n * @param {Object} uniweb - Configured uniweb instance\n * @param {function} [onProgress] - Progress callback\n */\nexport async function prefetchIcons(siteContent, uniweb, onProgress = () => {}) {\n const icons = siteContent.icons?.used || []\n if (icons.length === 0) return\n\n const cdnBase = siteContent.config?.icons?.cdnUrl || 'https://uniweb.github.io/icons'\n\n onProgress(`Fetching ${icons.length} icons for SSR...`)\n\n const results = await Promise.allSettled(\n icons.map(async (iconRef) => {\n const [family, name] = iconRef.split(':')\n const url = `${cdnBase}/${family}/${family}-${name}.svg`\n const response = await fetch(url)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const svg = await response.text()\n uniweb.iconCache.set(`${family}:${name}`, svg)\n })\n )\n\n const succeeded = results.filter(r => r.status === 'fulfilled').length\n const failed = results.filter(r => r.status === 'rejected').length\n if (failed > 0) {\n const msg = `Fetched ${succeeded}/${icons.length} icons (${failed} failed)`\n console.warn(`[prerender] ${msg}`)\n onProgress(` ${msg}`)\n }\n\n // Store icon cache on siteContent for embedding in HTML\n if (uniweb.iconCache.size > 0) {\n siteContent._iconCache = Object.fromEntries(uniweb.iconCache)\n }\n}\n\n// ============================================================================\n// Layer 3: Per-page rendering\n// ============================================================================\n\n/**\n * Classify an SSR rendering error.\n *\n * @param {Error} err\n * @returns {{ type: 'hooks'|'null-component'|'unknown', message: string }}\n */\nexport function classifyRenderError(err) {\n const msg = err.message || ''\n\n if (msg.includes('Invalid hook call') || msg.includes('useState') || msg.includes('useEffect')) {\n return {\n type: 'hooks',\n message: 'contains components with React hooks (renders client-side)',\n }\n }\n\n if (msg.includes('Element type is invalid') && msg.includes('null')) {\n return {\n type: 'null-component',\n message: 'a component resolved to null (often hook-related, renders client-side)',\n }\n }\n\n return {\n type: 'unknown',\n message: msg,\n }\n}\n\n/**\n * Render a single page to HTML.\n *\n * Handles the full per-page pipeline:\n * setActivePage → renderLayout → renderToString → error handling → section override CSS.\n *\n * @param {Page} page - Page instance to render\n * @param {Website} website - Website instance\n * @returns {{ renderedContent: string, sectionOverrideCSS: string } | { error: { type: string, message: string } }}\n */\nexport function renderPage(page, website) {\n website.setActivePage(page.route)\n\n const element = renderLayout(page, website)\n\n let renderedContent\n try {\n renderedContent = renderToString(element)\n } catch (err) {\n return { error: classifyRenderError(err) }\n }\n\n // Build per-page section override CSS (theme pinning, component vars)\n const appearance = website.themeData?.appearance\n const sectionOverrideCSS = buildSectionOverrides(page.getPageBlocks(), appearance)\n\n return { renderedContent, sectionOverrideCSS }\n}\n\n// ============================================================================\n// HTML injection\n// ============================================================================\n\n/**\n * Escape HTML special characters.\n */\nexport function escapeHtml(str) {\n if (!str) return ''\n return String(str)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/**\n * Inject prerendered content into an HTML shell.\n *\n * Common operations shared by both build and cloud:\n * - Replace #root div with rendered HTML\n * - Update page title\n * - Add/update meta description\n * - Inject section override CSS\n *\n * Build layers its additional injections on top of this return value:\n * __SITE_CONTENT__ JSON, icon cache, theme CSS (build-specific).\n *\n * @param {string} html - HTML shell\n * @param {string} renderedContent - React renderToString output\n * @param {Object} page - Page data { title, description, route }\n * @param {Object} [options]\n * @param {string} [options.sectionOverrideCSS] - Per-page section override CSS\n * @returns {string} HTML with injected content\n */\nexport function injectPageContent(html, renderedContent, page, options = {}) {\n let result = html\n\n // Inject per-page section override CSS before </head>\n if (options.sectionOverrideCSS) {\n const overrideStyle = `<style id=\"uniweb-page-overrides\">\\n${options.sectionOverrideCSS}\\n</style>`\n result = result.replace('</head>', `${overrideStyle}\\n</head>`)\n }\n\n // Replace the empty root div with pre-rendered content\n result = result.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${renderedContent}</div>`\n )\n\n // Update page title (use getTitle() so isIndex pages inherit parent title)\n const pageTitle = page.getTitle?.() || page.title\n if (pageTitle) {\n result = result.replace(\n /<title>.*?<\\/title>/,\n `<title>${escapeHtml(pageTitle)}</title>`\n )\n }\n\n // Add/update meta description\n if (page.description) {\n const metaDesc = `<meta name=\"description\" content=\"${escapeHtml(page.description)}\">`\n if (result.includes('<meta name=\"description\"')) {\n result = result.replace(/<meta name=\"description\"[^>]*>/, metaDesc)\n } else {\n result = result.replace('</head>', `${metaDesc}\\n</head>`)\n }\n }\n\n return result\n}\n\n// ============================================================================\n// 404 fallback generation\n// ============================================================================\n\n/**\n * Generate 404.html content for static hosting fallback.\n *\n * Serves two purposes on static hosts (GitHub Pages, Cloudflare Pages, etc.):\n * 1. Real 404: pre-rendered custom 404 page content (or blank #root if none defined)\n * 2. Valid dynamic route (e.g. /blog/2): inline script clears #root so SPA renders fresh\n *\n * Flow: static host serves 404.html → inline script runs before React mounts →\n * - dynamic route: clears #root, React renders the page normally\n * - real 404: leaves #root with pre-rendered content, React re-renders same 404 page\n *\n * @param {Object} options\n * @param {string} options.baseHtml - Assembled HTML shell (with site content already injected)\n * @param {Object} options.website - Initialized Website instance (from initPrerender)\n * @param {Object} options.siteContent - Site content object (to find dynamic templates)\n * @returns {{ html: string, hasNotFoundPage: boolean }}\n */\nexport function generate404Html({ baseHtml, website, siteContent }) {\n // Extract patterns for routes that remain as dynamic templates (prerender: false)\n const dynamicTemplates = siteContent.pages?.filter((p) => p.isDynamic) || []\n const routePatterns = dynamicTemplates.map((p) => {\n // '/blog/:id' → /^\\/blog\\/[^\\/]+\\/?$/\n const escaped = p.route\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:[^/]+/g, '[^\\\\/]+')\n return `^${escaped}\\\\/?$`\n })\n\n let html = baseHtml\n\n // Pre-render the custom 404 page content into #root (if the site defines one),\n // otherwise inject a default 404 message so the page isn't blank before JS loads\n const notFoundPage = website.getNotFoundPage()\n if (notFoundPage) {\n const notFoundResult = renderPage(notFoundPage, website)\n if (notFoundResult && !notFoundResult.error) {\n html = injectPageContent(html, notFoundResult.renderedContent, notFoundPage, {\n sectionOverrideCSS: notFoundResult.sectionOverrideCSS,\n })\n }\n } else {\n const basePath = website.basePath || ''\n html = html.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${default404Html(basePath)}</div>`\n )\n }\n\n // Inject inline script: if path matches a dynamic route, clear #root before React mounts\n // so the SPA renders the correct page rather than the 404 content\n if (routePatterns.length > 0) {\n const patternList = routePatterns.map((p) => `/${p}/`).join(',')\n const dynamicScript =\n `<script>(function(){` +\n `var p=[${patternList}],r=window.location.pathname;` +\n `if(p.some(function(x){return x.test(r)})){` +\n `var el=document.getElementById('root');if(el)el.innerHTML='';` +\n `}})()</script>`\n html = html.replace('</body>', `${dynamicScript}\\n</body>`)\n }\n\n return { html, hasNotFoundPage: !!notFoundPage }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/xref-styles.js","../src/Ref/Ref.jsx","../src/default-404.js","../src/ssr-renderer.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n * - Entity-shape guarantees on `content.data` when `data.entity` is declared\n * and a cascade match exists (see applyEntityShape below)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\nimport { singularize, isRichSchema } from '@uniweb/core'\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n images: item.images || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n snippets: item.snippets || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n ...(item.math && item.math.length ? { math: item.math } : {}),\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n images: content.images || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n insets: content.insets || [],\n snippets: content.snippets || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Rare collections — surfaced only when present so pages that don't\n // use them don't pay the allocation cost. Foundations that need them\n // should check for presence (content.math?.length) or use\n // content.sequence for in-order rendering.\n ...(content.math && content.math.length ? { math: content.math } : {}),\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply field defaults from a rich form `fields` array to an object.\n *\n * Recurses into `type: 'form'` (composite arrays with childSchema) and\n * `type: 'nestedObject'` / `type: 'object'` (single nested objects).\n *\n * Conditional visibility (`field.condition`) is not yet applied here —\n * components receive all fields the author filled plus defaults; hiding\n * is a later pass that requires the shared evaluateCondition util.\n *\n * @param {Object} obj - Row data (object keyed by field id)\n * @param {Array} fields - Rich field definitions\n * @returns {Object} - obj with defaults filled in\n */\nfunction applyRichFieldDefaults(obj, fields) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj\n if (!Array.isArray(fields)) return obj\n\n const result = { ...obj }\n\n for (const field of fields) {\n if (!field || typeof field !== 'object' || !field.id) continue\n const id = field.id\n\n if (result[id] === undefined && field.default !== undefined) {\n result[id] = field.default\n }\n\n if (field.type === 'form' && field.childSchema && Array.isArray(result[id])) {\n result[id] = result[id].map(item =>\n applyRichFieldDefaults(item, field.childSchema.fields)\n )\n } else if (\n (field.type === 'nestedObject' || field.type === 'object') &&\n Array.isArray(field.fields) &&\n result[id] &&\n typeof result[id] === 'object'\n ) {\n result[id] = applyRichFieldDefaults(result[id], field.fields)\n }\n }\n\n return result\n}\n\n/**\n * Apply a rich form schema to its stored value.\n *\n * Shape rules:\n * - composite (isComposite=true) → value is array of childSchema rows\n * - when `childCollection` is set, value may be `{ [childCollection]: [...] }`\n * - non-composite → value is a single object keyed by field id\n */\nfunction applyRichSchemaToValue(value, schema) {\n if (value == null) return value\n\n if (schema.isComposite && schema.childSchema) {\n const childFields = schema.childSchema.fields\n const collectionKey = schema.childCollection\n\n if (collectionKey && value && typeof value === 'object' && !Array.isArray(value)) {\n const arr = Array.isArray(value[collectionKey]) ? value[collectionKey] : []\n return {\n ...value,\n [collectionKey]: arr.map(row => applyRichFieldDefaults(row, childFields)),\n }\n }\n\n if (Array.isArray(value)) {\n return value.map(row => applyRichFieldDefaults(row, childFields))\n }\n\n return value\n }\n\n if (Array.isArray(schema.fields)) {\n return applyRichFieldDefaults(value, schema.fields)\n }\n\n return value\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = isRichSchema(schema)\n ? applyRichSchemaToValue(rawValue, schema)\n : applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply entity-shape guarantees based on `data.entity` declaration.\n *\n * When a component declares `data: { entity: 'articles' }` **and** the\n * cascade produced a match for that schema (`content.data.articles` is\n * present), normalize:\n * - `content.data.articles` to an array (missing → `[]` is *not* added;\n * absence is preserved as a signal of \"no source\"). This only shapes\n * when a cascade match exists — if the key is missing entirely, it\n * stays missing.\n * - On template pages, `content.data[singular(entity)]` is guaranteed\n * to exist (defaulting to `null`) so components can do\n * `if (!article) return <NotFound />` without a `?.` chain.\n *\n * The `undefined` vs `[]` vs `null` distinctions are load-bearing:\n * - `content.data.articles === undefined` → no query for this entity\n * - `content.data.articles === []` → query ran, returned empty\n * - `content.data.article === null` → on template page, item not found\n * - `content.data.article === {...}` → on template page, item resolved\n *\n * @param {Object} data - content.data (already merged with entity data)\n * @param {Object|null} entityMeta - runtime data meta (`{ type, limit }`)\n * @param {Object|null} dynamicContext - set when block is on a template page\n * @returns {Object} data with entity-shape guarantees applied\n */\nexport function applyEntityShape(data, entityMeta, dynamicContext) {\n if (!entityMeta?.type || !data) return data || {}\n\n const plural = entityMeta.type\n const result = { ...data }\n\n // Only shape the collection when it was delivered. Absence stays absence.\n if (plural in result && !Array.isArray(result[plural]) && result[plural] != null) {\n // Delivered but wrong shape — coerce to array (single item → [item]).\n result[plural] = [result[plural]]\n }\n\n // On template pages, guarantee the singular key exists (null = not found).\n if (dynamicContext) {\n const singular = singularize(plural) || plural\n if (singular !== plural && !(singular in result)) {\n result[singular] = null\n }\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Merge entity data onto a block's parsedContent.data.\n *\n * Section-level data already on the block (from prerender fetches via\n * blockData.parsedContent.data in the Block constructor) takes priority;\n * entity data only fills missing keys. Mutates `block.parsedContent.data`\n * in place so the vanilla JS layer holds the assembled data and\n * subsequent reads see the same shape.\n */\nfunction mergeEntityData(block, entityData) {\n if (!entityData) return\n const current = block.parsedContent.data || {}\n let changed = false\n const merged = { ...current }\n for (const key of Object.keys(entityData)) {\n if (merged[key] === undefined) {\n merged[key] = entityData[key]\n changed = true\n }\n }\n if (changed) {\n block.parsedContent.data = merged\n }\n}\n\n/**\n * Run the foundation-level data handler on a block, if one is\n * registered. Runs after entity data merge and before the content\n * handler — the handler sees the fully assembled data and can filter,\n * reshape, or augment it before Loom (or any content transform) runs.\n *\n * The handler receives `(data, block)` where data is\n * `block.parsedContent.data`. It returns a new data object, or\n * null/undefined for no change. The returned data replaces\n * `block.parsedContent.data` for all downstream processing — both\n * the content handler and the component see the transformed data.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), or when no handler is registered.\n * Errors are logged and the original data is preserved.\n */\nfunction runDataHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.data\n if (typeof handler !== 'function') return\n\n try {\n const result = handler(block.parsedContent.data, block)\n if (result != null && result !== block.parsedContent.data) {\n block.parsedContent.data = result\n }\n } catch (err) {\n console.error('Foundation data handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level content handler on a block, if one is\n * registered. Runs at prop-preparation time — after the data handler\n * has had a chance to filter/reshape the data — so the handler sees\n * the fully assembled (and possibly filtered) data. Replaces\n * `block.parsedContent` in place with the re-parsed, instantiated\n * form. The handler receives `(data, block)` and reads raw\n * ProseMirror from `block.rawContent`.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), when no handler is registered, when the\n * block has no raw content, when the handler returns a no-change\n * signal (undefined, null, or the same reference as rawContent), or\n * when the handler throws. Errors are logged via `console.error`.\n */\nfunction runContentHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.content\n if (typeof handler !== 'function') return\n if (!block.rawContent || Object.keys(block.rawContent).length === 0) return\n\n try {\n const transformed = handler(block.parsedContent.data, block)\n if (!transformed || transformed === block.rawContent) return\n const reparsed = block.parseContent(transformed)\n reparsed.data = block.parsedContent.data\n block.parsedContent = reparsed\n block.items = reparsed.items || []\n } catch (err) {\n console.error('Foundation content handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level props handler on the final { content, params }\n * before they reach the component. Runs after content parsing, param\n * defaults, content guarantees, and schema application — the handler\n * sees the exact shape the component would receive and can modify it.\n *\n * The handler receives `(content, params, block)` and returns a new\n * `{ content, params }` object, or null/undefined for no change.\n *\n * Use cases: post-parse content reshaping, computed fields derived\n * from both content and params, param-driven content reorganization.\n * Errors are logged and the original props are preserved.\n */\nfunction runPropsHandler(content, params, block) {\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.props\n if (typeof handler !== 'function') return null\n\n try {\n const result = handler(content, params, block)\n if (result && typeof result === 'object') return result\n } catch (err) {\n console.error('Foundation props handler failed:', err)\n }\n return null\n}\n\n/**\n * Prepare props for a component with runtime guarantees.\n *\n * Does the full content-assembly pipeline in one place so both\n * renderers (`BlockRenderer.jsx` CSR and `ssr-renderer.js` SSG) share\n * the same code path:\n *\n * 1. Merge entity data (resolved by EntityStore) onto\n * `block.parsedContent.data`.\n * 2. Run the foundation data handler (if registered) to filter or\n * reshape the assembled data.\n * 3. Run the foundation content handler (if registered) on the\n * block. This may replace `block.parsedContent` with a re-parsed,\n * instantiated version.\n * 4. Apply param defaults from meta.\n * 5. Build the guaranteed content structure.\n * 6. Apply schemas to content.data.\n * 7. Run the foundation props handler (if registered) for\n * post-processing of the final { content, params }.\n *\n * Steps 1–3 mutate the block (vanilla JS layer). Steps 4–7 are\n * pure derivations of the block's now-assembled state.\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @param {Object|null} [entityData] - Entity data resolved by EntityStore (null if none)\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta, entityData = null) {\n mergeEntityData(block, entityData)\n runDataHandler(block)\n runContentHandler(block)\n\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n let content = guaranteeContentStructure(block.parsedContent)\n\n // Apply entity-shape guarantees when the component declared `data.entity`\n // and a cascade match exists. Preserves `undefined` vs `[]` vs `null`\n // distinctions so components can differentiate \"no source\" from\n // \"empty source\" from \"template item not found.\"\n const entityMeta = meta?.data || null\n if (entityMeta && content.data) {\n const dynamicContext = block.dynamicContext || block.page?.dynamicContext || null\n content.data = applyEntityShape(content.data, entityMeta, dynamicContext)\n }\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n // Post-process hook\n const adjusted = runPropsHandler(content, params, block)\n if (adjusted) {\n return {\n content: adjusted.content || content,\n params: adjusted.params || params,\n }\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Cross-reference style catalog.\n *\n * Cross-references are framework-managed (parallel to citestyle's nine\n * citation styles, but framework-internal because there's no CSL-for-\n * xrefs standard). Foundations pick a preset; documents may override\n * per-kind.\n *\n * Each preset is a map from kind name to:\n * {\n * label, // singular label, e.g. \"Figure\"\n * labelPlural, // plural label, e.g. \"Figures\"\n * counter, // 'arabic' | 'hierarchical' | null (no counter)\n * sep, // separator between label and counter (\"\" | \" \")\n * }\n *\n * The catalog ships four presets:\n * - humanities (default — \"Figure 3\", \"§3.2\", \"Equation 1\")\n * - engineering (\"Fig. 3\", \"Sec. 3.2\", \"Eq. 1\")\n * - german (\"Abb. 3\", \"Abschn. 3.2\", \"Gl. 1\")\n * - plain (counters only — \"3\", \"3.2\", \"1\")\n *\n * Foundations override the preset by declaring `xref.kinds` in\n * foundation.js (additive: new kinds and overrides for existing kinds).\n * Documents override per-kind via `book.xref.<kind>:` in document.yml.\n */\n\nconst HUMANITIES = {\n figure: { label: 'Figure', labelPlural: 'Figures', counter: 'arabic', sep: ' ' },\n equation: { label: 'Equation', labelPlural: 'Equations', counter: 'arabic', sep: ' ' },\n section: { label: '§', labelPlural: '§§', counter: 'hierarchical', sep: '' },\n table: { label: 'Table', labelPlural: 'Tables', counter: 'arabic', sep: ' ' },\n}\n\nconst ENGINEERING = {\n figure: { label: 'Fig.', labelPlural: 'Figs.', counter: 'arabic', sep: ' ' },\n equation: { label: 'Eq.', labelPlural: 'Eqs.', counter: 'arabic', sep: ' ' },\n section: { label: 'Sec.', labelPlural: 'Secs.', counter: 'hierarchical', sep: ' ' },\n table: { label: 'Tab.', labelPlural: 'Tabs.', counter: 'arabic', sep: ' ' },\n}\n\nconst GERMAN = {\n figure: { label: 'Abb.', labelPlural: 'Abb.', counter: 'arabic', sep: ' ' },\n equation: { label: 'Gl.', labelPlural: 'Gl.', counter: 'arabic', sep: ' ' },\n section: { label: 'Abschn.', labelPlural: 'Abschn.', counter: 'hierarchical', sep: ' ' },\n table: { label: 'Tab.', labelPlural: 'Tab.', counter: 'arabic', sep: ' ' },\n}\n\nconst PLAIN = {\n figure: { label: '', labelPlural: '', counter: 'arabic', sep: '' },\n equation: { label: '', labelPlural: '', counter: 'arabic', sep: '' },\n section: { label: '', labelPlural: '', counter: 'hierarchical', sep: '' },\n table: { label: '', labelPlural: '', counter: 'arabic', sep: '' },\n}\n\nexport const XREF_STYLES = {\n humanities: HUMANITIES,\n engineering: ENGINEERING,\n german: GERMAN,\n plain: PLAIN,\n}\n\nexport const DEFAULT_XREF_STYLE = 'humanities'\n\n/**\n * Resolve the active xref-style for a document. Reads:\n * - `book.xrefStyle:` in document config (preset name).\n * - `book.xref.<kind>:` per-kind overrides on top of the preset.\n * - Foundation-declared kinds (`foundation.xref.kinds`) extend the\n * preset with whatever the foundation provides.\n *\n * Returns a plain map keyed by kind name; per-kind values are merged\n * objects (preset + foundation extensions + document overrides).\n */\nexport function resolveXrefStyle(presetName, config) {\n const preset = XREF_STYLES[presetName] || XREF_STYLES[DEFAULT_XREF_STYLE]\n const merged = { ...preset }\n\n // Foundation extensions: kinds declared by the foundation (e.g.\n // theorem, lemma, proof for a math foundation).\n const foundationKinds = globalThis.uniweb?.foundationConfig?.xref?.kinds\n if (foundationKinds && typeof foundationKinds === 'object') {\n for (const [kind, meta] of Object.entries(foundationKinds)) {\n merged[kind] = { ...merged[kind], ...meta }\n }\n }\n\n // Document overrides: `book.xref.<kind>:` granular tweaks.\n const docOverrides = config?.book?.xref || config?.xref || null\n if (docOverrides && typeof docOverrides === 'object') {\n for (const [kind, meta] of Object.entries(docOverrides)) {\n if (meta && typeof meta === 'object') {\n merged[kind] = { ...merged[kind], ...meta }\n }\n }\n }\n\n return merged\n}\n\nexport function getKindMeta(style, kind) {\n return style?.[kind] || null\n}\n","/**\n * Cross-reference renderer. The framework registers `<Ref>` as a default\n * inset component so the `[#id]` author syntax (recognized by\n * content-reader) compiles to `inset_ref { component: 'Ref', key: <id> }`\n * and falls through to this renderer regardless of which foundation is\n * active.\n *\n * Resolution flow at render time:\n * 1. Read `block.website.xref.entries[id]` — the per-document\n * registry the framework populated from {#id} attributes.\n * 2. Pick the active xref-style preset (foundation default + document\n * override) and the kind metadata for the registered entry.\n * 3. Render via the style's label + counter + locator.\n *\n * Multi-ref clusters (`[#a;#b]`) split on `;` and render same-kind\n * groups using the style's `labelPlural`. Mixed-kind clusters fall\n * back to comma-separated singular rendering with a console warning.\n *\n * Missing ids render `[?<id-with-typo>]` so the failing key is visible\n * in the output — easier to debug than the bare `[?]` we use for\n * missing cite keys.\n *\n * @module @uniweb/kit/Ref\n */\n\nimport React from 'react'\nimport { resolveXrefStyle, getKindMeta } from '../xref-styles.js'\n\nfunction splitKeys(raw) {\n return String(raw || '')\n .split(';')\n .map((k) => k.trim().replace(/^#/, ''))\n .filter(Boolean)\n}\n\nfunction formatLocator(params) {\n const { page, locator, label = 'page' } = params || {}\n const value = page || locator\n if (!value) return ''\n const labels = {\n page: 'p.',\n chapter: 'chap.',\n section: '§',\n paragraph: '¶',\n }\n const lab = labels[label] || `${label}.`\n return ` (${lab} ${value})`\n}\n\nfunction renderEntry(entry, kindMeta) {\n // Bare \"Figure 3\" / \"§3.2\" — label + separator + counter.\n const label = kindMeta?.label || ''\n const sep = kindMeta?.sep ?? ' '\n const counter = entry.counterText\n return label ? `${label}${sep}${counter}` : counter\n}\n\nfunction renderGroupSameKind(entries, kindMeta) {\n if (entries.length === 1) {\n return renderEntry(entries[0], kindMeta)\n }\n // Plural label + comma-separated counters with \" and \" before the last.\n const label = kindMeta?.labelPlural || kindMeta?.label || ''\n const sep = kindMeta?.sep ?? ' '\n const counters = entries.map((e) => e.counterText)\n let body\n if (counters.length === 2) {\n body = counters.join(' and ')\n } else {\n body = counters.slice(0, -1).join(', ') + ', and ' + counters[counters.length - 1]\n }\n return label ? `${label}${sep}${body}` : body\n}\n\nexport function Ref({ params, block }) {\n const website = block?.website\n const xref = website?.xref || website?.config?.xref || null\n const entries = xref?.entries || {}\n const styleName = website?.config?.book?.xrefStyle || 'humanities'\n const style = resolveXrefStyle(styleName, website?.config)\n\n const ids = splitKeys(params?.key)\n if (ids.length === 0) {\n return <span className=\"xref xref--missing\" title=\"No id\">[?]</span>\n }\n\n const resolved = ids.map((id) => {\n const entry = entries[id]\n return entry ? { id, entry, kindMeta: getKindMeta(style, entry.kind) } : { id, missing: true }\n })\n\n // Single missing-id fast path — rich placeholder with the failing key.\n if (resolved.length === 1 && resolved[0].missing) {\n return (\n <span className=\"xref xref--missing\" title={`Missing label: ${resolved[0].id}`}>\n [?{resolved[0].id}]\n </span>\n )\n }\n\n // Group consecutive entries by kind. Mixed-kind clusters fall back to\n // comma-separated singular rendering.\n const allKinds = resolved.filter((r) => !r.missing).map((r) => r.entry.kind)\n const sameKind = allKinds.every((k) => k === allKinds[0])\n\n const locator = formatLocator(params)\n\n if (!sameKind) {\n if (typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.warn(\n `[xref] mixed-kind cluster (${[...new Set(allKinds)].join(', ')}) — falling back to comma-separated rendering`,\n )\n }\n const parts = resolved.map((r) =>\n r.missing\n ? `[?${r.id}]`\n : renderEntry(r.entry, r.kindMeta),\n )\n return <span className=\"xref\">{parts.join(', ')}{locator}</span>\n }\n\n // Same-kind path — group, render with labelPlural when multiple.\n const onlyResolved = resolved.filter((r) => !r.missing)\n const text = onlyResolved.length > 0\n ? renderGroupSameKind(\n onlyResolved.map((r) => r.entry),\n onlyResolved[0].kindMeta,\n )\n : ''\n\n // Append placeholders for any missing keys at the end of the cluster.\n const missingTail = resolved.filter((r) => r.missing).map((r) => `[?${r.id}]`).join(', ')\n const body = [text, missingTail].filter(Boolean).join(', ')\n\n return <span className=\"xref\">{body}{locator}</span>\n}\n\nexport default Ref\n","/**\n * Default 404 Page Content\n *\n * Single source of truth for the fallback 404 page shown when a site\n * has no custom 404 page defined. Used by:\n * - PageRenderer.jsx (client-side, as React elements)\n * - ssr-renderer.js generate404Html (build-time, as HTML string)\n *\n * The wrapper uses min-height + flex centering so the 404 content\n * renders at the same position regardless of parent layout context.\n * This prevents a visible flash when React hydrates over the SSR content.\n */\n\nimport React from 'react'\n\nconst styles = {\n wrapper: {\n minHeight: '80vh',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '2rem',\n textAlign: 'center',\n },\n heading: { fontSize: '3rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' },\n message: { color: '#64748b', marginBottom: '2rem' },\n link: { color: '#3b82f6', textDecoration: 'underline' },\n}\n\n/**\n * React element for client-side rendering (PageRenderer).\n * Reads basePath from the runtime so the homepage link works\n * in subdirectory deployments (e.g., /sites/testproject).\n */\nexport function Default404() {\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n const homeHref = basePath ? `${basePath}/` : '/'\n return React.createElement('div', { className: 'page-not-found', style: styles.wrapper },\n React.createElement('h1', { style: styles.heading }, '404'),\n React.createElement('p', { style: styles.message }, 'Page not found'),\n React.createElement('a', { href: homeHref, style: styles.link }, 'Go to homepage')\n )\n}\n\n/**\n * Static HTML string for SSR injection (generate404Html).\n *\n * @param {string} [basePath] - Base path prefix for the homepage link (e.g., '/sites/testproject')\n */\nexport function default404Html(basePath = '') {\n const homeHref = basePath ? `${basePath}/` : '/'\n return (\n `<div class=\"page-not-found\" style=\"min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center\">` +\n `<h1 style=\"font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem\">404</h1>` +\n `<p style=\"color:#64748b;margin-bottom:2rem\">Page not found</p>` +\n `<a href=\"${homeHref}\" style=\"color:#3b82f6;text-decoration:underline\">Go to homepage</a>` +\n `</div>`\n )\n}\n","/**\n * SSR Renderer\n *\n * Hook-free rendering pipeline for SSG (build) and cloud SSR (unicloud).\n * Mirrors BlockRenderer.jsx + Background.jsx using React.createElement\n * directly — no hooks, no JSX, no browser APIs.\n *\n * This is the single source of truth for how blocks render during prerender.\n * When modifying BlockRenderer.jsx or Background.jsx, update this file to match.\n *\n * Exports three layers:\n * 1. Rendering functions (renderBlock, renderBlocks, renderLayout, renderBackground)\n * 2. Initialization (initPrerender, prefetchIcons)\n * 3. Per-page rendering (renderPage, classifyRenderError, injectPageContent, escapeHtml)\n */\n\nimport React from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { createUniweb } from '@uniweb/core'\nimport { Ref as KitRef } from './Ref/index.js'\nimport { buildSectionOverrides } from '@uniweb/theming'\nimport { prepareProps, getComponentMeta } from './prepare-props.js'\nimport { default404Html } from './default-404.js'\n\n// ============================================================================\n// Layer 1: Rendering functions\n// ============================================================================\n\n/**\n * Valid color contexts for section theming\n */\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n/**\n * Build wrapper props from block configuration.\n * Mirrors getWrapperProps in BlockRenderer.jsx.\n */\nexport function getWrapperProps(block) {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n // Empty themeName = Auto → no context class → inherits tokens from :root\n // Non-empty = Pinned → context class sets tokens directly on the element\n let contextClass = ''\n if (theme && VALID_CONTEXTS.includes(theme)) {\n contextClass = `context-${theme}`\n }\n\n let className = contextClass\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {} } = block.standardOptions\n const style = {}\n\n // If background has content, ensure relative positioning and a stacking context\n // so the background's z-index stays contained within this section.\n if (background.mode) {\n style.position = 'relative'\n style.isolation = 'isolate'\n }\n\n // Apply context overrides as inline CSS custom properties\n if (block.contextOverrides) {\n for (const [key, value] of Object.entries(block.contextOverrides)) {\n style[`--${key}`] = value\n }\n }\n\n // Use stableId for DOM ID if available (stable across reordering)\n const sectionId = block.stableId || block.id\n\n return { id: `section-${sectionId}`, style, className, background }\n}\n\n/**\n * Convert hex/rgb color to rgba with opacity.\n * Mirrors withOpacity() in Background.jsx.\n */\nfunction withOpacity(color, opacity) {\n if (color.startsWith('#')) {\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n return `rgba(${r}, ${g}, ${b}, ${opacity})`\n }\n if (color.startsWith('rgb')) {\n const match = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (match) {\n return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`\n }\n }\n return color\n}\n\n/**\n * Resolve a URL against the site's base path.\n * Mirrors resolveUrl() in Background.jsx.\n */\nfunction resolveUrl(url) {\n if (!url || !url.startsWith('/')) return url\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n if (!basePath) return url\n if (url.startsWith(basePath + '/') || url === basePath) return url\n return basePath + url\n}\n\n/**\n * Render a background element for SSR.\n * Mirrors Background.jsx (color, gradient, image — not video).\n * Video backgrounds require JS for autoplay and are skipped during SSR.\n */\nexport function renderBackground(background) {\n if (!background?.mode) return null\n\n const containerStyle = {\n position: 'absolute',\n inset: '0',\n overflow: 'hidden',\n zIndex: 0,\n }\n\n const children = []\n\n // Color background\n if (background.mode === 'color' && background.color) {\n children.push(\n React.createElement('div', {\n key: 'bg-color',\n className: 'background-color',\n style: { position: 'absolute', inset: '0', backgroundColor: background.color },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Gradient background (supports string or object with opacity)\n if (background.mode === 'gradient' && background.gradient) {\n const g = background.gradient\n\n let bgValue\n if (typeof g === 'string') {\n bgValue = g\n } else {\n const {\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 1,\n endOpacity = 1,\n } = g\n const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start\n const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end\n bgValue = `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-gradient',\n className: 'background-gradient',\n style: { position: 'absolute', inset: '0', background: bgValue },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Image background\n if (background.mode === 'image' && background.image?.src) {\n const img = background.image\n children.push(\n React.createElement('div', {\n key: 'bg-image',\n className: 'background-image',\n style: {\n position: 'absolute',\n inset: '0',\n backgroundImage: `url(${resolveUrl(img.src)})`,\n backgroundPosition: img.position || 'center',\n backgroundSize: img.size || 'cover',\n backgroundRepeat: 'no-repeat',\n },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Overlay (gradient or solid)\n if (background.overlay?.enabled) {\n const ov = background.overlay\n let overlayStyle\n\n if (ov.gradient) {\n const g = ov.gradient\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n background: `linear-gradient(${g.angle || 180}deg, ${g.start || 'rgba(0,0,0,0.7)'} ${g.startPosition || 0}%, ${g.end || 'rgba(0,0,0,0)'} ${g.endPosition || 100}%)`,\n opacity: ov.opacity ?? 0.5,\n }\n } else {\n const baseColor = ov.type === 'light' ? '255, 255, 255' : '0, 0, 0'\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n backgroundColor: `rgba(${baseColor}, ${ov.opacity ?? 0.5})`,\n }\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-overlay',\n className: ov.gradient ? 'background-overlay background-overlay--gradient' : 'background-overlay background-overlay--solid',\n style: overlayStyle,\n 'aria-hidden': 'true',\n })\n )\n }\n\n if (children.length === 0) return null\n\n return React.createElement('div', {\n className: `background background--${background.mode}`,\n style: containerStyle,\n 'aria-hidden': 'true',\n }, ...children)\n}\n\n/**\n * Render a single block for SSR.\n * Mirrors BlockRenderer.jsx but without hooks (no runtime data fetching).\n *\n * Two modes (mirrors client BlockRenderer):\n * - Bare (as=null/false): component only, no wrapper\n * - Section (as='section'/'div'/etc.): full treatment with wrapper, context, background\n *\n * @param {Block} block - Block instance to render\n * @param {Object} [options]\n * @param {string|null} [options.as='section'] - Wrapper element tag, or null/false for bare mode\n * @returns {React.ReactElement}\n */\nexport function renderBlock(block, { as = 'section' } = {}) {\n const Component = block.initComponent()\n\n if (!Component) {\n return React.createElement('div', {\n className: 'block-error',\n style: { padding: '1rem', background: '#fef2f2', color: '#dc2626' },\n }, `Component not found: ${block.type}`)\n }\n\n // Resolve inherited entity data synchronously (SSG has no async).\n // EntityStore walks page/site hierarchy to find data matching meta.inheritData.\n const meta = getComponentMeta(block.type)\n const entityStore = block.website?.entityStore\n let entityData = null\n if (entityStore) {\n const resolved = entityStore.resolve(block, meta)\n if (resolved.status === 'ready') entityData = resolved.data\n }\n\n // Build content and params with runtime guarantees.\n // prepareProps handles the full pipeline: entity data merge,\n // foundation content handler invocation, guaranteed content\n // structure, schema application, and param defaults.\n // See prepare-props.js for the pipeline details.\n const prepared = prepareProps(block, meta, entityData)\n const params = prepared.params\n const content = { ...prepared.content, ...block.properties }\n\n const componentProps = { content, params, block }\n\n // Bare mode: component only, no wrapper or section chrome.\n // Used by ChildBlocks for grid cells, tab panels, inline children, insets.\n if (!as) {\n return React.createElement(Component, componentProps)\n }\n\n // Section mode: full treatment with wrapper, context classes, background.\n const { background, ...wrapperProps } = getWrapperProps(block)\n\n // Merge Component.className (static classes declared on the component function)\n const componentClassName = Component.className\n if (componentClassName) {\n wrapperProps.className = wrapperProps.className\n ? `${wrapperProps.className} ${componentClassName}`\n : componentClassName\n }\n\n // Check if component handles its own background\n const hasBackground = background?.mode && meta?.background !== 'self'\n block.hasBackground = hasBackground\n\n // Determine wrapper element:\n // - Explicit as (not 'section') → use as prop directly\n // - Component.as → use component's declared tag (e.g., Header.as = 'header')\n // - fallback → 'section'\n const wrapperTag = as !== 'section' ? as : (Component.as || 'section')\n\n if (hasBackground) {\n return React.createElement(wrapperTag, wrapperProps,\n renderBackground(background),\n React.createElement('div', { style: { position: 'relative', zIndex: 10 } },\n React.createElement(Component, componentProps)\n )\n )\n }\n\n return React.createElement(wrapperTag, wrapperProps,\n React.createElement(Component, componentProps)\n )\n}\n\n/**\n * Render an array of blocks for SSR.\n */\nexport function renderBlocks(blocks) {\n if (!blocks || blocks.length === 0) return null\n return blocks.map((block, index) =>\n React.createElement(React.Fragment, { key: block.id || index },\n renderBlock(block)\n )\n )\n}\n\n/**\n * Render page layout for SSR.\n * Mirrors Layout.jsx but without hooks.\n */\nexport function renderLayout(page, website) {\n const layoutName = page.getLayoutName()\n const RemoteLayout = website.getRemoteLayout(layoutName)\n const layoutMeta = website.getLayoutMeta(layoutName)\n\n const bodyBlocks = page.getBodyBlocks()\n const areas = page.getLayoutAreas()\n\n const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null\n const areaElements = {}\n for (const [name, blocks] of Object.entries(areas)) {\n areaElements[name] = renderBlocks(blocks)\n }\n\n if (RemoteLayout) {\n const params = { ...(layoutMeta?.defaults || {}), ...(page.getLayoutParams() || {}) }\n return React.createElement(RemoteLayout, {\n page, website, params,\n body: bodyElement,\n ...areaElements,\n })\n }\n\n // Default layout\n return React.createElement(React.Fragment, null,\n areaElements.header && React.createElement('header', null, areaElements.header),\n bodyElement && React.createElement('main', null, bodyElement),\n areaElements.footer && React.createElement('footer', null, areaElements.footer)\n )\n}\n\n// ============================================================================\n// Layer 2: Initialization\n// ============================================================================\n\n/**\n * Create and configure the Uniweb runtime for prerendering.\n *\n * Handles the full initialization sequence in the correct order:\n * createUniweb → setFoundation → capabilities → layoutMeta → basePath → childBlockRenderer.\n *\n * Returns the configured uniweb instance. Consumers can add extras after:\n * - Build: pre-populate DataStore, load extensions\n * - Unicloud: (none needed — payload is complete)\n *\n * NOTE: Does NOT clone content. Cloning is the consumer's responsibility\n * (build modifies content before init; unicloud clones upfront).\n *\n * @param {Object} content - Site content JSON (pages, config, hierarchy)\n * @param {Object} foundation - Loaded foundation module\n * @param {Object} [options]\n * @param {function} [options.onProgress] - Progress callback\n * @returns {Object} Configured uniweb instance\n */\nexport function initPrerender(content, foundation, extensionsOrOptions, maybeOptions) {\n // Backwards-compatible arg shape: (content, foundation, options) or\n // (content, foundation, extensions, options). Extensions must be passed at\n // construction so the Website's FetcherDispatcher sees their routes.\n let extensions = []\n let options = {}\n if (Array.isArray(extensionsOrOptions)) {\n extensions = extensionsOrOptions\n options = maybeOptions || {}\n } else {\n options = extensionsOrOptions || {}\n }\n const { onProgress = () => {} } = options\n\n onProgress('Initializing runtime...')\n // Uniweb constructor wires foundation, capabilities, layoutMeta, handlers,\n // and extensions at construction time — see framework/core/src/uniweb.js.\n const uniweb = createUniweb(content, foundation, extensions)\n\n // Set base path from site config for subdirectory deployments\n if (content.config?.base && uniweb.activeWebsite?.setBasePath) {\n uniweb.activeWebsite.setBasePath(content.config.base)\n }\n\n // Set childBlockRenderer so ChildBlocks/Visual/Render work during prerender.\n // Mirrors the client's ChildBlocks component in PageRenderer.jsx:\n // - default bare rendering (no wrapAs) — component only, no wrapper\n // - pass wrapAs to opt into full section treatment\n uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, wrapAs }) {\n const blockList = blocks || from?.childBlocks || []\n return blockList.map((childBlock, index) =>\n React.createElement(React.Fragment, { key: childBlock.id || index },\n renderBlock(childBlock, { as: wrapAs || null })\n )\n )\n }\n\n // Framework-level default insets — components every foundation gets\n // for free. <Ref> resolves the `[#id]` cross-reference syntax against\n // the per-document xref-registry. The lookup chain in\n // `getComponent(name)` (core/uniweb.js) checks the foundation first,\n // then extensions, then this map.\n uniweb.defaultInsets = { Ref: KitRef }\n\n // Register SSR-safe routing so useRouting()/useActiveRoute() work during prerender.\n // renderPage() calls website.setActivePage() before rendering each page,\n // so activePage.route always reflects the page being rendered.\n const website = uniweb.activeWebsite\n uniweb.routingComponents = {\n useLocation: () => {\n const route = website?.activePage?.route || ''\n return { pathname: '/' + route, search: '', hash: '', state: null, key: 'default' }\n },\n useParams: () => ({}),\n useNavigate: () => () => {},\n }\n\n return uniweb\n}\n\n/**\n * Pre-fetch icons from CDN and populate the Uniweb icon cache.\n * Stores the cache on siteContent._iconCache for embedding in HTML.\n *\n * @param {Object} siteContent - Site content JSON (mutated: _iconCache added)\n * @param {Object} uniweb - Configured uniweb instance\n * @param {function} [onProgress] - Progress callback\n */\nexport async function prefetchIcons(siteContent, uniweb, onProgress = () => {}) {\n const icons = siteContent.icons?.used || []\n if (icons.length === 0) return\n\n const cdnBase = siteContent.config?.icons?.cdnUrl || 'https://uniweb.github.io/icons'\n\n onProgress(`Fetching ${icons.length} icons for SSR...`)\n\n const results = await Promise.allSettled(\n icons.map(async (iconRef) => {\n const [family, name] = iconRef.split(':')\n const url = `${cdnBase}/${family}/${family}-${name}.svg`\n const response = await fetch(url)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const svg = await response.text()\n uniweb.iconCache.set(`${family}:${name}`, svg)\n })\n )\n\n const succeeded = results.filter(r => r.status === 'fulfilled').length\n const failed = results.filter(r => r.status === 'rejected').length\n if (failed > 0) {\n const msg = `Fetched ${succeeded}/${icons.length} icons (${failed} failed)`\n console.warn(`[prerender] ${msg}`)\n onProgress(` ${msg}`)\n }\n\n // Store icon cache on siteContent for embedding in HTML\n if (uniweb.iconCache.size > 0) {\n siteContent._iconCache = Object.fromEntries(uniweb.iconCache)\n }\n}\n\n// ============================================================================\n// Layer 3: Per-page rendering\n// ============================================================================\n\n/**\n * Classify an SSR rendering error.\n *\n * @param {Error} err\n * @returns {{ type: 'hooks'|'null-component'|'unknown', message: string }}\n */\nexport function classifyRenderError(err) {\n const msg = err.message || ''\n\n if (msg.includes('Invalid hook call') || msg.includes('useState') || msg.includes('useEffect')) {\n return {\n type: 'hooks',\n message: 'contains components with React hooks (renders client-side)',\n }\n }\n\n if (msg.includes('Element type is invalid') && msg.includes('null')) {\n return {\n type: 'null-component',\n message: 'a component resolved to null (often hook-related, renders client-side)',\n }\n }\n\n return {\n type: 'unknown',\n message: msg,\n }\n}\n\n/**\n * Render a single page to HTML.\n *\n * Handles the full per-page pipeline:\n * setActivePage → renderLayout → renderToString → error handling → section override CSS.\n *\n * @param {Page} page - Page instance to render\n * @param {Website} website - Website instance\n * @returns {{ renderedContent: string, sectionOverrideCSS: string } | { error: { type: string, message: string } }}\n */\nexport function renderPage(page, website) {\n website.setActivePage(page.route)\n\n const element = renderLayout(page, website)\n\n let renderedContent\n try {\n renderedContent = renderToString(element)\n } catch (err) {\n return { error: classifyRenderError(err) }\n }\n\n // Build per-page section override CSS (theme pinning, component vars)\n const appearance = website.themeData?.appearance\n const sectionOverrideCSS = buildSectionOverrides(page.getPageBlocks(), appearance)\n\n return { renderedContent, sectionOverrideCSS }\n}\n\n// ============================================================================\n// HTML injection\n// ============================================================================\n\n/**\n * Escape HTML special characters.\n */\nexport function escapeHtml(str) {\n if (!str) return ''\n return String(str)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/**\n * Inject prerendered content into an HTML shell.\n *\n * Common operations shared by both build and cloud:\n * - Replace #root div with rendered HTML\n * - Update page title\n * - Add/update meta description\n * - Inject section override CSS\n *\n * Build layers its additional injections on top of this return value:\n * __SITE_CONTENT__ JSON, icon cache, theme CSS (build-specific).\n *\n * @param {string} html - HTML shell\n * @param {string} renderedContent - React renderToString output\n * @param {Object} page - Page data { title, description, route }\n * @param {Object} [options]\n * @param {string} [options.sectionOverrideCSS] - Per-page section override CSS\n * @returns {string} HTML with injected content\n */\nexport function injectPageContent(html, renderedContent, page, options = {}) {\n let result = html\n\n // Inject per-page section override CSS before </head>\n if (options.sectionOverrideCSS) {\n const overrideStyle = `<style id=\"uniweb-page-overrides\">\\n${options.sectionOverrideCSS}\\n</style>`\n result = result.replace('</head>', `${overrideStyle}\\n</head>`)\n }\n\n // Replace the empty root div with pre-rendered content\n result = result.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${renderedContent}</div>`\n )\n\n // Update page title (use getTitle() so isIndex pages inherit parent title)\n const pageTitle = page.getTitle?.() || page.title\n if (pageTitle) {\n result = result.replace(\n /<title>.*?<\\/title>/,\n `<title>${escapeHtml(pageTitle)}</title>`\n )\n }\n\n // Add/update meta description\n if (page.description) {\n const metaDesc = `<meta name=\"description\" content=\"${escapeHtml(page.description)}\">`\n if (result.includes('<meta name=\"description\"')) {\n result = result.replace(/<meta name=\"description\"[^>]*>/, metaDesc)\n } else {\n result = result.replace('</head>', `${metaDesc}\\n</head>`)\n }\n }\n\n return result\n}\n\n// ============================================================================\n// 404 fallback generation\n// ============================================================================\n\n/**\n * Generate 404.html content for static hosting fallback.\n *\n * Serves two purposes on static hosts (GitHub Pages, Cloudflare Pages, etc.):\n * 1. Real 404: pre-rendered custom 404 page content (or blank #root if none defined)\n * 2. Valid dynamic route (e.g. /blog/2): inline script clears #root so SPA renders fresh\n *\n * Flow: static host serves 404.html → inline script runs before React mounts →\n * - dynamic route: clears #root, React renders the page normally\n * - real 404: leaves #root with pre-rendered content, React re-renders same 404 page\n *\n * @param {Object} options\n * @param {string} options.baseHtml - Assembled HTML shell (with site content already injected)\n * @param {Object} options.website - Initialized Website instance (from initPrerender)\n * @param {Object} options.siteContent - Site content object (to find dynamic templates)\n * @returns {{ html: string, hasNotFoundPage: boolean }}\n */\nexport function generate404Html({ baseHtml, website, siteContent }) {\n // Extract patterns for routes that remain as dynamic templates (prerender: false)\n const dynamicTemplates = siteContent.pages?.filter((p) => p.isDynamic) || []\n const routePatterns = dynamicTemplates.map((p) => {\n // '/blog/:id' → /^\\/blog\\/[^\\/]+\\/?$/\n const escaped = p.route\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:[^/]+/g, '[^\\\\/]+')\n return `^${escaped}\\\\/?$`\n })\n\n let html = baseHtml\n\n // Pre-render the custom 404 page content into #root (if the site defines one),\n // otherwise inject a default 404 message so the page isn't blank before JS loads\n const notFoundPage = website.getNotFoundPage()\n if (notFoundPage) {\n const notFoundResult = renderPage(notFoundPage, website)\n if (notFoundResult && !notFoundResult.error) {\n html = injectPageContent(html, notFoundResult.renderedContent, notFoundPage, {\n sectionOverrideCSS: notFoundResult.sectionOverrideCSS,\n })\n }\n } else {\n const basePath = website.basePath || ''\n html = html.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${default404Html(basePath)}</div>`\n )\n }\n\n // Inject inline script: if path matches a dynamic route, clear #root before React mounts\n // so the SPA renders the correct page rather than the 404 content\n if (routePatterns.length > 0) {\n const patternList = routePatterns.map((p) => `/${p}/`).join(',')\n const dynamicScript =\n `<script>(function(){` +\n `var p=[${patternList}],r=window.location.pathname;` +\n `if(p.some(function(x){return x.test(r)})){` +\n `var el=document.getElementById('root');if(el)el.innerHTML='';` +\n `}})()</script>`\n html = html.replace('</body>', `${dynamicScript}\\n</body>`)\n }\n\n return { html, hasNotFoundPage: !!notFoundPage }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -35,14 +35,14 @@
35
35
  "node": ">=20.19"
36
36
  },
37
37
  "dependencies": {
38
- "@uniweb/core": "0.7.3",
38
+ "@uniweb/core": "0.7.5",
39
39
  "@uniweb/theming": "0.1.3"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@vitejs/plugin-react": "^4.5.2",
43
43
  "vite": "^7.3.1",
44
44
  "vitest": "^2.0.0",
45
- "@uniweb/build": "0.11.1"
45
+ "@uniweb/build": "0.11.3"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "react": "^18.0.0 || ^19.0.0",
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Cross-reference renderer. The framework registers `<Ref>` as a default
3
+ * inset component so the `[#id]` author syntax (recognized by
4
+ * content-reader) compiles to `inset_ref { component: 'Ref', key: <id> }`
5
+ * and falls through to this renderer regardless of which foundation is
6
+ * active.
7
+ *
8
+ * Resolution flow at render time:
9
+ * 1. Read `block.website.xref.entries[id]` — the per-document
10
+ * registry the framework populated from {#id} attributes.
11
+ * 2. Pick the active xref-style preset (foundation default + document
12
+ * override) and the kind metadata for the registered entry.
13
+ * 3. Render via the style's label + counter + locator.
14
+ *
15
+ * Multi-ref clusters (`[#a;#b]`) split on `;` and render same-kind
16
+ * groups using the style's `labelPlural`. Mixed-kind clusters fall
17
+ * back to comma-separated singular rendering with a console warning.
18
+ *
19
+ * Missing ids render `[?<id-with-typo>]` so the failing key is visible
20
+ * in the output — easier to debug than the bare `[?]` we use for
21
+ * missing cite keys.
22
+ *
23
+ * @module @uniweb/kit/Ref
24
+ */
25
+
26
+ import React from 'react'
27
+ import { resolveXrefStyle, getKindMeta } from '../xref-styles.js'
28
+
29
+ function splitKeys(raw) {
30
+ return String(raw || '')
31
+ .split(';')
32
+ .map((k) => k.trim().replace(/^#/, ''))
33
+ .filter(Boolean)
34
+ }
35
+
36
+ function formatLocator(params) {
37
+ const { page, locator, label = 'page' } = params || {}
38
+ const value = page || locator
39
+ if (!value) return ''
40
+ const labels = {
41
+ page: 'p.',
42
+ chapter: 'chap.',
43
+ section: '§',
44
+ paragraph: '¶',
45
+ }
46
+ const lab = labels[label] || `${label}.`
47
+ return ` (${lab} ${value})`
48
+ }
49
+
50
+ function renderEntry(entry, kindMeta) {
51
+ // Bare "Figure 3" / "§3.2" — label + separator + counter.
52
+ const label = kindMeta?.label || ''
53
+ const sep = kindMeta?.sep ?? ' '
54
+ const counter = entry.counterText
55
+ return label ? `${label}${sep}${counter}` : counter
56
+ }
57
+
58
+ function renderGroupSameKind(entries, kindMeta) {
59
+ if (entries.length === 1) {
60
+ return renderEntry(entries[0], kindMeta)
61
+ }
62
+ // Plural label + comma-separated counters with " and " before the last.
63
+ const label = kindMeta?.labelPlural || kindMeta?.label || ''
64
+ const sep = kindMeta?.sep ?? ' '
65
+ const counters = entries.map((e) => e.counterText)
66
+ let body
67
+ if (counters.length === 2) {
68
+ body = counters.join(' and ')
69
+ } else {
70
+ body = counters.slice(0, -1).join(', ') + ', and ' + counters[counters.length - 1]
71
+ }
72
+ return label ? `${label}${sep}${body}` : body
73
+ }
74
+
75
+ export function Ref({ params, block }) {
76
+ const website = block?.website
77
+ const xref = website?.xref || website?.config?.xref || null
78
+ const entries = xref?.entries || {}
79
+ const styleName = website?.config?.book?.xrefStyle || 'humanities'
80
+ const style = resolveXrefStyle(styleName, website?.config)
81
+
82
+ const ids = splitKeys(params?.key)
83
+ if (ids.length === 0) {
84
+ return <span className="xref xref--missing" title="No id">[?]</span>
85
+ }
86
+
87
+ const resolved = ids.map((id) => {
88
+ const entry = entries[id]
89
+ return entry ? { id, entry, kindMeta: getKindMeta(style, entry.kind) } : { id, missing: true }
90
+ })
91
+
92
+ // Single missing-id fast path — rich placeholder with the failing key.
93
+ if (resolved.length === 1 && resolved[0].missing) {
94
+ return (
95
+ <span className="xref xref--missing" title={`Missing label: ${resolved[0].id}`}>
96
+ [?{resolved[0].id}]
97
+ </span>
98
+ )
99
+ }
100
+
101
+ // Group consecutive entries by kind. Mixed-kind clusters fall back to
102
+ // comma-separated singular rendering.
103
+ const allKinds = resolved.filter((r) => !r.missing).map((r) => r.entry.kind)
104
+ const sameKind = allKinds.every((k) => k === allKinds[0])
105
+
106
+ const locator = formatLocator(params)
107
+
108
+ if (!sameKind) {
109
+ if (typeof console !== 'undefined') {
110
+ // eslint-disable-next-line no-console
111
+ console.warn(
112
+ `[xref] mixed-kind cluster (${[...new Set(allKinds)].join(', ')}) — falling back to comma-separated rendering`,
113
+ )
114
+ }
115
+ const parts = resolved.map((r) =>
116
+ r.missing
117
+ ? `[?${r.id}]`
118
+ : renderEntry(r.entry, r.kindMeta),
119
+ )
120
+ return <span className="xref">{parts.join(', ')}{locator}</span>
121
+ }
122
+
123
+ // Same-kind path — group, render with labelPlural when multiple.
124
+ const onlyResolved = resolved.filter((r) => !r.missing)
125
+ const text = onlyResolved.length > 0
126
+ ? renderGroupSameKind(
127
+ onlyResolved.map((r) => r.entry),
128
+ onlyResolved[0].kindMeta,
129
+ )
130
+ : ''
131
+
132
+ // Append placeholders for any missing keys at the end of the cluster.
133
+ const missingTail = resolved.filter((r) => r.missing).map((r) => `[?${r.id}]`).join(', ')
134
+ const body = [text, missingTail].filter(Boolean).join(', ')
135
+
136
+ return <span className="xref">{body}{locator}</span>
137
+ }
138
+
139
+ export default Ref
@@ -0,0 +1,2 @@
1
+ export { Ref } from './Ref.jsx'
2
+ export { default } from './Ref.jsx'
package/src/setup.js CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  } from 'react-router-dom'
29
29
 
30
30
  import { ChildBlocks } from './components/PageRenderer.jsx'
31
+ import { Ref as KitRef } from './Ref/index.js'
31
32
 
32
33
  // ─── View Transition Wrappers ───────────────────────────────────────────────
33
34
  //
@@ -254,6 +255,7 @@ export function initUniweb({ content, foundation, extensions = [], routingCompon
254
255
  }
255
256
 
256
257
  uniweb.childBlockRenderer = ChildBlocks
258
+ uniweb.defaultInsets = { ...(uniweb.defaultInsets || {}), Ref: KitRef }
257
259
  uniweb.routingComponents = routingComponents
258
260
  uniweb.iconResolver = createIconResolver(content?.icons)
259
261
 
@@ -17,6 +17,7 @@
17
17
  import React from 'react'
18
18
  import { renderToString } from 'react-dom/server'
19
19
  import { createUniweb } from '@uniweb/core'
20
+ import { Ref as KitRef } from './Ref/index.js'
20
21
  import { buildSectionOverrides } from '@uniweb/theming'
21
22
  import { prepareProps, getComponentMeta } from './prepare-props.js'
22
23
  import { default404Html } from './default-404.js'
@@ -417,6 +418,13 @@ export function initPrerender(content, foundation, extensionsOrOptions, maybeOpt
417
418
  )
418
419
  }
419
420
 
421
+ // Framework-level default insets — components every foundation gets
422
+ // for free. <Ref> resolves the `[#id]` cross-reference syntax against
423
+ // the per-document xref-registry. The lookup chain in
424
+ // `getComponent(name)` (core/uniweb.js) checks the foundation first,
425
+ // then extensions, then this map.
426
+ uniweb.defaultInsets = { Ref: KitRef }
427
+
420
428
  // Register SSR-safe routing so useRouting()/useActiveRoute() work during prerender.
421
429
  // renderPage() calls website.setActivePage() before rendering each page,
422
430
  // so activePage.route always reflects the page being rendered.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Cross-reference style catalog.
3
+ *
4
+ * Cross-references are framework-managed (parallel to citestyle's nine
5
+ * citation styles, but framework-internal because there's no CSL-for-
6
+ * xrefs standard). Foundations pick a preset; documents may override
7
+ * per-kind.
8
+ *
9
+ * Each preset is a map from kind name to:
10
+ * {
11
+ * label, // singular label, e.g. "Figure"
12
+ * labelPlural, // plural label, e.g. "Figures"
13
+ * counter, // 'arabic' | 'hierarchical' | null (no counter)
14
+ * sep, // separator between label and counter ("" | " ")
15
+ * }
16
+ *
17
+ * The catalog ships four presets:
18
+ * - humanities (default — "Figure 3", "§3.2", "Equation 1")
19
+ * - engineering ("Fig. 3", "Sec. 3.2", "Eq. 1")
20
+ * - german ("Abb. 3", "Abschn. 3.2", "Gl. 1")
21
+ * - plain (counters only — "3", "3.2", "1")
22
+ *
23
+ * Foundations override the preset by declaring `xref.kinds` in
24
+ * foundation.js (additive: new kinds and overrides for existing kinds).
25
+ * Documents override per-kind via `book.xref.<kind>:` in document.yml.
26
+ */
27
+
28
+ const HUMANITIES = {
29
+ figure: { label: 'Figure', labelPlural: 'Figures', counter: 'arabic', sep: ' ' },
30
+ equation: { label: 'Equation', labelPlural: 'Equations', counter: 'arabic', sep: ' ' },
31
+ section: { label: '§', labelPlural: '§§', counter: 'hierarchical', sep: '' },
32
+ table: { label: 'Table', labelPlural: 'Tables', counter: 'arabic', sep: ' ' },
33
+ }
34
+
35
+ const ENGINEERING = {
36
+ figure: { label: 'Fig.', labelPlural: 'Figs.', counter: 'arabic', sep: ' ' },
37
+ equation: { label: 'Eq.', labelPlural: 'Eqs.', counter: 'arabic', sep: ' ' },
38
+ section: { label: 'Sec.', labelPlural: 'Secs.', counter: 'hierarchical', sep: ' ' },
39
+ table: { label: 'Tab.', labelPlural: 'Tabs.', counter: 'arabic', sep: ' ' },
40
+ }
41
+
42
+ const GERMAN = {
43
+ figure: { label: 'Abb.', labelPlural: 'Abb.', counter: 'arabic', sep: ' ' },
44
+ equation: { label: 'Gl.', labelPlural: 'Gl.', counter: 'arabic', sep: ' ' },
45
+ section: { label: 'Abschn.', labelPlural: 'Abschn.', counter: 'hierarchical', sep: ' ' },
46
+ table: { label: 'Tab.', labelPlural: 'Tab.', counter: 'arabic', sep: ' ' },
47
+ }
48
+
49
+ const PLAIN = {
50
+ figure: { label: '', labelPlural: '', counter: 'arabic', sep: '' },
51
+ equation: { label: '', labelPlural: '', counter: 'arabic', sep: '' },
52
+ section: { label: '', labelPlural: '', counter: 'hierarchical', sep: '' },
53
+ table: { label: '', labelPlural: '', counter: 'arabic', sep: '' },
54
+ }
55
+
56
+ export const XREF_STYLES = {
57
+ humanities: HUMANITIES,
58
+ engineering: ENGINEERING,
59
+ german: GERMAN,
60
+ plain: PLAIN,
61
+ }
62
+
63
+ export const DEFAULT_XREF_STYLE = 'humanities'
64
+
65
+ /**
66
+ * Resolve the active xref-style for a document. Reads:
67
+ * - `book.xrefStyle:` in document config (preset name).
68
+ * - `book.xref.<kind>:` per-kind overrides on top of the preset.
69
+ * - Foundation-declared kinds (`foundation.xref.kinds`) extend the
70
+ * preset with whatever the foundation provides.
71
+ *
72
+ * Returns a plain map keyed by kind name; per-kind values are merged
73
+ * objects (preset + foundation extensions + document overrides).
74
+ */
75
+ export function resolveXrefStyle(presetName, config) {
76
+ const preset = XREF_STYLES[presetName] || XREF_STYLES[DEFAULT_XREF_STYLE]
77
+ const merged = { ...preset }
78
+
79
+ // Foundation extensions: kinds declared by the foundation (e.g.
80
+ // theorem, lemma, proof for a math foundation).
81
+ const foundationKinds = globalThis.uniweb?.foundationConfig?.xref?.kinds
82
+ if (foundationKinds && typeof foundationKinds === 'object') {
83
+ for (const [kind, meta] of Object.entries(foundationKinds)) {
84
+ merged[kind] = { ...merged[kind], ...meta }
85
+ }
86
+ }
87
+
88
+ // Document overrides: `book.xref.<kind>:` granular tweaks.
89
+ const docOverrides = config?.book?.xref || config?.xref || null
90
+ if (docOverrides && typeof docOverrides === 'object') {
91
+ for (const [kind, meta] of Object.entries(docOverrides)) {
92
+ if (meta && typeof meta === 'object') {
93
+ merged[kind] = { ...merged[kind], ...meta }
94
+ }
95
+ }
96
+ }
97
+
98
+ return merged
99
+ }
100
+
101
+ export function getKindMeta(style, kind) {
102
+ return style?.[kind] || null
103
+ }