@uniweb/runtime 0.2.15 → 0.2.16
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 +112 -1
- package/dist/ssr.js.map +1 -1
- package/package.json +2 -2
- package/src/components/BlockRenderer.jsx +41 -1
- package/src/data-fetcher-client.js +144 -0
- package/src/prepare-props.js +39 -0
package/dist/ssr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
3
3
|
function guaranteeItemStructure(item) {
|
|
4
4
|
return {
|
|
@@ -106,10 +106,33 @@ function applyDefaults(params, defaults) {
|
|
|
106
106
|
...params || {}
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
|
+
function applyCascadedData(localData, cascadedData, inheritData) {
|
|
110
|
+
if (!inheritData || !cascadedData || Object.keys(cascadedData).length === 0) {
|
|
111
|
+
return localData;
|
|
112
|
+
}
|
|
113
|
+
if (inheritData === true) {
|
|
114
|
+
return { ...cascadedData, ...localData };
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(inheritData)) {
|
|
117
|
+
const result = { ...localData };
|
|
118
|
+
for (const key of inheritData) {
|
|
119
|
+
if (cascadedData[key] !== void 0 && result[key] === void 0) {
|
|
120
|
+
result[key] = cascadedData[key];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
return localData;
|
|
126
|
+
}
|
|
109
127
|
function prepareProps(block, meta) {
|
|
110
128
|
const defaults = meta?.defaults || {};
|
|
111
129
|
const params = applyDefaults(block.properties, defaults);
|
|
112
130
|
const content = guaranteeContentStructure(block.parsedContent);
|
|
131
|
+
const inheritData = meta?.inheritData;
|
|
132
|
+
const cascadedData = block.cascadedData || {};
|
|
133
|
+
if (inheritData) {
|
|
134
|
+
content.data = applyCascadedData(content.data, cascadedData, inheritData);
|
|
135
|
+
}
|
|
113
136
|
const schemas = meta?.schemas || null;
|
|
114
137
|
if (schemas && content.data) {
|
|
115
138
|
content.data = applySchemas(content.data, schemas);
|
|
@@ -122,6 +145,69 @@ function getComponentMeta(componentName) {
|
|
|
122
145
|
function getComponentDefaults(componentName) {
|
|
123
146
|
return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
|
|
124
147
|
}
|
|
148
|
+
function getNestedValue(obj, path) {
|
|
149
|
+
if (!obj || !path) return obj;
|
|
150
|
+
const parts = path.split(".");
|
|
151
|
+
let current = obj;
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (current === null || current === void 0) return void 0;
|
|
154
|
+
current = current[part];
|
|
155
|
+
}
|
|
156
|
+
return current;
|
|
157
|
+
}
|
|
158
|
+
async function executeFetchClient(config) {
|
|
159
|
+
if (!config) return { data: null };
|
|
160
|
+
const { path, url, transform } = config;
|
|
161
|
+
try {
|
|
162
|
+
const fetchUrl = path || url;
|
|
163
|
+
if (!fetchUrl) {
|
|
164
|
+
return { data: [], error: "No path or url specified" };
|
|
165
|
+
}
|
|
166
|
+
const response = await fetch(fetchUrl);
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
169
|
+
}
|
|
170
|
+
const contentType = response.headers.get("content-type") || "";
|
|
171
|
+
let data;
|
|
172
|
+
if (contentType.includes("application/json")) {
|
|
173
|
+
data = await response.json();
|
|
174
|
+
} else {
|
|
175
|
+
const text = await response.text();
|
|
176
|
+
try {
|
|
177
|
+
data = JSON.parse(text);
|
|
178
|
+
} catch {
|
|
179
|
+
console.warn("[data-fetcher] Response is not JSON, returning as text");
|
|
180
|
+
data = text;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (transform && data) {
|
|
184
|
+
data = getNestedValue(data, transform);
|
|
185
|
+
}
|
|
186
|
+
return { data: data ?? [] };
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.warn(`[data-fetcher] Client fetch failed: ${error.message}`);
|
|
189
|
+
return { data: [], error: error.message };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function mergeIntoData(currentData, fetchedData, schema, merge = false) {
|
|
193
|
+
if (fetchedData === null || fetchedData === void 0 || !schema) {
|
|
194
|
+
return currentData;
|
|
195
|
+
}
|
|
196
|
+
const result = { ...currentData || {} };
|
|
197
|
+
if (merge && result[schema] !== void 0) {
|
|
198
|
+
const existing = result[schema];
|
|
199
|
+
if (Array.isArray(existing) && Array.isArray(fetchedData)) {
|
|
200
|
+
result[schema] = [...existing, ...fetchedData];
|
|
201
|
+
} else if (typeof existing === "object" && existing !== null && typeof fetchedData === "object" && fetchedData !== null && !Array.isArray(existing) && !Array.isArray(fetchedData)) {
|
|
202
|
+
result[schema] = { ...existing, ...fetchedData };
|
|
203
|
+
} else {
|
|
204
|
+
result[schema] = fetchedData;
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
result[schema] = fetchedData;
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
125
211
|
const hexToRgba = (hex, opacity) => {
|
|
126
212
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
127
213
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
@@ -169,7 +255,29 @@ const getWrapperProps = (block) => {
|
|
|
169
255
|
};
|
|
170
256
|
};
|
|
171
257
|
function BlockRenderer({ block, pure = false, extra = {} }) {
|
|
258
|
+
const [runtimeData, setRuntimeData] = useState(null);
|
|
259
|
+
const [fetchError, setFetchError] = useState(null);
|
|
172
260
|
const Component = block.initComponent();
|
|
261
|
+
const fetchConfig = block.fetch;
|
|
262
|
+
const shouldFetchAtRuntime = fetchConfig && fetchConfig.prerender === false;
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (!shouldFetchAtRuntime) return;
|
|
265
|
+
let cancelled = false;
|
|
266
|
+
async function doFetch() {
|
|
267
|
+
const result = await executeFetchClient(fetchConfig);
|
|
268
|
+
if (cancelled) return;
|
|
269
|
+
if (result.error) {
|
|
270
|
+
setFetchError(result.error);
|
|
271
|
+
}
|
|
272
|
+
if (result.data) {
|
|
273
|
+
setRuntimeData({ [fetchConfig.schema]: result.data });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
doFetch();
|
|
277
|
+
return () => {
|
|
278
|
+
cancelled = true;
|
|
279
|
+
};
|
|
280
|
+
}, [shouldFetchAtRuntime, fetchConfig]);
|
|
173
281
|
if (!Component) {
|
|
174
282
|
return /* @__PURE__ */ jsxs("div", { className: "block-error", style: { padding: "1rem", background: "#fef2f2", color: "#dc2626" }, children: [
|
|
175
283
|
"Component not found: ",
|
|
@@ -191,6 +299,9 @@ function BlockRenderer({ block, pure = false, extra = {} }) {
|
|
|
191
299
|
_prosemirror: block.parsedContent
|
|
192
300
|
// Keep original for components that need raw access
|
|
193
301
|
};
|
|
302
|
+
if (runtimeData && shouldFetchAtRuntime) {
|
|
303
|
+
content.data = mergeIntoData(content.data, runtimeData[fetchConfig.schema], fetchConfig.schema, fetchConfig.merge);
|
|
304
|
+
}
|
|
194
305
|
}
|
|
195
306
|
const componentProps = {
|
|
196
307
|
content,
|
package/dist/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/components/BlockRenderer.jsx","../src/components/Blocks.jsx","../src/components/Layout.jsx","../src/ssr.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 *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\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 imgs: item.imgs || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\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 }\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 subtitle2: content.subtitle2 || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n imgs: content.imgs || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\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 // 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 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] = applySchemaToValue(rawValue, schema)\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 * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const content = guaranteeContentStructure(block.parsedContent)\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 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 * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n */\n\nimport React from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\n\n/**\n * Convert hex color to rgba\n */\nconst hexToRgba = (hex, opacity) => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${opacity})`\n}\n\n/**\n * Build wrapper props from block configuration\n */\nconst getWrapperProps = (block) => {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n let className = theme || ''\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {}, colors = {} } = block.standardOptions\n const style = {}\n\n // Handle background modes\n if (background.mode === 'gradient') {\n const {\n enabled = false,\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 0.7,\n endOpacity = 0.3\n } = background.gradient || {}\n\n if (enabled) {\n style['--bg-color'] = `linear-gradient(${angle}deg,\n ${hexToRgba(start, startOpacity)} ${startPosition}%,\n ${hexToRgba(end, endOpacity)} ${endPosition}%)`\n }\n } else if (background.mode === 'image' || background.mode === 'video') {\n const settings = background[background.mode] || {}\n const { url = '', file = '' } = settings\n\n if (url || file) {\n style['--bg-color'] = 'transparent'\n style.position = 'relative'\n style.maxWidth = '100%'\n }\n }\n\n return {\n id: `Section${block.id}`,\n style,\n className\n }\n}\n\n/**\n * BlockRenderer component\n */\nexport default function BlockRenderer({ block, pure = false, extra = {} }) {\n const Component = block.initComponent()\n\n if (!Component) {\n return (\n <div className=\"block-error\" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>\n Component not found: {block.type}\n </div>\n )\n }\n\n // Build content and params with runtime guarantees\n // Sources:\n // 1. parsedContent._isPoc - simple PoC format (hardcoded content)\n // 2. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 3. block.properties - params from frontmatter (theme, alignment, etc.)\n // 4. meta - defaults from component meta.js\n let content, params\n\n if (block.parsedContent?._isPoc) {\n // Simple PoC format - content was passed directly\n content = block.parsedContent._pocContent\n params = block.properties\n } else {\n // Get runtime metadata for this component (has defaults, data binding, etc.)\n const meta = getComponentMeta(block.type)\n\n // Prepare props with runtime guarantees:\n // - Apply param defaults from meta.js\n // - Guarantee content structure exists\n const prepared = prepareProps(block, meta)\n params = prepared.params\n\n // Merge prepared content with raw access for components that need it\n content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n _prosemirror: block.parsedContent // Keep original for components that need raw access\n }\n }\n\n const componentProps = {\n content,\n params,\n block,\n input: block.input\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n const wrapperProps = getWrapperProps(block)\n\n return (\n <div {...wrapperProps}>\n <Component {...componentProps} />\n </div>\n )\n}\n","/**\n * Blocks\n *\n * Renders an array of blocks for a layout area (header, body, footer, panels).\n * Used by the Layout component to pre-render each area.\n */\n\nimport React from 'react'\nimport BlockRenderer from './BlockRenderer.jsx'\n\n/**\n * Render a list of blocks\n *\n * @param {Object} props\n * @param {Block[]} props.blocks - Array of Block instances to render\n * @param {Object} [props.extra] - Extra props to pass to each block\n */\nexport default function Blocks({ blocks, extra = {} }) {\n if (!blocks || blocks.length === 0) return null\n\n return blocks.map((block, index) => (\n <React.Fragment key={block.id || index}>\n <BlockRenderer block={block} extra={extra} />\n </React.Fragment>\n ))\n}\n","/**\n * Layout\n *\n * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).\n * Supports foundation-provided custom Layout components via website.getRemoteLayout().\n *\n * Layout Areas:\n * - header: Top navigation, branding (from @header page)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from @footer page)\n * - left: Left sidebar/panel (from @left page)\n * - right: Right sidebar/panel (from @right page)\n *\n * Custom Layouts:\n * Foundations can provide a custom Layout via src/exports.js:\n *\n * ```jsx\n * // src/exports.js\n * import Layout from './components/Layout'\n *\n * export default {\n * Layout,\n * props: {\n * themeToggleEnabled: true,\n * }\n * }\n * ```\n *\n * The Layout component receives pre-rendered areas as props:\n * - page, website: Runtime context\n * - header, body, footer: Pre-rendered React elements\n * - left, right (or leftPanel, rightPanel): Sidebar panels\n */\n\nimport Blocks from './Blocks.jsx'\n\n/**\n * Default layout - renders header, body, footer in sequence\n * (no panels in default layout)\n */\nfunction DefaultLayout({ header, body, footer }) {\n return (\n <>\n {header}\n {body}\n {footer}\n </>\n )\n}\n\n/**\n * Layout component\n *\n * @param {Object} props\n * @param {Page} props.page - Current page instance\n * @param {Website} props.website - Website instance\n */\nexport default function Layout({ page, website }) {\n // Check if foundation provides a custom Layout\n const RemoteLayout = website.getRemoteLayout()\n\n // Get block groups from page (respects layout preferences)\n const headerBlocks = page.getHeaderBlocks()\n const bodyBlocks = page.getBodyBlocks()\n const footerBlocks = page.getFooterBlocks()\n const leftBlocks = page.getLeftBlocks()\n const rightBlocks = page.getRightBlocks()\n\n // Pre-render each area as React elements\n const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null\n const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null\n const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null\n const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null\n const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null\n\n // Use foundation's custom Layout if provided\n if (RemoteLayout) {\n return (\n <RemoteLayout\n page={page}\n website={website}\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n left={leftElement}\n right={rightElement}\n // Aliases for backwards compatibility\n leftPanel={leftElement}\n rightPanel={rightElement}\n />\n )\n }\n\n // Default layout\n return (\n <DefaultLayout\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n />\n )\n}\n","/**\n * @uniweb/runtime/ssr - Server-Side Rendering Entry Point\n *\n * Node.js-compatible exports for SSG/prerendering.\n * This module is built to a standalone bundle that can be imported\n * directly by Node.js without Vite transpilation.\n *\n * Usage in prerender.js:\n * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'\n */\n\nimport React from 'react'\n\n// Props preparation (no browser APIs)\nexport {\n prepareProps,\n applySchemas,\n applyDefaults,\n guaranteeContentStructure,\n getComponentMeta,\n getComponentDefaults\n} from './prepare-props.js'\n\n// Components for rendering\nexport { default as BlockRenderer } from './components/BlockRenderer.jsx'\nexport { default as Blocks } from './components/Blocks.jsx'\nexport { default as Layout } from './components/Layout.jsx'\n\n// Re-export Layout's DefaultLayout for direct use\nimport LayoutComponent from './components/Layout.jsx'\n\n/**\n * Render a page to React elements\n *\n * This is the main entry point for SSG. It returns a React element\n * that can be passed to renderToString().\n *\n * @param {Object} props\n * @param {Page} props.page - The page instance to render\n * @param {Website} props.website - The website instance\n * @returns {React.ReactElement}\n */\nexport function PageElement({ page, website }) {\n return React.createElement(\n 'main',\n null,\n React.createElement(LayoutComponent, { page, website })\n )\n}\n"],"names":["LayoutComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/data-fetcher-client.js","../src/components/BlockRenderer.jsx","../src/components/Blocks.jsx","../src/components/Layout.jsx","../src/ssr.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 *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\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 imgs: item.imgs || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\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 }\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 subtitle2: content.subtitle2 || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n imgs: content.imgs || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\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 // 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 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] = applySchemaToValue(rawValue, schema)\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 * Apply cascaded data based on component's inheritData setting\n *\n * @param {Object} localData - content.data from the section itself\n * @param {Object} cascadedData - Data from page/site level fetches\n * @param {boolean|Array} inheritData - Component's inheritData setting\n * @returns {Object} Merged data object\n */\nfunction applyCascadedData(localData, cascadedData, inheritData) {\n if (!inheritData || !cascadedData || Object.keys(cascadedData).length === 0) {\n return localData\n }\n\n if (inheritData === true) {\n // Inherit all: cascaded data as base, local data overrides\n return { ...cascadedData, ...localData }\n }\n\n if (Array.isArray(inheritData)) {\n // Selective: only specified schemas, local data takes precedence\n const result = { ...localData }\n for (const key of inheritData) {\n if (cascadedData[key] !== undefined && result[key] === undefined) {\n result[key] = cascadedData[key]\n }\n }\n return result\n }\n\n return localData\n}\n\n/**\n * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const content = guaranteeContentStructure(block.parsedContent)\n\n // Apply cascaded data based on component's inheritData setting\n const inheritData = meta?.inheritData\n const cascadedData = block.cascadedData || {}\n if (inheritData) {\n content.data = applyCascadedData(content.data, cascadedData, inheritData)\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 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 * Client-side Data Fetcher\n *\n * Executes fetch operations in the browser for runtime data loading.\n * Used when prerender: false is set on fetch configurations.\n *\n * @module @uniweb/runtime/data-fetcher-client\n */\n\n/**\n * Get a nested value from an object using dot notation\n *\n * @param {object} obj - Source object\n * @param {string} path - Dot-separated path (e.g., 'data.items')\n * @returns {any} The nested value or undefined\n */\nfunction getNestedValue(obj, path) {\n if (!obj || !path) return obj\n\n const parts = path.split('.')\n let current = obj\n\n for (const part of parts) {\n if (current === null || current === undefined) return undefined\n current = current[part]\n }\n\n return current\n}\n\n/**\n * Execute a fetch operation in the browser\n *\n * @param {object} config - Normalized fetch config\n * @param {string} config.path - Local path (relative to site root)\n * @param {string} config.url - Remote URL\n * @param {string} config.schema - Schema key for data\n * @param {string} config.transform - Optional path to extract from response\n * @returns {Promise<{ data: any, error?: string }>} Fetched data or error\n *\n * @example\n * const result = await executeFetchClient({\n * path: '/data/team.json',\n * schema: 'team'\n * })\n * // result.data contains the parsed JSON array\n */\nexport async function executeFetchClient(config) {\n if (!config) return { data: null }\n\n const { path, url, transform } = config\n\n try {\n // Determine the fetch URL\n // For local paths, they're relative to the site root (served from public/)\n // For remote URLs, use as-is\n const fetchUrl = path || url\n\n if (!fetchUrl) {\n return { data: [], error: 'No path or url specified' }\n }\n\n const response = await fetch(fetchUrl)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n // Parse response based on content type\n const contentType = response.headers.get('content-type') || ''\n let data\n\n if (contentType.includes('application/json')) {\n data = await response.json()\n } else {\n // Try JSON first\n const text = await response.text()\n try {\n data = JSON.parse(text)\n } catch {\n // Return text as-is if not JSON\n console.warn('[data-fetcher] Response is not JSON, returning as text')\n data = text\n }\n }\n\n // Apply transform if specified (extract nested path)\n if (transform && data) {\n data = getNestedValue(data, transform)\n }\n\n return { data: data ?? [] }\n } catch (error) {\n console.warn(`[data-fetcher] Client fetch failed: ${error.message}`)\n return { data: [], error: error.message }\n }\n}\n\n/**\n * Merge fetched data into existing content.data\n *\n * @param {object} currentData - Current content.data object\n * @param {any} fetchedData - Data from fetch\n * @param {string} schema - Schema key to store under\n * @param {boolean} [merge=false] - If true, merge with existing; if false, replace\n * @returns {object} Updated data object\n */\nexport function mergeIntoData(currentData, fetchedData, schema, merge = false) {\n if (fetchedData === null || fetchedData === undefined || !schema) {\n return currentData\n }\n\n const result = { ...(currentData || {}) }\n\n if (merge && result[schema] !== undefined) {\n // Merge mode: combine with existing data\n const existing = result[schema]\n\n if (Array.isArray(existing) && Array.isArray(fetchedData)) {\n // Arrays: concatenate\n result[schema] = [...existing, ...fetchedData]\n } else if (\n typeof existing === 'object' &&\n existing !== null &&\n typeof fetchedData === 'object' &&\n fetchedData !== null &&\n !Array.isArray(existing) &&\n !Array.isArray(fetchedData)\n ) {\n // Objects: shallow merge\n result[schema] = { ...existing, ...fetchedData }\n } else {\n // Different types: fetched data wins\n result[schema] = fetchedData\n }\n } else {\n // Replace mode (default): fetched data overwrites\n result[schema] = fetchedData\n }\n\n return result\n}\n\nexport default executeFetchClient\n","/**\n * BlockRenderer\n *\n * Bridges Block data to foundation components.\n * Handles theming, wrapper props, and runtime guarantees.\n * Supports runtime data fetching for prerender: false configs.\n */\n\nimport React, { useState, useEffect } from 'react'\nimport { prepareProps, getComponentMeta } from '../prepare-props.js'\nimport { executeFetchClient, mergeIntoData } from '../data-fetcher-client.js'\n\n/**\n * Convert hex color to rgba\n */\nconst hexToRgba = (hex, opacity) => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${opacity})`\n}\n\n/**\n * Build wrapper props from block configuration\n */\nconst getWrapperProps = (block) => {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n let className = theme || ''\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {}, colors = {} } = block.standardOptions\n const style = {}\n\n // Handle background modes\n if (background.mode === 'gradient') {\n const {\n enabled = false,\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 0.7,\n endOpacity = 0.3\n } = background.gradient || {}\n\n if (enabled) {\n style['--bg-color'] = `linear-gradient(${angle}deg,\n ${hexToRgba(start, startOpacity)} ${startPosition}%,\n ${hexToRgba(end, endOpacity)} ${endPosition}%)`\n }\n } else if (background.mode === 'image' || background.mode === 'video') {\n const settings = background[background.mode] || {}\n const { url = '', file = '' } = settings\n\n if (url || file) {\n style['--bg-color'] = 'transparent'\n style.position = 'relative'\n style.maxWidth = '100%'\n }\n }\n\n return {\n id: `Section${block.id}`,\n style,\n className\n }\n}\n\n/**\n * BlockRenderer component\n */\nexport default function BlockRenderer({ block, pure = false, extra = {} }) {\n // State for runtime-fetched data (when prerender: false)\n const [runtimeData, setRuntimeData] = useState(null)\n const [fetchError, setFetchError] = useState(null)\n\n const Component = block.initComponent()\n\n // Runtime fetch for prerender: false configurations\n const fetchConfig = block.fetch\n const shouldFetchAtRuntime = fetchConfig && fetchConfig.prerender === false\n\n useEffect(() => {\n if (!shouldFetchAtRuntime) return\n\n let cancelled = false\n\n async function doFetch() {\n const result = await executeFetchClient(fetchConfig)\n if (cancelled) return\n\n if (result.error) {\n setFetchError(result.error)\n }\n if (result.data) {\n setRuntimeData({ [fetchConfig.schema]: result.data })\n }\n }\n\n doFetch()\n\n return () => {\n cancelled = true\n }\n }, [shouldFetchAtRuntime, fetchConfig])\n\n if (!Component) {\n return (\n <div className=\"block-error\" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>\n Component not found: {block.type}\n </div>\n )\n }\n\n // Build content and params with runtime guarantees\n // Sources:\n // 1. parsedContent._isPoc - simple PoC format (hardcoded content)\n // 2. parsedContent - semantic parser output (flat: title, paragraphs, links, etc.)\n // 3. block.properties - params from frontmatter (theme, alignment, etc.)\n // 4. meta - defaults from component meta.js\n let content, params\n\n if (block.parsedContent?._isPoc) {\n // Simple PoC format - content was passed directly\n content = block.parsedContent._pocContent\n params = block.properties\n } else {\n // Get runtime metadata for this component (has defaults, data binding, etc.)\n const meta = getComponentMeta(block.type)\n\n // Prepare props with runtime guarantees:\n // - Apply param defaults from meta.js\n // - Guarantee content structure exists\n // - Apply cascaded data based on inheritData\n const prepared = prepareProps(block, meta)\n params = prepared.params\n\n // Merge prepared content with raw access for components that need it\n content = {\n ...prepared.content,\n ...block.properties, // Frontmatter params overlay (legacy support)\n _prosemirror: block.parsedContent // Keep original for components that need raw access\n }\n\n // Merge runtime-fetched data if available\n if (runtimeData && shouldFetchAtRuntime) {\n content.data = mergeIntoData(content.data, runtimeData[fetchConfig.schema], fetchConfig.schema, fetchConfig.merge)\n }\n }\n\n const componentProps = {\n content,\n params,\n block,\n input: block.input\n }\n\n if (pure) {\n return <Component {...componentProps} extra={extra} />\n }\n\n const wrapperProps = getWrapperProps(block)\n\n return (\n <div {...wrapperProps}>\n <Component {...componentProps} />\n </div>\n )\n}\n","/**\n * Blocks\n *\n * Renders an array of blocks for a layout area (header, body, footer, panels).\n * Used by the Layout component to pre-render each area.\n */\n\nimport React from 'react'\nimport BlockRenderer from './BlockRenderer.jsx'\n\n/**\n * Render a list of blocks\n *\n * @param {Object} props\n * @param {Block[]} props.blocks - Array of Block instances to render\n * @param {Object} [props.extra] - Extra props to pass to each block\n */\nexport default function Blocks({ blocks, extra = {} }) {\n if (!blocks || blocks.length === 0) return null\n\n return blocks.map((block, index) => (\n <React.Fragment key={block.id || index}>\n <BlockRenderer block={block} extra={extra} />\n </React.Fragment>\n ))\n}\n","/**\n * Layout\n *\n * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).\n * Supports foundation-provided custom Layout components via website.getRemoteLayout().\n *\n * Layout Areas:\n * - header: Top navigation, branding (from @header page)\n * - body: Main page content (from page sections)\n * - footer: Bottom navigation, copyright (from @footer page)\n * - left: Left sidebar/panel (from @left page)\n * - right: Right sidebar/panel (from @right page)\n *\n * Custom Layouts:\n * Foundations can provide a custom Layout via src/exports.js:\n *\n * ```jsx\n * // src/exports.js\n * import Layout from './components/Layout'\n *\n * export default {\n * Layout,\n * props: {\n * themeToggleEnabled: true,\n * }\n * }\n * ```\n *\n * The Layout component receives pre-rendered areas as props:\n * - page, website: Runtime context\n * - header, body, footer: Pre-rendered React elements\n * - left, right (or leftPanel, rightPanel): Sidebar panels\n */\n\nimport Blocks from './Blocks.jsx'\n\n/**\n * Default layout - renders header, body, footer in sequence\n * (no panels in default layout)\n */\nfunction DefaultLayout({ header, body, footer }) {\n return (\n <>\n {header}\n {body}\n {footer}\n </>\n )\n}\n\n/**\n * Layout component\n *\n * @param {Object} props\n * @param {Page} props.page - Current page instance\n * @param {Website} props.website - Website instance\n */\nexport default function Layout({ page, website }) {\n // Check if foundation provides a custom Layout\n const RemoteLayout = website.getRemoteLayout()\n\n // Get block groups from page (respects layout preferences)\n const headerBlocks = page.getHeaderBlocks()\n const bodyBlocks = page.getBodyBlocks()\n const footerBlocks = page.getFooterBlocks()\n const leftBlocks = page.getLeftBlocks()\n const rightBlocks = page.getRightBlocks()\n\n // Pre-render each area as React elements\n const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null\n const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null\n const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null\n const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null\n const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null\n\n // Use foundation's custom Layout if provided\n if (RemoteLayout) {\n return (\n <RemoteLayout\n page={page}\n website={website}\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n left={leftElement}\n right={rightElement}\n // Aliases for backwards compatibility\n leftPanel={leftElement}\n rightPanel={rightElement}\n />\n )\n }\n\n // Default layout\n return (\n <DefaultLayout\n header={headerElement}\n body={bodyElement}\n footer={footerElement}\n />\n )\n}\n","/**\n * @uniweb/runtime/ssr - Server-Side Rendering Entry Point\n *\n * Node.js-compatible exports for SSG/prerendering.\n * This module is built to a standalone bundle that can be imported\n * directly by Node.js without Vite transpilation.\n *\n * Usage in prerender.js:\n * import { renderPage, Blocks, BlockRenderer } from '@uniweb/runtime/ssr'\n */\n\nimport React from 'react'\n\n// Props preparation (no browser APIs)\nexport {\n prepareProps,\n applySchemas,\n applyDefaults,\n guaranteeContentStructure,\n getComponentMeta,\n getComponentDefaults\n} from './prepare-props.js'\n\n// Components for rendering\nexport { default as BlockRenderer } from './components/BlockRenderer.jsx'\nexport { default as Blocks } from './components/Blocks.jsx'\nexport { default as Layout } from './components/Layout.jsx'\n\n// Re-export Layout's DefaultLayout for direct use\nimport LayoutComponent from './components/Layout.jsx'\n\n/**\n * Render a page to React elements\n *\n * This is the main entry point for SSG. It returns a React element\n * that can be passed to renderToString().\n *\n * @param {Object} props\n * @param {Page} props.page - The page instance to render\n * @param {Website} props.website - The website instance\n * @returns {React.ReactElement}\n */\nexport function PageElement({ page, website }) {\n return React.createElement(\n 'main',\n null,\n React.createElement(LayoutComponent, { page, website })\n )\n}\n"],"names":["LayoutComponent"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=20.19"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@uniweb/core": "0.1.
|
|
34
|
+
"@uniweb/core": "0.1.13"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@vitejs/plugin-react": "^4.5.2",
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bridges Block data to foundation components.
|
|
5
5
|
* Handles theming, wrapper props, and runtime guarantees.
|
|
6
|
+
* Supports runtime data fetching for prerender: false configs.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import React from 'react'
|
|
9
|
+
import React, { useState, useEffect } from 'react'
|
|
9
10
|
import { prepareProps, getComponentMeta } from '../prepare-props.js'
|
|
11
|
+
import { executeFetchClient, mergeIntoData } from '../data-fetcher-client.js'
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Convert hex color to rgba
|
|
@@ -73,8 +75,40 @@ const getWrapperProps = (block) => {
|
|
|
73
75
|
* BlockRenderer component
|
|
74
76
|
*/
|
|
75
77
|
export default function BlockRenderer({ block, pure = false, extra = {} }) {
|
|
78
|
+
// State for runtime-fetched data (when prerender: false)
|
|
79
|
+
const [runtimeData, setRuntimeData] = useState(null)
|
|
80
|
+
const [fetchError, setFetchError] = useState(null)
|
|
81
|
+
|
|
76
82
|
const Component = block.initComponent()
|
|
77
83
|
|
|
84
|
+
// Runtime fetch for prerender: false configurations
|
|
85
|
+
const fetchConfig = block.fetch
|
|
86
|
+
const shouldFetchAtRuntime = fetchConfig && fetchConfig.prerender === false
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!shouldFetchAtRuntime) return
|
|
90
|
+
|
|
91
|
+
let cancelled = false
|
|
92
|
+
|
|
93
|
+
async function doFetch() {
|
|
94
|
+
const result = await executeFetchClient(fetchConfig)
|
|
95
|
+
if (cancelled) return
|
|
96
|
+
|
|
97
|
+
if (result.error) {
|
|
98
|
+
setFetchError(result.error)
|
|
99
|
+
}
|
|
100
|
+
if (result.data) {
|
|
101
|
+
setRuntimeData({ [fetchConfig.schema]: result.data })
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
doFetch()
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
cancelled = true
|
|
109
|
+
}
|
|
110
|
+
}, [shouldFetchAtRuntime, fetchConfig])
|
|
111
|
+
|
|
78
112
|
if (!Component) {
|
|
79
113
|
return (
|
|
80
114
|
<div className="block-error" style={{ padding: '1rem', background: '#fef2f2', color: '#dc2626' }}>
|
|
@@ -102,6 +136,7 @@ export default function BlockRenderer({ block, pure = false, extra = {} }) {
|
|
|
102
136
|
// Prepare props with runtime guarantees:
|
|
103
137
|
// - Apply param defaults from meta.js
|
|
104
138
|
// - Guarantee content structure exists
|
|
139
|
+
// - Apply cascaded data based on inheritData
|
|
105
140
|
const prepared = prepareProps(block, meta)
|
|
106
141
|
params = prepared.params
|
|
107
142
|
|
|
@@ -111,6 +146,11 @@ export default function BlockRenderer({ block, pure = false, extra = {} }) {
|
|
|
111
146
|
...block.properties, // Frontmatter params overlay (legacy support)
|
|
112
147
|
_prosemirror: block.parsedContent // Keep original for components that need raw access
|
|
113
148
|
}
|
|
149
|
+
|
|
150
|
+
// Merge runtime-fetched data if available
|
|
151
|
+
if (runtimeData && shouldFetchAtRuntime) {
|
|
152
|
+
content.data = mergeIntoData(content.data, runtimeData[fetchConfig.schema], fetchConfig.schema, fetchConfig.merge)
|
|
153
|
+
}
|
|
114
154
|
}
|
|
115
155
|
|
|
116
156
|
const componentProps = {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Data Fetcher
|
|
3
|
+
*
|
|
4
|
+
* Executes fetch operations in the browser for runtime data loading.
|
|
5
|
+
* Used when prerender: false is set on fetch configurations.
|
|
6
|
+
*
|
|
7
|
+
* @module @uniweb/runtime/data-fetcher-client
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get a nested value from an object using dot notation
|
|
12
|
+
*
|
|
13
|
+
* @param {object} obj - Source object
|
|
14
|
+
* @param {string} path - Dot-separated path (e.g., 'data.items')
|
|
15
|
+
* @returns {any} The nested value or undefined
|
|
16
|
+
*/
|
|
17
|
+
function getNestedValue(obj, path) {
|
|
18
|
+
if (!obj || !path) return obj
|
|
19
|
+
|
|
20
|
+
const parts = path.split('.')
|
|
21
|
+
let current = obj
|
|
22
|
+
|
|
23
|
+
for (const part of parts) {
|
|
24
|
+
if (current === null || current === undefined) return undefined
|
|
25
|
+
current = current[part]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return current
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Execute a fetch operation in the browser
|
|
33
|
+
*
|
|
34
|
+
* @param {object} config - Normalized fetch config
|
|
35
|
+
* @param {string} config.path - Local path (relative to site root)
|
|
36
|
+
* @param {string} config.url - Remote URL
|
|
37
|
+
* @param {string} config.schema - Schema key for data
|
|
38
|
+
* @param {string} config.transform - Optional path to extract from response
|
|
39
|
+
* @returns {Promise<{ data: any, error?: string }>} Fetched data or error
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const result = await executeFetchClient({
|
|
43
|
+
* path: '/data/team.json',
|
|
44
|
+
* schema: 'team'
|
|
45
|
+
* })
|
|
46
|
+
* // result.data contains the parsed JSON array
|
|
47
|
+
*/
|
|
48
|
+
export async function executeFetchClient(config) {
|
|
49
|
+
if (!config) return { data: null }
|
|
50
|
+
|
|
51
|
+
const { path, url, transform } = config
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Determine the fetch URL
|
|
55
|
+
// For local paths, they're relative to the site root (served from public/)
|
|
56
|
+
// For remote URLs, use as-is
|
|
57
|
+
const fetchUrl = path || url
|
|
58
|
+
|
|
59
|
+
if (!fetchUrl) {
|
|
60
|
+
return { data: [], error: 'No path or url specified' }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const response = await fetch(fetchUrl)
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parse response based on content type
|
|
70
|
+
const contentType = response.headers.get('content-type') || ''
|
|
71
|
+
let data
|
|
72
|
+
|
|
73
|
+
if (contentType.includes('application/json')) {
|
|
74
|
+
data = await response.json()
|
|
75
|
+
} else {
|
|
76
|
+
// Try JSON first
|
|
77
|
+
const text = await response.text()
|
|
78
|
+
try {
|
|
79
|
+
data = JSON.parse(text)
|
|
80
|
+
} catch {
|
|
81
|
+
// Return text as-is if not JSON
|
|
82
|
+
console.warn('[data-fetcher] Response is not JSON, returning as text')
|
|
83
|
+
data = text
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Apply transform if specified (extract nested path)
|
|
88
|
+
if (transform && data) {
|
|
89
|
+
data = getNestedValue(data, transform)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { data: data ?? [] }
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn(`[data-fetcher] Client fetch failed: ${error.message}`)
|
|
95
|
+
return { data: [], error: error.message }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Merge fetched data into existing content.data
|
|
101
|
+
*
|
|
102
|
+
* @param {object} currentData - Current content.data object
|
|
103
|
+
* @param {any} fetchedData - Data from fetch
|
|
104
|
+
* @param {string} schema - Schema key to store under
|
|
105
|
+
* @param {boolean} [merge=false] - If true, merge with existing; if false, replace
|
|
106
|
+
* @returns {object} Updated data object
|
|
107
|
+
*/
|
|
108
|
+
export function mergeIntoData(currentData, fetchedData, schema, merge = false) {
|
|
109
|
+
if (fetchedData === null || fetchedData === undefined || !schema) {
|
|
110
|
+
return currentData
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = { ...(currentData || {}) }
|
|
114
|
+
|
|
115
|
+
if (merge && result[schema] !== undefined) {
|
|
116
|
+
// Merge mode: combine with existing data
|
|
117
|
+
const existing = result[schema]
|
|
118
|
+
|
|
119
|
+
if (Array.isArray(existing) && Array.isArray(fetchedData)) {
|
|
120
|
+
// Arrays: concatenate
|
|
121
|
+
result[schema] = [...existing, ...fetchedData]
|
|
122
|
+
} else if (
|
|
123
|
+
typeof existing === 'object' &&
|
|
124
|
+
existing !== null &&
|
|
125
|
+
typeof fetchedData === 'object' &&
|
|
126
|
+
fetchedData !== null &&
|
|
127
|
+
!Array.isArray(existing) &&
|
|
128
|
+
!Array.isArray(fetchedData)
|
|
129
|
+
) {
|
|
130
|
+
// Objects: shallow merge
|
|
131
|
+
result[schema] = { ...existing, ...fetchedData }
|
|
132
|
+
} else {
|
|
133
|
+
// Different types: fetched data wins
|
|
134
|
+
result[schema] = fetchedData
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Replace mode (default): fetched data overwrites
|
|
138
|
+
result[schema] = fetchedData
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default executeFetchClient
|
package/src/prepare-props.js
CHANGED
|
@@ -186,6 +186,38 @@ export function applyDefaults(params, defaults) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Apply cascaded data based on component's inheritData setting
|
|
191
|
+
*
|
|
192
|
+
* @param {Object} localData - content.data from the section itself
|
|
193
|
+
* @param {Object} cascadedData - Data from page/site level fetches
|
|
194
|
+
* @param {boolean|Array} inheritData - Component's inheritData setting
|
|
195
|
+
* @returns {Object} Merged data object
|
|
196
|
+
*/
|
|
197
|
+
function applyCascadedData(localData, cascadedData, inheritData) {
|
|
198
|
+
if (!inheritData || !cascadedData || Object.keys(cascadedData).length === 0) {
|
|
199
|
+
return localData
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (inheritData === true) {
|
|
203
|
+
// Inherit all: cascaded data as base, local data overrides
|
|
204
|
+
return { ...cascadedData, ...localData }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (Array.isArray(inheritData)) {
|
|
208
|
+
// Selective: only specified schemas, local data takes precedence
|
|
209
|
+
const result = { ...localData }
|
|
210
|
+
for (const key of inheritData) {
|
|
211
|
+
if (cascadedData[key] !== undefined && result[key] === undefined) {
|
|
212
|
+
result[key] = cascadedData[key]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return localData
|
|
219
|
+
}
|
|
220
|
+
|
|
189
221
|
/**
|
|
190
222
|
* Prepare props for a component with runtime guarantees
|
|
191
223
|
*
|
|
@@ -201,6 +233,13 @@ export function prepareProps(block, meta) {
|
|
|
201
233
|
// Guarantee content structure
|
|
202
234
|
const content = guaranteeContentStructure(block.parsedContent)
|
|
203
235
|
|
|
236
|
+
// Apply cascaded data based on component's inheritData setting
|
|
237
|
+
const inheritData = meta?.inheritData
|
|
238
|
+
const cascadedData = block.cascadedData || {}
|
|
239
|
+
if (inheritData) {
|
|
240
|
+
content.data = applyCascadedData(content.data, cascadedData, inheritData)
|
|
241
|
+
}
|
|
242
|
+
|
|
204
243
|
// Apply schemas to content.data
|
|
205
244
|
const schemas = meta?.schemas || null
|
|
206
245
|
if (schemas && content.data) {
|