uniweb 0.1.6 → 0.2.0
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 +291 -59
- package/package.json +3 -3
- package/src/index.js +298 -112
package/README.md
CHANGED
|
@@ -1,24 +1,116 @@
|
|
|
1
1
|
# uniweb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create a well-structured Vite + React site with content/code separation, file-based routing, localization, and structured content—out of the box. The architecture scales to dynamic content management and collaborative visual editing when you need it.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
-
Create a new Uniweb project
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx uniweb@latest create my-project --template workspace
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
This scaffolds a Vite project with everything wired—routing, state, and build pipeline ready. You get a site and a Foundation in a monorepo, pre-configured to work together.
|
|
7
|
+
Create a new Uniweb project:
|
|
14
8
|
|
|
15
9
|
```bash
|
|
10
|
+
npx uniweb@latest create my-project
|
|
16
11
|
cd my-project
|
|
17
12
|
pnpm install
|
|
18
13
|
pnpm dev
|
|
19
14
|
```
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
You get a workspace with two packages:
|
|
17
|
+
|
|
18
|
+
- **`site/`** — Content, pages, entry point
|
|
19
|
+
- **`foundation/`** — Your React components
|
|
20
|
+
|
|
21
|
+
Content authors work in markdown. Component authors work in React. Neither can break the other's work, and component updates flow to every site using them.
|
|
22
|
+
|
|
23
|
+
## What You Get
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
my-project/
|
|
27
|
+
├── site/ # Content + configuration
|
|
28
|
+
│ ├── pages/ # File-based routing
|
|
29
|
+
│ │ └── home/
|
|
30
|
+
│ │ ├── page.yml # Page metadata
|
|
31
|
+
│ │ └── 1-hero.md # Section content
|
|
32
|
+
│ ├── locales/ # i18n (mirrors pages/)
|
|
33
|
+
│ │ └── es/
|
|
34
|
+
│ └── public/ # Static assets
|
|
35
|
+
│
|
|
36
|
+
└── foundation/ # Your components
|
|
37
|
+
└── src/
|
|
38
|
+
└── components/
|
|
39
|
+
└── Hero/
|
|
40
|
+
└── index.jsx
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Pages are folders.** Create `pages/about/` with a markdown file inside → visit `/about`. That's the whole routing model.
|
|
44
|
+
|
|
45
|
+
### Content as Markdown
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
---
|
|
49
|
+
component: Hero
|
|
50
|
+
theme: dark
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
# Welcome
|
|
54
|
+
|
|
55
|
+
Build something great.
|
|
56
|
+
|
|
57
|
+
[Get Started](#)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Frontmatter specifies the component and configuration. The body contains the actual content—headings, paragraphs, links, images—which gets semantically parsed into structured data your component receives.
|
|
61
|
+
|
|
62
|
+
### Beyond Markdown
|
|
63
|
+
|
|
64
|
+
For content that doesn't fit markdown patterns—products, team members, events—use JSON blocks with schema tags:
|
|
65
|
+
|
|
66
|
+
````markdown
|
|
67
|
+
```json #team-member
|
|
68
|
+
{
|
|
69
|
+
"name": "Sarah Chen",
|
|
70
|
+
"role": "Lead Architect"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
````
|
|
74
|
+
|
|
75
|
+
Components receive validated, localized data. Natural content stays in markdown; structured data goes in JSON blocks.
|
|
76
|
+
|
|
77
|
+
### Components as React
|
|
78
|
+
|
|
79
|
+
```jsx
|
|
80
|
+
export function Hero({ content, params }) {
|
|
81
|
+
const { title } = content.main.header;
|
|
82
|
+
const { paragraphs, links } = content.main.body;
|
|
83
|
+
const { theme = 'light' } = params;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<section className={`py-20 text-center ${theme === 'dark' ? 'bg-gray-900 text-white' : ''}`}>
|
|
87
|
+
<h1 className="text-4xl font-bold">{title}</h1>
|
|
88
|
+
<p className="text-xl text-gray-600">{paragraphs[0]}</p>
|
|
89
|
+
{links[0] && (
|
|
90
|
+
<a href={links[0].url} className="mt-8 px-6 py-3 bg-blue-600 text-white rounded inline-block">
|
|
91
|
+
{links[0].text}
|
|
92
|
+
</a>
|
|
93
|
+
)}
|
|
94
|
+
</section>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Standard React. Standard Tailwind. The `{ content, params }` interface is only for *exposed* components—the ones content creators select in markdown frontmatter. Internal components (the majority of your codebase) use regular React props.
|
|
100
|
+
|
|
101
|
+
No framework to learn. Foundations are purpose-built component systems designed for a specific domain (marketing, documentation, learning, etc.). Sites are Vite apps that load content from markdown files. The CLI handles the wiring.
|
|
102
|
+
|
|
103
|
+
## The Bigger Picture
|
|
104
|
+
|
|
105
|
+
The structure you start with scales without rewrites:
|
|
106
|
+
|
|
107
|
+
1. **Single project** — One site, one component library. Most projects stay here.
|
|
108
|
+
2. **Multi-site** — One foundation powers multiple sites. Release it once, updates propagate automatically.
|
|
109
|
+
3. **Full platform** — [uniweb.app](https://uniweb.app) adds visual editing, live content management, and team collaboration. Your foundation plugs in and its components become native to the editor.
|
|
110
|
+
|
|
111
|
+
Start with local markdown files deployed anywhere. Connect to [uniweb.app](https://uniweb.app) when you're ready for dynamic content and team collaboration.
|
|
112
|
+
|
|
113
|
+
---
|
|
22
114
|
|
|
23
115
|
## Installation
|
|
24
116
|
|
|
@@ -42,9 +134,9 @@ uniweb create [project-name] [options]
|
|
|
42
134
|
|
|
43
135
|
**Options:**
|
|
44
136
|
|
|
45
|
-
| Option | Description
|
|
46
|
-
| ------------------- |
|
|
47
|
-
| `--template <type>` | Project template: `
|
|
137
|
+
| Option | Description |
|
|
138
|
+
| ------------------- | ------------------------------------- |
|
|
139
|
+
| `--template <type>` | Project template: `single` or `multi` |
|
|
48
140
|
|
|
49
141
|
**Examples:**
|
|
50
142
|
|
|
@@ -52,17 +144,14 @@ uniweb create [project-name] [options]
|
|
|
52
144
|
# Interactive prompts
|
|
53
145
|
uniweb create
|
|
54
146
|
|
|
55
|
-
# Create with specific name
|
|
147
|
+
# Create with specific name (defaults to single template)
|
|
56
148
|
uniweb create my-project
|
|
57
149
|
|
|
58
|
-
#
|
|
59
|
-
uniweb create my-
|
|
60
|
-
|
|
61
|
-
# Standalone foundation
|
|
62
|
-
uniweb create my-foundation --template foundation
|
|
150
|
+
# Single project with site + foundation
|
|
151
|
+
uniweb create my-project --template single
|
|
63
152
|
|
|
64
|
-
#
|
|
65
|
-
uniweb create my-
|
|
153
|
+
# Multi-site/foundation monorepo
|
|
154
|
+
uniweb create my-workspace --template multi
|
|
66
155
|
```
|
|
67
156
|
|
|
68
157
|
### `build`
|
|
@@ -75,9 +164,10 @@ uniweb build [options]
|
|
|
75
164
|
|
|
76
165
|
**Options:**
|
|
77
166
|
|
|
78
|
-
| Option
|
|
79
|
-
|
|
|
80
|
-
| `--target <type>`
|
|
167
|
+
| Option | Description |
|
|
168
|
+
| ------------------- | --------------------------------------------------------------------- |
|
|
169
|
+
| `--target <type>` | Build target: `foundation` or `site` (auto-detected if not specified) |
|
|
170
|
+
| `--platform <name>` | Deployment platform (e.g., `vercel`) for platform-specific output |
|
|
81
171
|
|
|
82
172
|
**Examples:**
|
|
83
173
|
|
|
@@ -90,56 +180,112 @@ uniweb build --target foundation
|
|
|
90
180
|
|
|
91
181
|
# Explicitly build as site
|
|
92
182
|
uniweb build --target site
|
|
183
|
+
|
|
184
|
+
# Build for Vercel deployment
|
|
185
|
+
uniweb build --platform vercel
|
|
93
186
|
```
|
|
94
187
|
|
|
95
188
|
## Project Templates
|
|
96
189
|
|
|
97
|
-
###
|
|
190
|
+
### Single (Default)
|
|
98
191
|
|
|
99
|
-
A
|
|
192
|
+
A minimal workspace with a site and foundation as sibling packages. This is the recommended starting point.
|
|
100
193
|
|
|
101
194
|
```
|
|
102
|
-
my-
|
|
103
|
-
├── package.json
|
|
104
|
-
├── pnpm-workspace.yaml
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
195
|
+
my-project/
|
|
196
|
+
├── package.json # Workspace root (includes workspaces field for npm)
|
|
197
|
+
├── pnpm-workspace.yaml # Pre-configured for evolution (see below)
|
|
198
|
+
│
|
|
199
|
+
├── site/ # Site package (content + entry)
|
|
200
|
+
│ ├── package.json
|
|
201
|
+
│ ├── vite.config.js
|
|
202
|
+
│ ├── index.html
|
|
203
|
+
│ ├── site.yml
|
|
204
|
+
│ ├── src/
|
|
205
|
+
│ │ └── main.jsx # Thin entry point
|
|
206
|
+
│ ├── pages/ # Content structure
|
|
207
|
+
│ │ └── home/
|
|
208
|
+
│ │ ├── page.yml
|
|
209
|
+
│ │ └── 1-hero.md
|
|
210
|
+
│ ├── locales/ # i18n (mirrors pages/)
|
|
211
|
+
│ │ └── es/
|
|
212
|
+
│ │ └── home/
|
|
213
|
+
│ └── public/ # Static assets
|
|
214
|
+
│
|
|
215
|
+
└── foundation/ # Foundation package (components)
|
|
216
|
+
├── package.json
|
|
217
|
+
├── vite.config.js
|
|
218
|
+
├── src/
|
|
219
|
+
│ ├── index.js # Component registry
|
|
220
|
+
│ ├── styles.css # Tailwind
|
|
221
|
+
│ ├── meta.js # Foundation metadata
|
|
222
|
+
│ └── components/
|
|
223
|
+
│ └── Hero/
|
|
224
|
+
│ ├── index.jsx
|
|
225
|
+
│ └── meta.js
|
|
226
|
+
└── ...
|
|
108
227
|
```
|
|
109
228
|
|
|
110
|
-
|
|
229
|
+
**Key characteristics:**
|
|
111
230
|
|
|
112
|
-
|
|
231
|
+
- **Convention-compliant** — Each package has its own `src/`
|
|
232
|
+
- **Clear dep boundaries** — Component libs → `foundation/package.json`, runtime → `site/package.json`
|
|
233
|
+
- **Zero extraction** — `foundation/` is already a complete, publishable package
|
|
234
|
+
- **Scales naturally** — Rename to `sites/marketing/` and `foundations/marketing/` when needed
|
|
235
|
+
|
|
236
|
+
### Multi
|
|
237
|
+
|
|
238
|
+
A monorepo for multi-site or multi-foundation development.
|
|
113
239
|
|
|
114
240
|
```
|
|
115
|
-
my-
|
|
116
|
-
├── package.json
|
|
117
|
-
├──
|
|
118
|
-
|
|
119
|
-
├──
|
|
120
|
-
│
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
241
|
+
my-workspace/
|
|
242
|
+
├── package.json # Workspace root (includes workspaces field for npm)
|
|
243
|
+
├── pnpm-workspace.yaml # Same config as site template
|
|
244
|
+
│
|
|
245
|
+
├── sites/
|
|
246
|
+
│ ├── marketing/ # Main marketing site
|
|
247
|
+
│ │ ├── package.json
|
|
248
|
+
│ │ ├── site.yml
|
|
249
|
+
│ │ ├── src/
|
|
250
|
+
│ │ ├── pages/
|
|
251
|
+
│ │ └── ...
|
|
252
|
+
│ └── docs/ # Documentation site
|
|
253
|
+
│
|
|
254
|
+
└── foundations/
|
|
255
|
+
├── marketing/ # Marketing foundation
|
|
256
|
+
│ ├── package.json
|
|
257
|
+
│ ├── src/components/
|
|
258
|
+
│ └── ...
|
|
259
|
+
└── documentation/ # Documentation foundation
|
|
125
260
|
```
|
|
126
261
|
|
|
127
|
-
|
|
262
|
+
Use this when you need:
|
|
128
263
|
|
|
129
|
-
|
|
264
|
+
- Multiple sites sharing foundations
|
|
265
|
+
- Multiple foundations for different purposes
|
|
266
|
+
- A testing site for foundation development
|
|
130
267
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
268
|
+
## Dependency Management
|
|
269
|
+
|
|
270
|
+
Each package manages its own dependencies:
|
|
271
|
+
|
|
272
|
+
**`site/package.json`:**
|
|
273
|
+
|
|
274
|
+
- `@uniweb/runtime`
|
|
275
|
+
- `@my-project/foundation` (workspace link)
|
|
276
|
+
- Vite, Tailwind (dev)
|
|
277
|
+
|
|
278
|
+
**`foundation/package.json`:**
|
|
279
|
+
|
|
280
|
+
- Component libraries (carousel, icons, etc.)
|
|
281
|
+
- React as peer dependency
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Add component dependency
|
|
285
|
+
cd foundation && pnpm add embla-carousel
|
|
286
|
+
|
|
287
|
+
# Site references foundation via workspace
|
|
288
|
+
# No path gymnastics needed
|
|
143
289
|
```
|
|
144
290
|
|
|
145
291
|
## Foundation Build Process
|
|
@@ -165,10 +311,96 @@ dist/
|
|
|
165
311
|
└── [preset].webp
|
|
166
312
|
```
|
|
167
313
|
|
|
314
|
+
## Workspace Configuration
|
|
315
|
+
|
|
316
|
+
Both templates use the same unified workspace configuration:
|
|
317
|
+
|
|
318
|
+
```yaml
|
|
319
|
+
# pnpm-workspace.yaml
|
|
320
|
+
packages:
|
|
321
|
+
- "site"
|
|
322
|
+
- "foundation"
|
|
323
|
+
- "sites/*"
|
|
324
|
+
- "foundations/*"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
// package.json (workspaces field for npm compatibility)
|
|
329
|
+
{
|
|
330
|
+
"workspaces": ["site", "foundation", "sites/*", "foundations/*"]
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
This means **no config changes when evolving your project**.
|
|
335
|
+
|
|
336
|
+
## Evolving Your Project
|
|
337
|
+
|
|
338
|
+
The workspace is pre-configured for growth. Choose your path:
|
|
339
|
+
|
|
340
|
+
**Add alongside existing structure:**
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
# Keep site/ and foundation/, add more in sites/ and foundations/
|
|
344
|
+
mkdir -p sites/docs
|
|
345
|
+
mkdir -p foundations/documentation
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Or migrate to multi-site structure:**
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# Move and rename by purpose
|
|
352
|
+
mv site sites/marketing
|
|
353
|
+
mv foundation foundations/marketing
|
|
354
|
+
|
|
355
|
+
# Update package names and workspace dependencies in package.json files
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Both patterns work simultaneously—evolve gradually as needed.
|
|
359
|
+
|
|
360
|
+
## Releasing a Foundation
|
|
361
|
+
|
|
362
|
+
Publish your foundation to [uniweb.app](https://uniweb.app) to make it available for your sites:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
uniweb login # First time only
|
|
366
|
+
uniweb build
|
|
367
|
+
uniweb publish # Publishes the foundation in the current directory
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Each release creates a new version. Sites link to foundations at runtime and control their own update strategy—automatic, minor-only, patch-only, or pinned to a specific version. You own your foundations and license them to sites—yours or your clients'.
|
|
371
|
+
|
|
372
|
+
You can also publish to npm:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npm publish
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## FAQ
|
|
379
|
+
|
|
380
|
+
**How is this different from MDX?**
|
|
381
|
+
|
|
382
|
+
MDX blends markdown and JSX—content authors write code. Uniweb keeps them separate: content stays in markdown, components stay in React. Content authors can't break components, and component updates don't require content changes.
|
|
383
|
+
|
|
384
|
+
**How is this different from Astro?**
|
|
385
|
+
|
|
386
|
+
Astro is a static site generator. Uniweb is a content architecture that works with any deployment (static, SSR, or platform-managed). The Foundation model means components are reusable across sites and ready for visual editing.
|
|
387
|
+
|
|
388
|
+
**Do I need uniweb.app?**
|
|
389
|
+
|
|
390
|
+
No. Local markdown files work great for developer-managed sites. The platform adds dynamic content, visual editing, and team collaboration when you need it.
|
|
391
|
+
|
|
392
|
+
**Is this SEO-friendly?**
|
|
393
|
+
|
|
394
|
+
Yes. Content is pre-embedded in the initial HTML—no fetch waterfalls, no layout shifts, instant interaction. Meta tags are generated per page for proper social sharing previews. SSG and SSR are also supported.
|
|
395
|
+
|
|
396
|
+
**What about dynamic routes?**
|
|
397
|
+
|
|
398
|
+
Pages can define data sources that auto-generate subroutes. A `/blog` page can have an index (listing) and a `[slug]` template that renders each post. No manual folders for every entry.
|
|
399
|
+
|
|
168
400
|
## Related Packages
|
|
169
401
|
|
|
170
|
-
- [`@uniweb/build`](https://github.com/uniweb/build)
|
|
171
|
-
- [`@uniweb/runtime`](https://github.com/uniweb/runtime)
|
|
402
|
+
- [`@uniweb/build`](https://github.com/uniweb/build) — Foundation build tooling
|
|
403
|
+
- [`@uniweb/runtime`](https://github.com/uniweb/runtime) — Runtime loader for sites
|
|
172
404
|
|
|
173
405
|
## License
|
|
174
406
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"uniweb": "
|
|
7
|
+
"uniweb": "src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"src"
|
package/src/index.js
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
9
|
* npx uniweb create [project-name]
|
|
10
|
-
* npx uniweb create --template site
|
|
11
|
-
* npx uniweb create --template
|
|
12
|
-
* npx uniweb create --template workspace
|
|
10
|
+
* npx uniweb create --template single # site/ + foundation/ (default)
|
|
11
|
+
* npx uniweb create --template multi # sites/* + foundations/*
|
|
13
12
|
* npx uniweb build
|
|
14
13
|
* npx uniweb build --target foundation
|
|
15
14
|
*/
|
|
@@ -51,17 +50,13 @@ function title(message) {
|
|
|
51
50
|
|
|
52
51
|
// Template definitions
|
|
53
52
|
const templates = {
|
|
54
|
-
|
|
55
|
-
name: '
|
|
56
|
-
description: '
|
|
53
|
+
single: {
|
|
54
|
+
name: 'Single Project',
|
|
55
|
+
description: 'One site + one foundation in site/ and foundation/ (recommended)',
|
|
57
56
|
},
|
|
58
|
-
|
|
59
|
-
name: 'Site
|
|
60
|
-
description: '
|
|
61
|
-
},
|
|
62
|
-
foundation: {
|
|
63
|
-
name: 'Foundation Only',
|
|
64
|
-
description: 'A standalone foundation package',
|
|
57
|
+
multi: {
|
|
58
|
+
name: 'Multi-Site Project',
|
|
59
|
+
description: 'Multiple sites and foundations in sites/* and foundations/*',
|
|
65
60
|
},
|
|
66
61
|
}
|
|
67
62
|
|
|
@@ -126,19 +121,14 @@ async function main() {
|
|
|
126
121
|
message: 'What would you like to create?',
|
|
127
122
|
choices: [
|
|
128
123
|
{
|
|
129
|
-
title: templates.
|
|
130
|
-
description: templates.
|
|
131
|
-
value: '
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
title: templates.site.name,
|
|
135
|
-
description: templates.site.description,
|
|
136
|
-
value: 'site',
|
|
124
|
+
title: templates.single.name,
|
|
125
|
+
description: templates.single.description,
|
|
126
|
+
value: 'single',
|
|
137
127
|
},
|
|
138
128
|
{
|
|
139
|
-
title: templates.
|
|
140
|
-
description: templates.
|
|
141
|
-
value: '
|
|
129
|
+
title: templates.multi.name,
|
|
130
|
+
description: templates.multi.description,
|
|
131
|
+
value: 'multi',
|
|
142
132
|
},
|
|
143
133
|
],
|
|
144
134
|
},
|
|
@@ -169,14 +159,11 @@ async function main() {
|
|
|
169
159
|
|
|
170
160
|
// Generate project based on template
|
|
171
161
|
switch (templateType) {
|
|
172
|
-
case '
|
|
173
|
-
await
|
|
174
|
-
break
|
|
175
|
-
case 'site':
|
|
176
|
-
await createSite(projectDir, projectName)
|
|
162
|
+
case 'single':
|
|
163
|
+
await createSingleProject(projectDir, projectName)
|
|
177
164
|
break
|
|
178
|
-
case '
|
|
179
|
-
await
|
|
165
|
+
case 'multi':
|
|
166
|
+
await createMultiProject(projectDir, projectName)
|
|
180
167
|
break
|
|
181
168
|
}
|
|
182
169
|
|
|
@@ -186,15 +173,7 @@ async function main() {
|
|
|
186
173
|
log(`Next steps:\n`)
|
|
187
174
|
log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
188
175
|
log(` ${colors.cyan}pnpm install${colors.reset}`)
|
|
189
|
-
|
|
190
|
-
if (templateType === 'workspace') {
|
|
191
|
-
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
192
|
-
} else if (templateType === 'site') {
|
|
193
|
-
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
194
|
-
} else {
|
|
195
|
-
log(` ${colors.cyan}pnpm build${colors.reset}`)
|
|
196
|
-
}
|
|
197
|
-
|
|
176
|
+
log(` ${colors.cyan}pnpm dev${colors.reset}`)
|
|
198
177
|
log('')
|
|
199
178
|
}
|
|
200
179
|
|
|
@@ -210,50 +189,62 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
210
189
|
build Build the current project
|
|
211
190
|
|
|
212
191
|
${colors.bright}Create Options:${colors.reset}
|
|
213
|
-
--template <type> Project template (
|
|
192
|
+
--template <type> Project template (single, multi)
|
|
214
193
|
|
|
215
194
|
${colors.bright}Build Options:${colors.reset}
|
|
216
195
|
--target <type> Build target (foundation, site) - auto-detected if not specified
|
|
196
|
+
--platform <name> Deployment platform (e.g., vercel) for platform-specific output
|
|
217
197
|
|
|
218
198
|
${colors.bright}Examples:${colors.reset}
|
|
219
199
|
npx uniweb create my-project
|
|
220
|
-
npx uniweb create my-
|
|
221
|
-
npx uniweb create my-
|
|
200
|
+
npx uniweb create my-project --template single
|
|
201
|
+
npx uniweb create my-workspace --template multi
|
|
222
202
|
npx uniweb build
|
|
223
203
|
npx uniweb build --target foundation
|
|
224
204
|
|
|
225
205
|
${colors.bright}Templates:${colors.reset}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
foundation Standalone foundation package
|
|
206
|
+
single One site + one foundation in site/ and foundation/ (default)
|
|
207
|
+
multi Multiple sites and foundations in sites/* and foundations/*
|
|
229
208
|
`)
|
|
230
209
|
}
|
|
231
210
|
|
|
232
211
|
// Template generators
|
|
233
|
-
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Creates the common project base: package.json, pnpm-workspace.yaml, .gitignore
|
|
215
|
+
* Both single and multi templates share the same workspace configuration.
|
|
216
|
+
*/
|
|
217
|
+
function createProjectBase(projectDir, projectName, defaultFilter) {
|
|
234
218
|
mkdirSync(projectDir, { recursive: true })
|
|
235
219
|
|
|
236
|
-
// Root package.json
|
|
220
|
+
// Root package.json (workspaces field for npm compatibility)
|
|
237
221
|
writeJSON(join(projectDir, 'package.json'), {
|
|
238
222
|
name: projectName,
|
|
239
223
|
version: '0.1.0',
|
|
240
224
|
private: true,
|
|
241
225
|
type: 'module',
|
|
242
|
-
workspaces: ['packages/*'],
|
|
243
226
|
scripts: {
|
|
244
|
-
dev:
|
|
245
|
-
'dev:runtime':
|
|
246
|
-
|
|
247
|
-
build: 'pnpm build:foundation && pnpm --filter site build',
|
|
227
|
+
dev: `pnpm --filter ${defaultFilter} dev`,
|
|
228
|
+
'dev:runtime': `VITE_FOUNDATION_MODE=runtime pnpm --filter ${defaultFilter} dev`,
|
|
229
|
+
build: 'pnpm -r build',
|
|
248
230
|
},
|
|
231
|
+
workspaces: [
|
|
232
|
+
'site',
|
|
233
|
+
'foundation',
|
|
234
|
+
'sites/*',
|
|
235
|
+
'foundations/*',
|
|
236
|
+
],
|
|
249
237
|
pnpm: {
|
|
250
238
|
onlyBuiltDependencies: ['esbuild', 'sharp'],
|
|
251
239
|
},
|
|
252
240
|
})
|
|
253
241
|
|
|
254
|
-
// pnpm-workspace.yaml
|
|
242
|
+
// pnpm-workspace.yaml (all patterns for seamless evolution)
|
|
255
243
|
writeFile(join(projectDir, 'pnpm-workspace.yaml'), `packages:
|
|
256
|
-
- '
|
|
244
|
+
- 'site'
|
|
245
|
+
- 'foundation'
|
|
246
|
+
- 'sites/*'
|
|
247
|
+
- 'foundations/*'
|
|
257
248
|
`)
|
|
258
249
|
|
|
259
250
|
// .gitignore
|
|
@@ -262,11 +253,19 @@ dist
|
|
|
262
253
|
.DS_Store
|
|
263
254
|
*.local
|
|
264
255
|
`)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Creates a single project with site/ and foundation/ as sibling packages.
|
|
260
|
+
* This is the default template for new projects.
|
|
261
|
+
*/
|
|
262
|
+
async function createSingleProject(projectDir, projectName) {
|
|
263
|
+
createProjectBase(projectDir, projectName, 'site')
|
|
265
264
|
|
|
266
265
|
// README.md
|
|
267
266
|
writeFile(join(projectDir, 'README.md'), `# ${projectName}
|
|
268
267
|
|
|
269
|
-
|
|
268
|
+
Structured Vite + React, ready to go.
|
|
270
269
|
|
|
271
270
|
## Quick Start
|
|
272
271
|
|
|
@@ -281,16 +280,21 @@ Open [http://localhost:3000](http://localhost:3000) to see your site.
|
|
|
281
280
|
|
|
282
281
|
\`\`\`
|
|
283
282
|
${projectName}/
|
|
284
|
-
├──
|
|
285
|
-
│ ├──
|
|
286
|
-
│ │
|
|
287
|
-
│ │
|
|
288
|
-
│ │
|
|
289
|
-
│
|
|
290
|
-
│
|
|
291
|
-
│
|
|
292
|
-
│
|
|
293
|
-
|
|
283
|
+
├── site/ # Content + configuration
|
|
284
|
+
│ ├── pages/ # File-based routing
|
|
285
|
+
│ │ └── home/
|
|
286
|
+
│ │ ├── page.yml # Page metadata
|
|
287
|
+
│ │ └── 1-hero.md # Section content
|
|
288
|
+
│ ├── locales/ # i18n (mirrors pages/)
|
|
289
|
+
│ ├── src/ # Site entry point
|
|
290
|
+
│ └── public/ # Static assets
|
|
291
|
+
│
|
|
292
|
+
├── foundation/ # Your components
|
|
293
|
+
│ └── src/
|
|
294
|
+
│ ├── components/ # React components
|
|
295
|
+
│ ├── meta.js # Foundation metadata
|
|
296
|
+
│ └── styles.css # Tailwind styles
|
|
297
|
+
│
|
|
294
298
|
├── package.json
|
|
295
299
|
└── pnpm-workspace.yaml
|
|
296
300
|
\`\`\`
|
|
@@ -303,8 +307,8 @@ ${projectName}/
|
|
|
303
307
|
pnpm dev
|
|
304
308
|
\`\`\`
|
|
305
309
|
|
|
306
|
-
Edit components in \`
|
|
307
|
-
Edit content in \`
|
|
310
|
+
Edit components in \`foundation/src/components/\` and see changes instantly via HMR.
|
|
311
|
+
Edit content in \`site/pages/\` to add or modify pages.
|
|
308
312
|
|
|
309
313
|
### Runtime Loading Mode
|
|
310
314
|
|
|
@@ -318,24 +322,23 @@ Use this to debug issues that only appear in production.
|
|
|
318
322
|
## Building for Production
|
|
319
323
|
|
|
320
324
|
\`\`\`bash
|
|
321
|
-
# Build both foundation and site
|
|
322
325
|
pnpm build
|
|
323
326
|
\`\`\`
|
|
324
327
|
|
|
325
|
-
Output
|
|
326
|
-
- \`
|
|
327
|
-
- \`
|
|
328
|
+
**Output:**
|
|
329
|
+
- \`foundation/dist/\` — Bundled components, CSS, and schema.json
|
|
330
|
+
- \`site/dist/\` — Production-ready static site
|
|
328
331
|
|
|
329
332
|
## Adding Components
|
|
330
333
|
|
|
331
|
-
1. Create a new folder in \`
|
|
334
|
+
1. Create a new folder in \`foundation/src/components/YourComponent/\`
|
|
332
335
|
2. Add \`index.jsx\` with your React component
|
|
333
336
|
3. Add \`meta.js\` describing the component's content slots and options
|
|
334
|
-
4. Export from \`
|
|
337
|
+
4. Export from \`foundation/src/index.js\`
|
|
335
338
|
|
|
336
339
|
## Adding Pages
|
|
337
340
|
|
|
338
|
-
1. Create a folder in \`
|
|
341
|
+
1. Create a folder in \`site/pages/your-page/\`
|
|
339
342
|
2. Add \`page.yml\` with page metadata
|
|
340
343
|
3. Add markdown files (\`1-section.md\`, \`2-section.md\`, etc.) for each section
|
|
341
344
|
|
|
@@ -344,24 +347,48 @@ Each markdown file specifies which component to use:
|
|
|
344
347
|
\`\`\`markdown
|
|
345
348
|
---
|
|
346
349
|
component: Hero
|
|
347
|
-
title: Your Title
|
|
348
|
-
subtitle: Your subtitle
|
|
349
350
|
---
|
|
350
351
|
|
|
351
|
-
|
|
352
|
+
# Your Title
|
|
353
|
+
|
|
354
|
+
Your subtitle or description here.
|
|
355
|
+
|
|
356
|
+
[Get Started](#)
|
|
352
357
|
\`\`\`
|
|
353
358
|
|
|
354
|
-
##
|
|
359
|
+
## Scaling Up
|
|
355
360
|
|
|
356
|
-
|
|
361
|
+
The workspace is pre-configured for growth—no config changes needed.
|
|
357
362
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
363
|
+
**Add a second site** (keep existing structure):
|
|
364
|
+
|
|
365
|
+
\`\`\`bash
|
|
366
|
+
mkdir -p sites/docs
|
|
367
|
+
# Create your docs site in sites/docs/
|
|
362
368
|
\`\`\`
|
|
363
369
|
|
|
364
|
-
|
|
370
|
+
**Or migrate to multi-site structure**:
|
|
371
|
+
|
|
372
|
+
\`\`\`bash
|
|
373
|
+
# Move and rename by purpose
|
|
374
|
+
mv site sites/marketing
|
|
375
|
+
mv foundation foundations/marketing
|
|
376
|
+
|
|
377
|
+
# Update package names in package.json files
|
|
378
|
+
# Update dependencies to reference new names
|
|
379
|
+
\`\`\`
|
|
380
|
+
|
|
381
|
+
Both patterns work simultaneously—evolve gradually as needed.
|
|
382
|
+
|
|
383
|
+
## Publishing Your Foundation
|
|
384
|
+
|
|
385
|
+
Your \`foundation/\` is already a complete package:
|
|
386
|
+
|
|
387
|
+
\`\`\`bash
|
|
388
|
+
cd foundation
|
|
389
|
+
npx uniweb build
|
|
390
|
+
npm publish
|
|
391
|
+
\`\`\`
|
|
365
392
|
|
|
366
393
|
## What is Uniweb?
|
|
367
394
|
|
|
@@ -372,22 +399,145 @@ Sites provide content that flows through Foundations.
|
|
|
372
399
|
Learn more:
|
|
373
400
|
- [Uniweb on GitHub](https://github.com/uniweb)
|
|
374
401
|
- [CLI Documentation](https://github.com/uniweb/cli)
|
|
375
|
-
- [uniweb.app](https://uniweb.app) —
|
|
402
|
+
- [uniweb.app](https://uniweb.app) — Visual editing platform
|
|
376
403
|
|
|
377
404
|
`)
|
|
378
405
|
|
|
379
406
|
// Create site package
|
|
380
|
-
await createSite(join(projectDir, '
|
|
407
|
+
await createSite(join(projectDir, 'site'), 'site', true)
|
|
381
408
|
|
|
382
409
|
// Create foundation package
|
|
383
|
-
await createFoundation(join(projectDir, '
|
|
410
|
+
await createFoundation(join(projectDir, 'foundation'), 'foundation', true)
|
|
384
411
|
|
|
385
412
|
// Update site to reference workspace foundation
|
|
386
|
-
const sitePackageJson = join(projectDir, '
|
|
413
|
+
const sitePackageJson = join(projectDir, 'site/package.json')
|
|
387
414
|
const sitePkg = JSON.parse(readFile(sitePackageJson))
|
|
388
415
|
sitePkg.dependencies['foundation'] = 'workspace:*'
|
|
389
416
|
writeJSON(sitePackageJson, sitePkg)
|
|
390
417
|
|
|
418
|
+
success(`Created project: ${projectName}`)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Creates a multi-site/foundation workspace with sites/ and foundations/ directories.
|
|
423
|
+
* Used for larger projects with multiple sites sharing foundations.
|
|
424
|
+
*/
|
|
425
|
+
async function createMultiProject(projectDir, projectName) {
|
|
426
|
+
createProjectBase(projectDir, projectName, 'marketing')
|
|
427
|
+
|
|
428
|
+
// README.md
|
|
429
|
+
writeFile(join(projectDir, 'README.md'), `# ${projectName}
|
|
430
|
+
|
|
431
|
+
A Uniweb workspace for multiple sites and foundations.
|
|
432
|
+
|
|
433
|
+
## Quick Start
|
|
434
|
+
|
|
435
|
+
\`\`\`bash
|
|
436
|
+
pnpm install
|
|
437
|
+
pnpm dev
|
|
438
|
+
\`\`\`
|
|
439
|
+
|
|
440
|
+
Open [http://localhost:3000](http://localhost:3000) to see the marketing site.
|
|
441
|
+
|
|
442
|
+
## Project Structure
|
|
443
|
+
|
|
444
|
+
\`\`\`
|
|
445
|
+
${projectName}/
|
|
446
|
+
├── sites/
|
|
447
|
+
│ └── marketing/ # Main marketing site
|
|
448
|
+
│ ├── pages/ # Content pages
|
|
449
|
+
│ ├── locales/ # i18n
|
|
450
|
+
│ └── src/
|
|
451
|
+
│
|
|
452
|
+
├── foundations/
|
|
453
|
+
│ └── marketing/ # Marketing foundation
|
|
454
|
+
│ └── src/
|
|
455
|
+
│ ├── components/ # React components
|
|
456
|
+
│ └── styles.css # Tailwind styles
|
|
457
|
+
│
|
|
458
|
+
├── package.json
|
|
459
|
+
└── pnpm-workspace.yaml
|
|
460
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
## Development
|
|
463
|
+
|
|
464
|
+
\`\`\`bash
|
|
465
|
+
# Run the marketing site (default)
|
|
466
|
+
pnpm dev
|
|
467
|
+
|
|
468
|
+
# Run a specific site
|
|
469
|
+
pnpm --filter docs dev
|
|
470
|
+
|
|
471
|
+
# Runtime loading mode (tests production behavior)
|
|
472
|
+
pnpm dev:runtime
|
|
473
|
+
\`\`\`
|
|
474
|
+
|
|
475
|
+
## Adding More Sites
|
|
476
|
+
|
|
477
|
+
Create a new site in \`sites/\`:
|
|
478
|
+
|
|
479
|
+
\`\`\`bash
|
|
480
|
+
mkdir -p sites/docs
|
|
481
|
+
# Copy structure from sites/marketing or create manually
|
|
482
|
+
\`\`\`
|
|
483
|
+
|
|
484
|
+
Update \`sites/docs/site.yml\` to specify which foundation:
|
|
485
|
+
|
|
486
|
+
\`\`\`yaml
|
|
487
|
+
name: docs
|
|
488
|
+
defaultLanguage: en
|
|
489
|
+
foundation: documentation # Or marketing, or any foundation
|
|
490
|
+
\`\`\`
|
|
491
|
+
|
|
492
|
+
## Adding More Foundations
|
|
493
|
+
|
|
494
|
+
Create a new foundation in \`foundations/\`:
|
|
495
|
+
|
|
496
|
+
\`\`\`bash
|
|
497
|
+
mkdir -p foundations/documentation
|
|
498
|
+
# Build components for documentation use case
|
|
499
|
+
\`\`\`
|
|
500
|
+
|
|
501
|
+
Name foundations by purpose: marketing, documentation, learning, etc.
|
|
502
|
+
|
|
503
|
+
## Building for Production
|
|
504
|
+
|
|
505
|
+
\`\`\`bash
|
|
506
|
+
# Build everything
|
|
507
|
+
pnpm build
|
|
508
|
+
|
|
509
|
+
# Build specific packages
|
|
510
|
+
pnpm --filter marketing build
|
|
511
|
+
pnpm --filter foundations/marketing build
|
|
512
|
+
\`\`\`
|
|
513
|
+
|
|
514
|
+
## Learn More
|
|
515
|
+
|
|
516
|
+
- [Uniweb on GitHub](https://github.com/uniweb)
|
|
517
|
+
- [uniweb.app](https://uniweb.app) — Visual editing platform
|
|
518
|
+
|
|
519
|
+
`)
|
|
520
|
+
|
|
521
|
+
// Create first site in sites/marketing
|
|
522
|
+
await createSite(join(projectDir, 'sites/marketing'), 'marketing', true)
|
|
523
|
+
|
|
524
|
+
// Create first foundation in foundations/marketing
|
|
525
|
+
await createFoundation(join(projectDir, 'foundations/marketing'), 'marketing', true)
|
|
526
|
+
|
|
527
|
+
// Update site to reference workspace foundation
|
|
528
|
+
const sitePackageJson = join(projectDir, 'sites/marketing/package.json')
|
|
529
|
+
const sitePkg = JSON.parse(readFile(sitePackageJson))
|
|
530
|
+
sitePkg.dependencies['marketing'] = 'workspace:*'
|
|
531
|
+
writeJSON(sitePackageJson, sitePkg)
|
|
532
|
+
|
|
533
|
+
// Update site.yml to reference the marketing foundation
|
|
534
|
+
writeFile(join(projectDir, 'sites/marketing/site.yml'), `name: marketing
|
|
535
|
+
defaultLanguage: en
|
|
536
|
+
|
|
537
|
+
# Foundation to use for this site
|
|
538
|
+
foundation: marketing
|
|
539
|
+
`)
|
|
540
|
+
|
|
391
541
|
success(`Created workspace: ${projectName}`)
|
|
392
542
|
}
|
|
393
543
|
|
|
@@ -569,13 +719,13 @@ order: 1
|
|
|
569
719
|
// pages/home/1-hero.md
|
|
570
720
|
writeFile(join(projectDir, 'pages/home/1-hero.md'), `---
|
|
571
721
|
component: Hero
|
|
572
|
-
title: Welcome to ${projectName}
|
|
573
|
-
subtitle: Built with Uniweb and Vite
|
|
574
|
-
ctaText: Get Started
|
|
575
|
-
ctaUrl: "#"
|
|
576
722
|
---
|
|
577
723
|
|
|
578
|
-
|
|
724
|
+
# Welcome to ${projectName}
|
|
725
|
+
|
|
726
|
+
Built with Uniweb and Vite.
|
|
727
|
+
|
|
728
|
+
[Get Started](#)
|
|
579
729
|
`)
|
|
580
730
|
|
|
581
731
|
// README.md (only for standalone site, not workspace)
|
|
@@ -623,19 +773,22 @@ order: 2
|
|
|
623
773
|
\`\`\`markdown
|
|
624
774
|
---
|
|
625
775
|
component: Hero
|
|
626
|
-
|
|
627
|
-
subtitle: Section subtitle
|
|
776
|
+
theme: dark
|
|
628
777
|
---
|
|
629
778
|
|
|
630
|
-
|
|
779
|
+
# Section Title
|
|
780
|
+
|
|
781
|
+
Section description here.
|
|
782
|
+
|
|
783
|
+
[Call to Action](#)
|
|
631
784
|
\`\`\`
|
|
632
785
|
|
|
633
786
|
## How It Works
|
|
634
787
|
|
|
635
788
|
- Each folder in \`pages/\` becomes a route (\`/home\`, \`/about\`, etc.)
|
|
636
789
|
- Section files are numbered to control order (\`1-*.md\`, \`2-*.md\`)
|
|
637
|
-
-
|
|
638
|
-
-
|
|
790
|
+
- Frontmatter specifies the component and configuration parameters
|
|
791
|
+
- Content in the markdown body is semantically parsed into structured data
|
|
639
792
|
|
|
640
793
|
## Configuration
|
|
641
794
|
|
|
@@ -847,22 +1000,36 @@ export { default } from './index.js'
|
|
|
847
1000
|
// src/components/Hero/index.jsx
|
|
848
1001
|
writeFile(join(projectDir, 'src/components/Hero/index.jsx'), `import React from 'react'
|
|
849
1002
|
|
|
850
|
-
export function Hero({ content }) {
|
|
851
|
-
|
|
1003
|
+
export function Hero({ content, params }) {
|
|
1004
|
+
// Extract from semantic parser structure
|
|
1005
|
+
const { title } = content.main?.header || {}
|
|
1006
|
+
const { paragraphs = [], links = [] } = content.main?.body || {}
|
|
1007
|
+
const { theme = 'light' } = params || {}
|
|
1008
|
+
|
|
1009
|
+
const isDark = theme === 'dark'
|
|
1010
|
+
const cta = links[0]
|
|
852
1011
|
|
|
853
1012
|
return (
|
|
854
|
-
<section className=
|
|
1013
|
+
<section className={\`py-20 px-6 \${isDark ? 'bg-gradient-to-br from-primary to-blue-700 text-white' : 'bg-gray-50'}\`}>
|
|
855
1014
|
<div className="max-w-4xl mx-auto text-center">
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1015
|
+
{title && (
|
|
1016
|
+
<h1 className="text-4xl md:text-5xl font-bold mb-6">{title}</h1>
|
|
1017
|
+
)}
|
|
1018
|
+
{paragraphs[0] && (
|
|
1019
|
+
<p className={\`text-lg mb-8 \${isDark ? 'text-blue-100' : 'text-gray-600'}\`}>
|
|
1020
|
+
{paragraphs[0]}
|
|
1021
|
+
</p>
|
|
859
1022
|
)}
|
|
860
|
-
{
|
|
1023
|
+
{cta && (
|
|
861
1024
|
<a
|
|
862
|
-
href={
|
|
863
|
-
className=
|
|
1025
|
+
href={cta.url}
|
|
1026
|
+
className={\`inline-block px-6 py-3 font-semibold rounded-lg transition-colors \${
|
|
1027
|
+
isDark
|
|
1028
|
+
? 'bg-white text-primary hover:bg-blue-50'
|
|
1029
|
+
: 'bg-primary text-white hover:bg-blue-700'
|
|
1030
|
+
}\`}
|
|
864
1031
|
>
|
|
865
|
-
{
|
|
1032
|
+
{cta.text}
|
|
866
1033
|
</a>
|
|
867
1034
|
)}
|
|
868
1035
|
</div>
|
|
@@ -876,26 +1043,45 @@ export default Hero
|
|
|
876
1043
|
// src/components/Hero/meta.js
|
|
877
1044
|
writeFile(join(projectDir, 'src/components/Hero/meta.js'), `/**
|
|
878
1045
|
* Hero Component Metadata
|
|
1046
|
+
*
|
|
1047
|
+
* Content comes from the markdown body (parsed semantically):
|
|
1048
|
+
* - H1 → title (content.main.header.title)
|
|
1049
|
+
* - Paragraphs → description (content.main.body.paragraphs)
|
|
1050
|
+
* - Links → CTA buttons (content.main.body.links)
|
|
879
1051
|
*/
|
|
880
1052
|
export default {
|
|
881
1053
|
title: 'Hero Banner',
|
|
882
|
-
description: 'A prominent header section with headline,
|
|
1054
|
+
description: 'A prominent header section with headline, description, and call-to-action',
|
|
883
1055
|
category: 'Headers',
|
|
884
1056
|
|
|
1057
|
+
// Content structure (informational - describes what the semantic parser provides)
|
|
885
1058
|
elements: {
|
|
886
1059
|
title: {
|
|
887
1060
|
label: 'Headline',
|
|
1061
|
+
description: 'From H1 in markdown',
|
|
888
1062
|
required: true,
|
|
889
1063
|
},
|
|
890
|
-
|
|
891
|
-
label: '
|
|
1064
|
+
paragraphs: {
|
|
1065
|
+
label: 'Description',
|
|
1066
|
+
description: 'From paragraphs in markdown',
|
|
892
1067
|
},
|
|
893
1068
|
links: {
|
|
894
1069
|
label: 'Call to Action',
|
|
1070
|
+
description: 'From links in markdown',
|
|
895
1071
|
},
|
|
896
1072
|
},
|
|
897
1073
|
|
|
1074
|
+
// Configuration parameters (set in frontmatter)
|
|
898
1075
|
properties: {
|
|
1076
|
+
theme: {
|
|
1077
|
+
type: 'select',
|
|
1078
|
+
label: 'Theme',
|
|
1079
|
+
options: [
|
|
1080
|
+
{ value: 'light', label: 'Light' },
|
|
1081
|
+
{ value: 'dark', label: 'Dark' },
|
|
1082
|
+
],
|
|
1083
|
+
default: 'light',
|
|
1084
|
+
},
|
|
899
1085
|
alignment: {
|
|
900
1086
|
type: 'select',
|
|
901
1087
|
label: 'Text Alignment',
|