uniweb 0.8.9 → 0.8.11
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
CHANGED
|
@@ -115,7 +115,7 @@ Frontmatter specifies the component type and configuration. The body contains th
|
|
|
115
115
|
|
|
116
116
|
### Beyond Markdown
|
|
117
117
|
|
|
118
|
-
For content that doesn't fit markdown patterns—products, team members, events—use tagged
|
|
118
|
+
For content that doesn't fit markdown patterns—products, team members, events—use tagged data blocks:
|
|
119
119
|
|
|
120
120
|
````markdown
|
|
121
121
|
```yaml:team-member
|
|
@@ -202,7 +202,7 @@ Open `foundation/src/sections/Hero.jsx`. The component receives parsed content:
|
|
|
202
202
|
|
|
203
203
|
```jsx
|
|
204
204
|
export default function Hero({ content }) {
|
|
205
|
-
const { title, paragraphs, links,
|
|
205
|
+
const { title, paragraphs, links, images, items } = content
|
|
206
206
|
// Edit the JSX below...
|
|
207
207
|
}
|
|
208
208
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/build": "0.8.
|
|
44
|
+
"@uniweb/build": "0.8.10",
|
|
45
45
|
"@uniweb/content-reader": "1.1.4",
|
|
46
|
-
"@uniweb/
|
|
47
|
-
"@uniweb/
|
|
48
|
-
"@uniweb/
|
|
49
|
-
"@uniweb/semantic-parser": "1.1.
|
|
46
|
+
"@uniweb/core": "0.5.7",
|
|
47
|
+
"@uniweb/runtime": "0.6.7",
|
|
48
|
+
"@uniweb/kit": "0.7.7",
|
|
49
|
+
"@uniweb/semantic-parser": "1.1.5"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/partials/agents.md
CHANGED
|
@@ -50,10 +50,10 @@ This project was created with [Uniweb CLI](https://github.com/uniweb/cli). Full
|
|
|
50
50
|
| Component metadata (meta.js) | `reference/component-metadata.md` |
|
|
51
51
|
| Migrating existing designs | `development/converting-existing.md` |
|
|
52
52
|
|
|
53
|
-
> **npm registry:** Use `https://registry.npmjs.org/uniweb` for package metadata — the npmjs.com website blocks automated requests.
|
|
54
|
-
|
|
55
53
|
## Project Structure
|
|
56
54
|
|
|
55
|
+
Most projects start as a workspace with two packages:
|
|
56
|
+
|
|
57
57
|
```
|
|
58
58
|
project/
|
|
59
59
|
├── foundation/ # Component developer's domain
|
|
@@ -61,23 +61,24 @@ project/
|
|
|
61
61
|
└── pnpm-workspace.yaml
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
- **Foundation** (developer): React components. Those in `src/sections` and `src/layouts` are *section types* — selectable by content authors via `type:` in frontmatter, or used for site-level layout areas (header, footer, panel). Most have an a `meta.js` with metadata in them. Everything in `src/components` (or elsewhere) is ordinary React.
|
|
65
|
+
- **Site** (content author): Markdown content + configuration. Each section file references a section type. Authors work here without touching foundation code. It may also contain collections of structured content and/or references to external data sources.
|
|
65
66
|
|
|
66
|
-
-
|
|
67
|
-
- **Site** (content author): Markdown content + configuration. Each section file references a section type. Authors work here without touching foundation code.
|
|
67
|
+
> Multi-site projects use sub-folders with site/foundation pairs in them, or segregate foundations and sites into separate folders (`foundations/`, `sites/`).
|
|
68
68
|
|
|
69
69
|
## Project Setup
|
|
70
70
|
|
|
71
71
|
Always use the CLI to scaffold projects — never write `package.json`, `vite.config.js`, `main.js`, or `index.html` manually. The CLI resolves correct versions and structure.
|
|
72
72
|
|
|
73
|
+
**npm or pnpm.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command below.
|
|
74
|
+
|
|
73
75
|
### New workspace
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
|
-
pnpm create uniweb my-project
|
|
78
|
+
pnpm create uniweb my-project --template <n>
|
|
77
79
|
cd my-project && pnpm install
|
|
78
80
|
```
|
|
79
|
-
|
|
80
|
-
This creates a workspace with foundation + site + starter content — two commands to a dev server. Use `--template <n>` for an official template (`marketing`, `docs`, `academic`, etc.), `--template none` for foundation + site with no content, or `--blank` for an empty workspace.
|
|
81
|
+
Use `--template <n>` for an official template (`none`, `starter`, `marketing`, `docs`, `academic`, etc.), `--template none` for foundation + site with no content, or `--blank` for an empty workspace.
|
|
81
82
|
|
|
82
83
|
### Adding a co-located project
|
|
83
84
|
|
|
@@ -129,8 +130,6 @@ pnpm build # Build for production
|
|
|
129
130
|
pnpm preview # Preview production build (SSG + SPA)
|
|
130
131
|
```
|
|
131
132
|
|
|
132
|
-
> **npm works too.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command above.
|
|
133
|
-
|
|
134
133
|
---
|
|
135
134
|
|
|
136
135
|
## Content Authoring
|
|
@@ -176,13 +175,14 @@ content = {
|
|
|
176
175
|
subtitle2: '', // Third-level heading
|
|
177
176
|
paragraphs: [], // Text blocks
|
|
178
177
|
links: [], // { href, label, role } — standalone links become buttons
|
|
179
|
-
|
|
178
|
+
images: [], // { src, alt, role, href }
|
|
180
179
|
icons: [], // { library, name, role }
|
|
181
|
-
videos: [], //
|
|
180
|
+
videos: [], // { src, alt, role, poster, href }
|
|
182
181
|
insets: [], // Inline @Component references — { refId }
|
|
183
182
|
lists: [], // [[{ paragraphs, links, lists, ... }]] — each list item is an object, not a string
|
|
184
183
|
quotes: [], // Blockquotes
|
|
185
|
-
|
|
184
|
+
snippets: [], // Fenced code — [{ language, code }]
|
|
185
|
+
data: {}, // From tagged data blocks (```yaml:tagname, ```json:tagname)
|
|
186
186
|
headings: [], // Overflow headings after subtitle2
|
|
187
187
|
items: [], // Each has the same flat structure — from headings after body content
|
|
188
188
|
sequence: [], // All elements in document order
|
|
@@ -205,7 +205,7 @@ Lightning quick. ← items[0].paragraphs[0]
|
|
|
205
205
|
Enterprise-grade. ← items[1].paragraphs[0]
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
**Items have the full content shape** — this is the most commonly overlooked feature. Each item has `title`, `pretitle`, `subtitle`, `paragraphs`, `links`, `icons`, `lists`, and even `data` (tagged blocks). You don't need workarounds for structured content within items:
|
|
208
|
+
**Items have the full content shape** — this is the most commonly overlooked feature. Each item has `title`, `pretitle`, `subtitle`, `paragraphs`, `links`, `icons`, `lists`, `snippets`, and even `data` (tagged data blocks). You don't need workarounds for structured content within items:
|
|
209
209
|
|
|
210
210
|
```markdown
|
|
211
211
|
### The Problem ← items[0].pretitle
|
|
@@ -258,7 +258,7 @@ You have three layers. Most of the design skill is choosing between them:
|
|
|
258
258
|
|
|
259
259
|
**Frontmatter params** — `columns: 3`, `variant: centered`, `theme: dark`. Configuration that an author might change but that isn't *content*. Would changing this value change the section's *meaning*, or just its *presentation*? Presentation → param. Meaning → content.
|
|
260
260
|
|
|
261
|
-
**Tagged data blocks** — for content that doesn't fit markdown patterns. Products with SKUs, team members with roles, event schedules, pricing metadata, form definitions. When the information is genuinely structured data that a content author still owns, a well-named tagged block (`yaml:pricing`, `yaml:speakers`, `yaml:config`) is clearer than contorting markdown into a data format.
|
|
261
|
+
**Tagged data blocks** — for content that doesn't fit markdown patterns. Products with SKUs, team members with roles, event schedules, pricing metadata, form definitions. When the information is genuinely structured data that a content author still owns, a well-named tagged block (`yaml:pricing`, `yaml:speakers`, `yaml:config`) is clearer than contorting markdown into a data format. Supported formats: `yaml` and `json`. The format is a serialization format (how to parse the data), not a language for display. Tagged blocks are parsed at build time into JS objects and delivered as `content.data.tagName`.
|
|
262
262
|
|
|
263
263
|
Read the markdown out loud. If a content author would understand what every line does and how to edit it, you've chosen the right layer. The moment markdown feels like it's encoding data rather than expressing content, step up to a tagged block — that's fine. A well-documented `yaml:pricing` block is better than a markdown structure that puzzles the author.
|
|
264
264
|
|
|
@@ -321,7 +321,7 @@ Custom SVGs: `{role=icon}`
|
|
|
321
321
|
```markdown
|
|
322
322
|
[text](url){target=_blank} <!-- Open in new tab -->
|
|
323
323
|
[text](./file.pdf){download} <!-- Download -->
|
|
324
|
-
{role=banner} <!-- Role determines array:
|
|
324
|
+
{role=banner} <!-- Role determines array: images, icons, or videos -->
|
|
325
325
|
```
|
|
326
326
|
|
|
327
327
|
**Quote values that contain spaces:** `{note="Ready to go"}` not `{note=Ready to go}`. Unquoted values end at the first space.
|
|
@@ -345,9 +345,11 @@ This is [less important]{muted} context.
|
|
|
345
345
|
|
|
346
346
|
Sites can define additional named styles in `theme.yml`'s `inline:` section.
|
|
347
347
|
|
|
348
|
-
###
|
|
348
|
+
### Fenced Code in Content
|
|
349
|
+
|
|
350
|
+
Fenced code in markdown serves two distinct purposes depending on whether it has a tag:
|
|
349
351
|
|
|
350
|
-
Tagged
|
|
352
|
+
**Tagged data blocks** — structured data parsed into JS objects. The format (`yaml`/`json`) is a serialization format, not a display language. The tag is the key in `content.data`:
|
|
351
353
|
|
|
352
354
|
````markdown
|
|
353
355
|
```yaml:form
|
|
@@ -358,9 +360,21 @@ submitLabel: Send
|
|
|
358
360
|
```
|
|
359
361
|
````
|
|
360
362
|
|
|
361
|
-
Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`
|
|
363
|
+
Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`. Supported formats: `yaml` (or `yml`) and `json`.
|
|
364
|
+
|
|
365
|
+
**Code snippets** — display content with a language for syntax highlighting. Available in `content.snippets` as `[{ language, code }]`:
|
|
366
|
+
|
|
367
|
+
````markdown
|
|
368
|
+
```jsx
|
|
369
|
+
function Hello() {
|
|
370
|
+
return <h1>Hello world</h1>
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
````
|
|
374
|
+
|
|
375
|
+
Access: `content.snippets[0]` → `{ language: 'jsx', code: 'function Hello() {...}' }`. The `language` attribute is a display hint for syntax highlighting, not a parsing format. Filter by language: `content.snippets.filter(s => s.language === 'css')`.
|
|
362
376
|
|
|
363
|
-
|
|
377
|
+
Both appear in `content.sequence` for document-order rendering. The difference: tagged data blocks are parsed and extracted to `content.data`; code snippets are preserved and collected in `content.snippets`.
|
|
364
378
|
|
|
365
379
|
### Composition: Nesting and Embedding
|
|
366
380
|
|
|
@@ -377,7 +391,7 @@ In other frameworks, this is where you'd reach for MDX, or prop-drill a componen
|
|
|
377
391
|
Standard markdown image syntax — `{attributes}`. The content author placed a full React component with content and params, and it looks like an image reference. The developer builds `NetworkDiagram` as an ordinary React component with `inset: true` in its `meta.js`. The kit's `<Visual>` component renders the first non-empty candidate — so the same section type works whether the author provides a static image, a video, or an interactive component:
|
|
378
392
|
|
|
379
393
|
```jsx
|
|
380
|
-
<Visual inset={block.insets[0]} video={content.videos[0]} image={content.
|
|
394
|
+
<Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} className="rounded-2xl" />
|
|
381
395
|
```
|
|
382
396
|
|
|
383
397
|
The content author controls what goes in the visual slot. The developer's component doesn't need to know or care whether it's rendering an image or a ThreeJS scene.
|
|
@@ -461,6 +475,8 @@ export default function Grid({ block, params }) {
|
|
|
461
475
|
|
|
462
476
|
Set `background` in frontmatter — the runtime renders it automatically:
|
|
463
477
|
|
|
478
|
+
> **FIXME:** var(--primary-900) should be primary-900, right?
|
|
479
|
+
|
|
464
480
|
```yaml
|
|
465
481
|
background: /images/hero.jpg # Image
|
|
466
482
|
background: /videos/hero.mp4 # Video
|
|
@@ -796,7 +812,7 @@ import { Section, Render } from '@uniweb/kit'
|
|
|
796
812
|
```jsx
|
|
797
813
|
import { Visual } from '@uniweb/kit'
|
|
798
814
|
|
|
799
|
-
<Visual inset={block.insets[0]} video={content.videos[0]} image={content.
|
|
815
|
+
<Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} className="rounded-2xl" />
|
|
800
816
|
```
|
|
801
817
|
|
|
802
818
|
### Kit API by Use Case
|
|
@@ -916,8 +932,20 @@ export default {
|
|
|
916
932
|
compact: { label: 'Compact', params: { columns: 4 } },
|
|
917
933
|
},
|
|
918
934
|
|
|
935
|
+
// context and initialState: keys are developer-defined, not framework fields.
|
|
936
|
+
// Design your own names for your foundation's cross-block communication.
|
|
937
|
+
|
|
938
|
+
// Static — neighbors read via getNextBlockInfo().context
|
|
919
939
|
context: {
|
|
920
|
-
|
|
940
|
+
// Example: a Hero might declare this so a Header knows it can float.
|
|
941
|
+
// allowTranslucentTop: true,
|
|
942
|
+
},
|
|
943
|
+
|
|
944
|
+
// Dynamic — neighbors read via getNextBlockInfo().state
|
|
945
|
+
// Component can update with useBlockState()
|
|
946
|
+
initialState: {
|
|
947
|
+
// Example: Hero starts translucent-ready, but component logic may disable it.
|
|
948
|
+
// allowTranslucentTop: true,
|
|
921
949
|
},
|
|
922
950
|
}
|
|
923
951
|
```
|
|
@@ -928,46 +956,52 @@ All defaults belong in `meta.js`, not inline in component code.
|
|
|
928
956
|
|
|
929
957
|
Section types naturally use params to adjust their own rendering — `variant: flipped` reverses a flex direction, `columns: 3` sets a grid. That's not a pattern, that's the baseline.
|
|
930
958
|
|
|
931
|
-
The **Front Desk pattern** is when a section type does virtually no rendering itself. It reads the author's params, picks the right helper component
|
|
959
|
+
The **Front Desk pattern** is when a section type does virtually no rendering itself. It reads the author's params, picks the right helper component, and translates author-friendly vocabulary into developer-oriented props. The section type is a front desk — it greets the request and routes it to the right specialist.
|
|
960
|
+
|
|
961
|
+
The workers behind the front desk don't need to share the same interface. A `Hero` might delegate to a `SliderHero` that renders an image carousel and a `ContactHero` that renders a quote request form. They expect different content and different props — that's fine. The front desk declares the **union** of all content its workers might need. Some content won't be used for a given variant, and that's perfectly normal in CCA — params change behavior, and that includes not rendering some content:
|
|
962
|
+
|
|
963
|
+
```js
|
|
964
|
+
// meta.js — the union of all variants' needs
|
|
965
|
+
export default {
|
|
966
|
+
params: {
|
|
967
|
+
variant: { type: 'select', options: ['slider', 'contact'], default: 'slider' },
|
|
968
|
+
slideInterval: { type: 'number', default: 5 },
|
|
969
|
+
density: { type: 'select', options: ['default', 'compact'], default: 'default' },
|
|
970
|
+
style: { type: 'select', options: ['default', 'dramatic'], default: 'default' },
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
```
|
|
932
974
|
|
|
933
975
|
```jsx
|
|
934
|
-
// sections/
|
|
935
|
-
import {
|
|
936
|
-
import {
|
|
937
|
-
import { ComparisonTable } from '../../components/ComparisonTable'
|
|
976
|
+
// sections/Hero/index.jsx — the front desk
|
|
977
|
+
import { SliderHero } from '../../components/SliderHero'
|
|
978
|
+
import { ContactHero } from '../../components/ContactHero'
|
|
938
979
|
|
|
939
|
-
const variants = {
|
|
980
|
+
const variants = { slider: SliderHero, contact: ContactHero }
|
|
940
981
|
|
|
941
|
-
export default function
|
|
942
|
-
const
|
|
982
|
+
export default function Hero({ content, block, params }) {
|
|
983
|
+
const Variant = variants[params.variant] || SliderHero
|
|
943
984
|
|
|
944
985
|
return (
|
|
945
|
-
<
|
|
986
|
+
<Variant
|
|
987
|
+
// Shared — every variant gets these
|
|
946
988
|
title={content.title}
|
|
947
989
|
subtitle={content.paragraphs[0]}
|
|
948
|
-
|
|
990
|
+
links={content.links}
|
|
949
991
|
block={block}
|
|
950
|
-
|
|
951
|
-
|
|
992
|
+
// Content that only some variants use
|
|
993
|
+
images={content.images}
|
|
994
|
+
formData={content.data?.quote}
|
|
995
|
+
// Translated params — author vocabulary → developer props
|
|
996
|
+
interval={params.slideInterval}
|
|
952
997
|
compact={params.density === 'compact'}
|
|
998
|
+
transition={params.style === 'dramatic' ? 'zoom' : 'fade'}
|
|
953
999
|
/>
|
|
954
1000
|
)
|
|
955
1001
|
}
|
|
956
1002
|
```
|
|
957
1003
|
|
|
958
|
-
|
|
959
|
-
// meta.js — author-friendly language
|
|
960
|
-
export default {
|
|
961
|
-
params: {
|
|
962
|
-
variant: { type: 'select', options: ['grid', 'list', 'comparison'], default: 'grid' },
|
|
963
|
-
columns: { type: 'number', default: 3 },
|
|
964
|
-
showIcon: { type: 'boolean', default: true },
|
|
965
|
-
density: { type: 'select', options: ['default', 'compact'], default: 'default' },
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
```
|
|
969
|
-
|
|
970
|
-
The content author writes `variant: comparison` — they don't know or care about `ComparisonTable`. The section type translates `density: compact` into a `compact={true}` prop. `CardGrid`, `CardList`, `ComparisonTable` live in `src/components/` — ordinary React, reusable across multiple section types, testable independently.
|
|
1004
|
+
`SliderHero` uses `images`, `interval`, and `transition`; it ignores `formData` and `compact`. `ContactHero` uses `formData` and `compact`; it ignores `images` and `interval`. Each worker takes what it needs. Some params only matter for certain variants (`slideInterval` for slider, `density` for contact). Some are high-level names that the front desk translates into developer-oriented values (`style: dramatic` → `transition="zoom"`). The content author writes `variant: contact` — they don't know or care about `ContactHero`.
|
|
971
1005
|
|
|
972
1006
|
This is the system-building pattern at its clearest: **section types are the public interface** to your content system (author-friendly names, documented in `meta.js`). **Helper components are the implementation** (developer-friendly APIs, ordinary React props). The section type is the thin translation layer that connects the two worlds.
|
|
973
1007
|
|
|
@@ -1027,11 +1061,65 @@ page.hasChildren(), page.children
|
|
|
1027
1061
|
|
|
1028
1062
|
### Cross-Block Communication
|
|
1029
1063
|
|
|
1064
|
+
Section types sometimes need to coordinate. The typical case: a Header needs to know whether the section below it supports a floating translucent overlay — a Hero with a full-bleed background does, a plain text section doesn't. The section that **owns the capability declares it**; the section that **needs to adapt reads it**.
|
|
1065
|
+
|
|
1066
|
+
`getBlockInfo()` exposes two channels:
|
|
1067
|
+
|
|
1068
|
+
- **`context`** — Static capabilities from `meta.js`. Never changes. The declaring section type always has this capability.
|
|
1069
|
+
- **`state`** — Dynamic runtime state via `useBlockState()`. Can change based on component logic. Initial value comes from `initialState` in `meta.js`.
|
|
1070
|
+
|
|
1071
|
+
```jsx
|
|
1072
|
+
// Header reads the next section's info to decide how to render
|
|
1073
|
+
const nextBlockInfo = block.getNextBlockInfo()
|
|
1074
|
+
// nextBlockInfo.context → static (meta.js)
|
|
1075
|
+
// nextBlockInfo.state → dynamic (useBlockState)
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
**Static context** — Hero declares a permanent capability, Header reads it:
|
|
1079
|
+
|
|
1080
|
+
```js
|
|
1081
|
+
// Hero/meta.js — "I always support a translucent header over me"
|
|
1082
|
+
export default {
|
|
1083
|
+
context: { allowTranslucentTop: true },
|
|
1084
|
+
}
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
```jsx
|
|
1088
|
+
// Header/index.jsx — adapts based on what's below
|
|
1089
|
+
const nextBlockInfo = block.getNextBlockInfo()
|
|
1090
|
+
const isFloating = nextBlockInfo?.context?.allowTranslucentTop || false
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
**Dynamic state** — Hero declares an initial value but can change it at runtime:
|
|
1094
|
+
|
|
1095
|
+
```js
|
|
1096
|
+
// Hero/meta.js — starts as true, but component logic may change it
|
|
1097
|
+
export default {
|
|
1098
|
+
initialState: { allowTranslucentTop: true },
|
|
1099
|
+
}
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
```jsx
|
|
1103
|
+
// Hero/index.jsx — conditionally updates
|
|
1104
|
+
function Hero({ content, block }) {
|
|
1105
|
+
const [state, setState] = block.useBlockState(useState)
|
|
1106
|
+
// state.allowTranslucentTop is true initially (from meta.js)
|
|
1107
|
+
// Component logic can change it: setState({ allowTranslucentTop: false })
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1030
1111
|
```jsx
|
|
1031
|
-
|
|
1032
|
-
|
|
1112
|
+
// Header/index.jsx — reads dynamic state, falls back to static context
|
|
1113
|
+
const nextBlockInfo = block.getNextBlockInfo()
|
|
1114
|
+
const isFloating = nextBlockInfo?.state?.allowTranslucentTop
|
|
1115
|
+
?? nextBlockInfo?.context?.allowTranslucentTop
|
|
1116
|
+
?? false
|
|
1033
1117
|
```
|
|
1034
1118
|
|
|
1119
|
+
The key names (`allowTranslucentTop`, `expanded`, etc.) are yours to design — they're not framework fields. Define whatever protocol your foundation's sections need.
|
|
1120
|
+
|
|
1121
|
+
Other navigation methods: `block.getPrevBlockInfo()`, `block.page.getFirstBodyBlockInfo()`.
|
|
1122
|
+
|
|
1035
1123
|
### Custom Layouts
|
|
1036
1124
|
|
|
1037
1125
|
Layouts live in `foundation/src/layouts/` and are auto-discovered:
|
|
@@ -1154,9 +1242,9 @@ Semantic tokens come from `theme-tokens.css` (populated from `theme.yml`). Use `
|
|
|
1154
1242
|
|
|
1155
1243
|
**Content not appearing as expected?**
|
|
1156
1244
|
```bash
|
|
1157
|
-
uniweb inspect pages/home/hero.md # Single section
|
|
1158
|
-
uniweb inspect pages/home/ # Whole page
|
|
1159
|
-
uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
1245
|
+
pnpm uniweb inspect pages/home/hero.md # Single section
|
|
1246
|
+
pnpm uniweb inspect pages/home/ # Whole page
|
|
1247
|
+
pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
1160
1248
|
```
|
|
1161
1249
|
|
|
1162
1250
|
## Learning from Official Templates
|
|
@@ -1164,7 +1252,7 @@ uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
|
1164
1252
|
When you're unsure how to implement a pattern — data fetching, i18n, layouts, insets, theming — install an official template as a reference project in your workspace:
|
|
1165
1253
|
|
|
1166
1254
|
```bash
|
|
1167
|
-
uniweb add project marketing --from marketing
|
|
1255
|
+
pnpm uniweb add project marketing --from marketing
|
|
1168
1256
|
pnpm install
|
|
1169
1257
|
```
|
|
1170
1258
|
|
package/src/commands/inspect.js
CHANGED
|
@@ -149,7 +149,7 @@ function guaranteeItemStructure(item) {
|
|
|
149
149
|
subtitle: item.subtitle || '',
|
|
150
150
|
paragraphs: item.paragraphs || [],
|
|
151
151
|
links: item.links || [],
|
|
152
|
-
|
|
152
|
+
images: item.images || [],
|
|
153
153
|
lists: item.lists || [],
|
|
154
154
|
icons: item.icons || [],
|
|
155
155
|
videos: item.videos || [],
|
|
@@ -178,7 +178,7 @@ function guaranteeContentStructure(parsedContent) {
|
|
|
178
178
|
alignment: content.alignment || null,
|
|
179
179
|
paragraphs: content.paragraphs || [],
|
|
180
180
|
links: content.links || [],
|
|
181
|
-
|
|
181
|
+
images: content.images || [],
|
|
182
182
|
lists: content.lists || [],
|
|
183
183
|
icons: content.icons || [],
|
|
184
184
|
videos: content.videos || [],
|
|
@@ -7,7 +7,7 @@ import { H1, H2, P, Link, cn } from '@uniweb/kit'
|
|
|
7
7
|
* Uses semantic tokens so it adapts to any theme context automatically.
|
|
8
8
|
*/
|
|
9
9
|
export default function Section({ content, params }) {
|
|
10
|
-
const { title, pretitle, subtitle, paragraphs = [], links = [],
|
|
10
|
+
const { title, pretitle, subtitle, paragraphs = [], links = [], images = [] } = content || {}
|
|
11
11
|
|
|
12
12
|
const {
|
|
13
13
|
align = 'center',
|
|
@@ -77,9 +77,9 @@ export default function Section({ content, params }) {
|
|
|
77
77
|
</div>
|
|
78
78
|
)}
|
|
79
79
|
|
|
80
|
-
{
|
|
80
|
+
{images.length > 0 && (
|
|
81
81
|
<div className="mt-8">
|
|
82
|
-
{
|
|
82
|
+
{images.map((img, index) => (
|
|
83
83
|
<img
|
|
84
84
|
key={index}
|
|
85
85
|
src={img.url || img.src}
|