cms-renderer 0.5.2 → 0.6.1
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/README.md +278 -154
- package/dist/lib/block-renderer.d.ts +13 -10
- package/dist/lib/block-renderer.js +105 -239
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/block-toolbar.d.ts +5 -5
- package/dist/lib/block-toolbar.js +89 -70
- package/dist/lib/block-toolbar.js.map +1 -1
- package/dist/lib/client-editable-block.d.ts +11 -10
- package/dist/lib/client-editable-block.js +248 -32
- package/dist/lib/client-editable-block.js.map +1 -1
- package/dist/lib/custom-schemas.js +77 -39
- package/dist/lib/custom-schemas.js.map +1 -1
- package/dist/lib/renderer.js +105 -284
- package/dist/lib/renderer.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,43 +1,156 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Website
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**CMS-powered website built with Next.js 15 and App Router**
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
CMS (authors content)
|
|
6
|
+
CMS (authors content) --> tRPC API --> Website (renders blocks)
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
| Export | Description |
|
|
12
|
-
|--------|-------------|
|
|
13
|
-
| `./lib/renderer` | `ParametricRoutePage` — drop-in catch-all page component |
|
|
14
|
-
| `./lib/block-renderer` | `BlockRenderer`, `walkReactNode` — core rendering primitives |
|
|
15
|
-
| `./lib/block-toolbar` | `BlockToolbar` — floating edit toolbar (portal to `document.body`) |
|
|
16
|
-
| `./lib/client-editable-block` | `ClientEditableBlock` — client-side span injector fallback |
|
|
17
|
-
| `./lib/cms-api` | `getCmsClient` — tRPC HTTP client for Server Components |
|
|
18
|
-
| `./lib/refresher` | `Refresher` — SSE-based live revalidation |
|
|
19
|
-
| `./lib/proxy` | `createCmsProxy` — Next.js middleware to proxy `/admin`, `/api`, `/auth` |
|
|
20
|
-
| `./lib/types` | `BlockData`, `BlockComponentRegistry`, `ResolvedRouteParams`, … |
|
|
21
|
-
| `./lib/schema` | Schema utilities |
|
|
22
|
-
| `./lib/custom-schemas` | Custom schema helpers |
|
|
23
|
-
| `./lib/markdown-utils` | Markdown helpers |
|
|
24
|
-
| `./lib/trpc` | Client-side tRPC hooks |
|
|
25
|
-
| `./lib/result` | `Result<T, E>` type utilities |
|
|
26
|
-
| `./lib/image/lazy-load` | Lazy image component |
|
|
9
|
+
The website app fetches structured page data via tRPC and renders blocks using the ComponentMap pattern. Article content uses WASM-powered markdown rendering for performance.
|
|
27
10
|
|
|
28
11
|
---
|
|
29
12
|
|
|
30
13
|
## Quick Start
|
|
31
14
|
|
|
15
|
+
**Prerequisites**
|
|
16
|
+
|
|
17
|
+
| Tool | Version | Check Command |
|
|
18
|
+
|------|---------|---------------|
|
|
19
|
+
| Bun | 1.2.8+ | `bun --version` |
|
|
20
|
+
| Node.js | 18+ | `node --version` |
|
|
21
|
+
|
|
22
|
+
**Installation**
|
|
23
|
+
|
|
32
24
|
```bash
|
|
33
|
-
|
|
34
|
-
bun
|
|
25
|
+
# From monorepo root
|
|
26
|
+
bun install
|
|
27
|
+
|
|
28
|
+
# Start the website dev server
|
|
29
|
+
cd apps/renderer
|
|
30
|
+
bun run dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Verification**
|
|
34
|
+
|
|
35
|
+
Open [http://localhost:3001](http://localhost:3001). You should see:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Website
|
|
39
|
+
CMS-powered website built with Next.js 15 and App Router.
|
|
35
40
|
```
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
Visit [http://localhost:3001/demo](http://localhost:3001/demo) to see the complete demo page with navigation, header, and article blocks.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Architecture
|
|
47
|
+
|
|
48
|
+
### How This App Fits in the Monorepo
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
auteur/
|
|
52
|
+
├── apps/
|
|
53
|
+
│ ├── cms/ # Content authoring (Lexical editor)
|
|
54
|
+
│ └── renderer/ # This app - renders CMS content
|
|
55
|
+
└── packages/
|
|
56
|
+
├── cms-schema/ # Shared Zod schemas
|
|
57
|
+
└── markdown-wasm/ # WASM markdown renderer
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The website consumes schemas from `@repo/cms-schema` and uses `@repo/markdown-wasm` for article body rendering. It does not import from `apps/cms/` directly.
|
|
61
|
+
|
|
62
|
+
### Key Directories
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
apps/renderer/
|
|
66
|
+
├── app/ # Next.js App Router pages
|
|
67
|
+
│ ├── [slug]/ # Dynamic page routes
|
|
68
|
+
│ │ ├── page.tsx # Server Component fetches via tRPC
|
|
69
|
+
│ │ ├── loading.tsx # Skeleton loading state
|
|
70
|
+
│ │ └── error.tsx # Error boundary
|
|
71
|
+
│ ├── api/trpc/[trpc]/ # tRPC API handler
|
|
72
|
+
│ ├── layout.tsx # Root layout with CSS imports
|
|
73
|
+
│ ├── globals.css # CSS reset
|
|
74
|
+
│ └── page.tsx # Homepage
|
|
75
|
+
├── components/
|
|
76
|
+
│ ├── blocks/ # Block components + registry
|
|
77
|
+
│ │ ├── navigation-block.tsx
|
|
78
|
+
│ │ ├── header-block.tsx
|
|
79
|
+
│ │ ├── article-block.tsx
|
|
80
|
+
│ │ ├── block-renderer.tsx # Dispatcher component
|
|
81
|
+
│ │ ├── types.ts # BlockData discriminated union
|
|
82
|
+
│ │ └── index.ts # Exports + blockComponents registry
|
|
83
|
+
│ ├── page-renderer.tsx # Renders array of blocks
|
|
84
|
+
│ └── trpc-provider.tsx # Client-side tRPC/React Query
|
|
85
|
+
├── server/
|
|
86
|
+
│ ├── trpc.ts # tRPC initialization
|
|
87
|
+
│ └── routers/
|
|
88
|
+
│ ├── blocks/ # Block data procedures
|
|
89
|
+
│ │ ├── get-navigation.ts
|
|
90
|
+
│ │ ├── get-header.ts
|
|
91
|
+
│ │ └── get-article.ts
|
|
92
|
+
│ └── pages/ # Page composition procedures
|
|
93
|
+
│ └── get-page.ts
|
|
94
|
+
├── seed/ # Mock data for development
|
|
95
|
+
│ └── index.ts
|
|
96
|
+
├── styles/
|
|
97
|
+
│ └── blocks.css # Vanilla CSS for blocks
|
|
98
|
+
└── lib/
|
|
99
|
+
└── trpc.ts # Client-side tRPC hooks
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Data Flow
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
106
|
+
│ Dynamic Route │────>│ tRPC Caller │────>│ Mock Data │
|
|
107
|
+
│ [slug]/page │ │ pages.getPage │ │ seed/index.ts │
|
|
108
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
109
|
+
│
|
|
110
|
+
v
|
|
111
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
112
|
+
│ PageRenderer │────>│ BlockRenderer │
|
|
113
|
+
│ (iterates) │ │ (dispatches) │
|
|
114
|
+
└─────────────────┘ └─────────────────┘
|
|
115
|
+
│
|
|
116
|
+
┌──────────────────────┼──────────────────────┐
|
|
117
|
+
v v v
|
|
118
|
+
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
119
|
+
│ NavigationBlock│ │ HeaderBlock │ │ ArticleBlock │
|
|
120
|
+
│ │ │ │ │ (WASM markdown)│
|
|
121
|
+
└───────────────┘ └───────────────┘ └───────────────┘
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Development Guide
|
|
127
|
+
|
|
128
|
+
### Available Scripts
|
|
129
|
+
|
|
130
|
+
| Script | Command | Description |
|
|
131
|
+
|--------|---------|-------------|
|
|
132
|
+
| `dev` | `bun run dev` | Start dev server on port 3001 |
|
|
133
|
+
| `build` | `bun run build` | Production build |
|
|
134
|
+
| `start` | `bun run start` | Start production server |
|
|
135
|
+
| `lint` | `bun run lint` | Run Biome linter |
|
|
136
|
+
| `check-types` | `bun run check-types` | TypeScript type checking |
|
|
137
|
+
|
|
138
|
+
### Hot Reload Behavior
|
|
139
|
+
|
|
140
|
+
- **Page changes**: Instant hot reload
|
|
141
|
+
- **tRPC procedures**: Requires server restart
|
|
142
|
+
- **CSS changes**: Instant hot reload
|
|
143
|
+
- **Block components**: Instant hot reload
|
|
144
|
+
|
|
145
|
+
### Environment Variables
|
|
146
|
+
|
|
147
|
+
No environment variables are required for development. The app uses mock data from `seed/index.ts`.
|
|
148
|
+
|
|
149
|
+
For production with a real database, you would add:
|
|
38
150
|
|
|
39
151
|
```env
|
|
40
|
-
|
|
152
|
+
# .env.local (not required for development)
|
|
153
|
+
DATABASE_URL=your-database-url
|
|
41
154
|
```
|
|
42
155
|
|
|
43
156
|
---
|
|
@@ -46,193 +159,204 @@ NEXT_PUBLIC_WEBSITE_ID=<uuid> # or pass websiteId prop directly
|
|
|
46
159
|
|
|
47
160
|
### ComponentMap Pattern
|
|
48
161
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
// app/registry.ts
|
|
53
|
-
import type { BlockComponentRegistry } from 'cms-renderer/lib/types';
|
|
54
|
-
import HeroBlock from '@/components/hero-block';
|
|
55
|
-
import ArticleBlock from '@/components/article-block';
|
|
162
|
+
The website uses a registry-based pattern to map block types to React components:
|
|
56
163
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
164
|
+
```typescript
|
|
165
|
+
// components/blocks/index.ts
|
|
166
|
+
export const blockComponents: BlockComponentRegistry = {
|
|
167
|
+
navigation: NavigationBlock,
|
|
168
|
+
header: HeaderBlock,
|
|
169
|
+
article: ArticleBlock,
|
|
170
|
+
} as const;
|
|
61
171
|
```
|
|
62
172
|
|
|
63
|
-
|
|
173
|
+
The `BlockRenderer` uses this registry to dispatch blocks:
|
|
64
174
|
|
|
65
175
|
```tsx
|
|
66
|
-
|
|
176
|
+
// Render any block by type
|
|
177
|
+
<BlockRenderer block={{ type: 'header', content: {...} }} />
|
|
67
178
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
179
|
+
// Render a page of blocks
|
|
180
|
+
{page.blocks.map((block, i) => (
|
|
181
|
+
<BlockRenderer key={i} block={block} />
|
|
182
|
+
))}
|
|
71
183
|
```
|
|
72
184
|
|
|
73
|
-
|
|
185
|
+
### Block Types
|
|
74
186
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
187
|
+
| Block | Component | Content Fields |
|
|
188
|
+
|-------|-----------|----------------|
|
|
189
|
+
| `navigation` | `NavigationBlock` | logo, ariaLabel, links (3-level hierarchy) |
|
|
190
|
+
| `header` | `HeaderBlock` | headline, subheadline, backgroundImage, ctaButton, alignment |
|
|
191
|
+
| `article` | `ArticleBlock` | headline, author, publishedAt, body (markdown), tags |
|
|
78
192
|
|
|
79
|
-
|
|
193
|
+
### tRPC Integration
|
|
80
194
|
|
|
81
|
-
|
|
195
|
+
**Server Component** (recommended):
|
|
82
196
|
|
|
83
|
-
|
|
197
|
+
```tsx
|
|
198
|
+
// Direct procedure call in Server Components
|
|
199
|
+
import { createCaller } from '@/server/routers';
|
|
200
|
+
import { createContext } from '@/server/trpc';
|
|
84
201
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Fallback for all other paths
|
|
90
|
-
'article': ArticleBlockDefault,
|
|
91
|
-
};
|
|
202
|
+
export default async function Page({ params }: PageProps) {
|
|
203
|
+
const { slug } = await params;
|
|
204
|
+
const caller = createCaller(createContext());
|
|
205
|
+
const page = await caller.pages.getPage({ slug });
|
|
92
206
|
|
|
93
|
-
<
|
|
207
|
+
return <PageRenderer title={page.title} blocks={page.blocks} />;
|
|
208
|
+
}
|
|
94
209
|
```
|
|
95
210
|
|
|
96
|
-
|
|
211
|
+
**Client Component** (when needed):
|
|
97
212
|
|
|
98
|
-
|
|
213
|
+
```tsx
|
|
214
|
+
'use client';
|
|
215
|
+
import { trpc } from '@/lib/trpc';
|
|
99
216
|
|
|
100
|
-
|
|
217
|
+
function PageClient({ slug }: { slug: string }) {
|
|
218
|
+
const { data, isLoading } = trpc.pages.getPage.useQuery({ slug });
|
|
101
219
|
|
|
102
|
-
|
|
220
|
+
if (isLoading) return <div>Loading...</div>;
|
|
221
|
+
return <PageRenderer title={data.title} blocks={data.blocks} />;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### WASM Markdown Rendering
|
|
226
|
+
|
|
227
|
+
The `ArticleBlock` uses `@repo/markdown-wasm` for fast markdown-to-React conversion:
|
|
103
228
|
|
|
104
229
|
```tsx
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
export { generateMetadata };
|
|
110
|
-
|
|
111
|
-
export default function Page(props: any) {
|
|
112
|
-
return (
|
|
113
|
-
<ParametricRoutePage
|
|
114
|
-
{...props}
|
|
115
|
-
cmsUrl={process.env.CMS_URL!}
|
|
116
|
-
apiKey={process.env.CMS_API_KEY}
|
|
117
|
-
registry={registry}
|
|
118
|
-
/>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
230
|
+
import { MarkdownRenderer } from '@repo/markdown-wasm';
|
|
231
|
+
|
|
232
|
+
// Inside ArticleBlock
|
|
233
|
+
<MarkdownRenderer content={body} className="article-block__content" />
|
|
121
234
|
```
|
|
122
235
|
|
|
123
|
-
|
|
124
|
-
- Route lookup via `client.route.getByPath`
|
|
125
|
-
- Block fetching (parallel, failures are skipped)
|
|
126
|
-
- Article publish-state filtering
|
|
127
|
-
- `?edit_mode=true` — enables editable wrappers (for iframe preview)
|
|
128
|
-
- `?ai_preview=1` — renders AI-generated content variant at index 1
|
|
236
|
+
md4w parses markdown 2.5x faster than JavaScript alternatives and outputs a traversable AST that maps directly to React components.
|
|
129
237
|
|
|
130
238
|
---
|
|
131
239
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
When `disableEditable` is `false` (i.e. `edit_mode=true`), `BlockRenderer` wraps each block with `data-cms-block` and injects `data-cms-editable` spans around every text node that corresponds to a content field.
|
|
240
|
+
## API Routes Reference
|
|
135
241
|
|
|
136
|
-
|
|
242
|
+
### tRPC Endpoints
|
|
137
243
|
|
|
138
|
-
|
|
139
|
-
|
|
244
|
+
| Endpoint | Method | Input | Returns |
|
|
245
|
+
|----------|--------|-------|---------|
|
|
246
|
+
| `blocks.getNavigation` | Query | None | Navigation data |
|
|
247
|
+
| `blocks.getHeader` | Query | None | Header data |
|
|
248
|
+
| `blocks.getArticle` | Query | None | Article data |
|
|
249
|
+
| `pages.getPage` | Query | `{ slug: string }` | Page with blocks array |
|
|
140
250
|
|
|
141
|
-
|
|
251
|
+
### Testing Endpoints
|
|
142
252
|
|
|
143
|
-
```
|
|
144
|
-
|
|
253
|
+
```bash
|
|
254
|
+
# Start the dev server
|
|
255
|
+
bun run dev
|
|
145
256
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
257
|
+
# Test endpoints (in another terminal)
|
|
258
|
+
curl "http://localhost:3001/api/trpc/blocks.getNavigation"
|
|
259
|
+
curl "http://localhost:3001/api/trpc/blocks.getHeader"
|
|
260
|
+
curl "http://localhost:3001/api/trpc/pages.getPage?input=%7B%22json%22%3A%7B%22slug%22%3A%22demo%22%7D%7D"
|
|
150
261
|
```
|
|
151
262
|
|
|
152
263
|
---
|
|
153
264
|
|
|
154
|
-
|
|
265
|
+
## Testing
|
|
155
266
|
|
|
156
|
-
|
|
267
|
+
The website app does not currently have unit tests. Integration testing is done via the tRPC endpoints.
|
|
157
268
|
|
|
158
|
-
|
|
269
|
+
```bash
|
|
270
|
+
# Type check
|
|
271
|
+
bun run check-types
|
|
159
272
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{ type: 'cms-block-action', action: 'move-up' | 'move-down' | 'add-block' | 'delete', blockId }
|
|
163
|
-
{ type: 'cms-editable-click', blockId, blockType, contentPath } // null contentPath = block-level click
|
|
273
|
+
# Lint
|
|
274
|
+
bun run lint
|
|
164
275
|
```
|
|
165
276
|
|
|
166
|
-
|
|
277
|
+
Test files would live at:
|
|
278
|
+
- `components/blocks/*.test.tsx` for block components
|
|
279
|
+
- `server/routers/**/*.test.ts` for tRPC procedures
|
|
167
280
|
|
|
168
281
|
---
|
|
169
282
|
|
|
170
|
-
|
|
283
|
+
## Troubleshooting
|
|
171
284
|
|
|
172
|
-
|
|
285
|
+
### Port 3001 Already in Use
|
|
173
286
|
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<body>
|
|
182
|
-
{children}
|
|
183
|
-
<Refresher
|
|
184
|
-
websiteId={process.env.NEXT_PUBLIC_WEBSITE_ID!}
|
|
185
|
-
cmsUrl={process.env.CMS_URL!}
|
|
186
|
-
apiKey={process.env.CMS_API_KEY}
|
|
187
|
-
/>
|
|
188
|
-
</body>
|
|
189
|
-
</html>
|
|
190
|
-
);
|
|
191
|
-
}
|
|
287
|
+
```bash
|
|
288
|
+
# Find and kill the process
|
|
289
|
+
lsof -i :3001
|
|
290
|
+
kill -9 <PID>
|
|
291
|
+
|
|
292
|
+
# Or use a different port
|
|
293
|
+
bun run dev -- --port 3002
|
|
192
294
|
```
|
|
193
295
|
|
|
194
|
-
|
|
296
|
+
### Cannot Find Module '@repo/cms-schema'
|
|
195
297
|
|
|
196
|
-
|
|
298
|
+
```bash
|
|
299
|
+
# Run bun install from monorepo root
|
|
300
|
+
cd ../..
|
|
301
|
+
bun install
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### TypeScript Errors on First Run
|
|
305
|
+
|
|
306
|
+
The `.next/types` directory is generated on first dev server run:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
bun run dev # Run once to generate types
|
|
310
|
+
bun run check-types # Now type checking works
|
|
311
|
+
```
|
|
197
312
|
|
|
198
|
-
|
|
313
|
+
### WASM Module Not Found
|
|
199
314
|
|
|
200
|
-
|
|
201
|
-
import { createCmsProxy } from 'cms-renderer/lib/proxy';
|
|
315
|
+
If you see errors about md4w:
|
|
202
316
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
317
|
+
```bash
|
|
318
|
+
# Rebuild the markdown-wasm package
|
|
319
|
+
cd ../../packages/markdown-wasm
|
|
320
|
+
bun install
|
|
321
|
+
bun run build
|
|
207
322
|
```
|
|
208
323
|
|
|
324
|
+
### tRPC 'NOT_FOUND' Error
|
|
325
|
+
|
|
326
|
+
The mock data only includes pages with slugs: `demo`, `about`, `blog`. Requesting other slugs returns a 404.
|
|
327
|
+
|
|
209
328
|
---
|
|
210
329
|
|
|
211
|
-
##
|
|
330
|
+
## Related Documentation
|
|
212
331
|
|
|
213
|
-
|
|
332
|
+
- **Tutorial**: [`_docs/cms-website-integration/`](../../_docs/cms-website-integration/00-overview.md) - Full 11-chapter tutorial
|
|
333
|
+
- **CMS App**: [`apps/cms/README.md`](../cms/README.md) - Content authoring application
|
|
334
|
+
- **Schema Package**: [`packages/cms-schema/`](../../packages/cms-schema/) - Shared Zod schemas
|
|
335
|
+
- **Markdown Package**: [`packages/markdown-wasm/`](../../packages/markdown-wasm/) - WASM renderer
|
|
214
336
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
### Code Style
|
|
342
|
+
|
|
343
|
+
This project uses [Biome](https://biomejs.dev/) for linting and formatting:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
bun run lint # Check for issues
|
|
347
|
+
bun run lint --fix # Auto-fix issues
|
|
226
348
|
```
|
|
227
349
|
|
|
228
|
-
|
|
350
|
+
### Adding a New Block Type
|
|
351
|
+
|
|
352
|
+
1. Define the schema in `apps/cms/app/schemas/`
|
|
353
|
+
2. Add mock data to `seed/index.ts`
|
|
354
|
+
3. Create the component in `components/blocks/`
|
|
355
|
+
4. Add to `blockComponents` registry in `components/blocks/index.ts`
|
|
356
|
+
5. Add the type to `BlockData` union in `components/blocks/types.ts`
|
|
229
357
|
|
|
230
|
-
|
|
358
|
+
### Pull Requests
|
|
231
359
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
| `bun run dev` | Watch mode |
|
|
236
|
-
| `bun run check-types` | TypeScript type check |
|
|
237
|
-
| `bun run lint` | Biome lint |
|
|
238
|
-
| `bun test` | Run tests |
|
|
360
|
+
- Run `bun run check-types` before submitting
|
|
361
|
+
- Run `bun run lint` to ensure code style compliance
|
|
362
|
+
- Test the demo page at `/demo` renders correctly
|
|
@@ -36,6 +36,10 @@ type WalkVisitors = {
|
|
|
36
36
|
*/
|
|
37
37
|
onElement?: (info: ElementInfo) => React__default.ReactElement;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Recursively maps a ReactNode tree, allowing transformations of text nodes and/or elements.
|
|
41
|
+
* SSR-safe: does not touch DOM APIs.
|
|
42
|
+
*/
|
|
39
43
|
declare function walkReactNode(node: React__default.ReactNode, visitors: WalkVisitors, ctx?: {
|
|
40
44
|
path?: Array<string | number>;
|
|
41
45
|
parentType?: React__default.ElementType;
|
|
@@ -43,12 +47,8 @@ declare function walkReactNode(node: React__default.ReactNode, visitors: WalkVis
|
|
|
43
47
|
inSvg?: boolean;
|
|
44
48
|
}): React__default.ReactNode;
|
|
45
49
|
/**
|
|
46
|
-
* Renders the shared
|
|
47
|
-
*
|
|
48
|
-
* e.g. just before the block list in your route component.
|
|
49
|
-
*
|
|
50
|
-
* If you use `BlockRenderer` standalone (outside of `ParametricRoutePage`)
|
|
51
|
-
* you must render `<CmsEditableInit />` yourself somewhere on the page.
|
|
50
|
+
* Renders the shared CMS edit-mode styles and click-routing script.
|
|
51
|
+
* Place this once at the top of your page when edit_mode is active.
|
|
52
52
|
*/
|
|
53
53
|
declare function CmsEditableInit(): React__default.JSX.Element;
|
|
54
54
|
interface BlockRendererProps {
|
|
@@ -81,10 +81,13 @@ interface BlockRendererProps {
|
|
|
81
81
|
* Uses the ComponentMap pattern: the block's `type` field determines which
|
|
82
82
|
* component renders the block's `content`.
|
|
83
83
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
84
|
+
* In editable mode, wraps the block in a ClientEditableBlock that:
|
|
85
|
+
* - Stamps data-cms-block attributes directly on the component's root element
|
|
86
|
+
* - Injects data-cms-editable spans around matching text nodes
|
|
87
|
+
* - Portals the BlockToolbar into the component's root element
|
|
88
|
+
*
|
|
89
|
+
* Render CmsEditableInit once at the page level to include the shared styles
|
|
90
|
+
* and click-routing script.
|
|
88
91
|
*/
|
|
89
92
|
declare function BlockRenderer({ block, registry, disableEditable, routeParams, path, }: BlockRendererProps): React__default.JSX.Element | null;
|
|
90
93
|
|