// Block registry (client side) — mirrors the server registry // (server/src/blocks/registry.js) but carries the browser-only concerns: the // React renderer, the admin edit form, and the palette icon/label. The page // builder's palette, drag-reorder canvas, per-block edit panel, and the public // page renderer all read from this registry, so adding a block later is one // entry here (plus its server-side schema entry) rather than edits scattered // across the builder and renderer. // // A registered definition looks like: // { // type: 'heading', // must match the server registry type // version: 1, // must match the server schema version // label: 'Heading', // palette display name // icon: 'heading', // palette icon key // component: HeadingBlock, // renderer: (props) => JSX // editor: HeadingEditor, // admin edit form: ({ props, onChange }) => JSX // defaults: () => ({ ... }), // starting props when a block is added // container: false, // true only for two_column // containerSlots: [], // ['left','right'] for two_column // } // // This module only defines the pattern; Wave 1 definitions register via // ./index.js as each block is built (spec build order step 3). const registry = new Map() // Kept in sync with the server's RESERVED_KEYS — the only top-level keys on a // stored block object. Exported so the builder can construct envelopes without // hard-coding the shape. export const RESERVED_KEYS = ['id', 'type', 'version', 'visible', 'props'] /** * Register a block definition. Throws on a duplicate type — a programmer error * caught at module load, not runtime. * @param {object} def * @returns {object} the stored definition */ export function registerBlock(def) { if (!def || typeof def.type !== 'string' || def.type.length === 0) { throw new Error('registerBlock: a block definition needs a string `type`') } if (registry.has(def.type)) { throw new Error(`registerBlock: block type already registered: ${def.type}`) } const entry = { type: def.type, version: Number.isInteger(def.version) ? def.version : 1, label: def.label || def.type, icon: def.icon || null, component: def.component || null, editor: def.editor || null, defaults: typeof def.defaults === 'function' ? def.defaults : () => ({}), container: Boolean(def.container), containerSlots: def.containerSlots ? [...def.containerSlots] : [], } registry.set(entry.type, entry) return entry } /** @returns {object|null} the definition for `type`, or null if unknown. */ export function getBlock(type) { return registry.get(type) || null } /** @returns {boolean} whether `type` is a registered block. */ export function hasBlock(type) { return registry.has(type) } /** @returns {object[]} all registered definitions (registration order). */ export function listBlocks() { return [...registry.values()] } /** * Generate a stable block id. Called once when a block is added to the canvas; * never derived from array position, so a reorder keeps ids intact (they are the * React key and the future revision-history join point). * @returns {string} */ export function makeBlockId() { const rand = Math.random().toString(36).slice(2, 8).toUpperCase() return `b_${rand}` }