uniweb 0.8.8 → 0.8.10
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/package.json +6 -4
- package/partials/agents.md +562 -625
- package/src/commands/inspect.js +358 -0
- package/src/index.js +8 -0
- package/src/utils/scaffold.js +32 -8
- package/templates/foundation/src/foundation.js.hbs +23 -1
- package/templates/site/site.yml.hbs +49 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uniweb inspect — Show parsed content shape of markdown files.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* uniweb inspect pages/home/hero.md Single section
|
|
6
|
+
* uniweb inspect pages/home/ All sections in a page folder
|
|
7
|
+
* uniweb inspect pages/home/hero.md --raw ProseMirror AST instead of flat shape
|
|
8
|
+
* uniweb inspect pages/home/ --full Include empty fields (matches runtime)
|
|
9
|
+
* uniweb inspect pages/home/ --sequence Include sequence array
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
13
|
+
import { resolve, extname, basename } from 'node:path'
|
|
14
|
+
import yaml from 'js-yaml'
|
|
15
|
+
|
|
16
|
+
// Colors for terminal output
|
|
17
|
+
const colors = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
dim: '\x1b[2m',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse CLI arguments for inspect command
|
|
26
|
+
*/
|
|
27
|
+
function parseArgs(args) {
|
|
28
|
+
const flags = {
|
|
29
|
+
target: null,
|
|
30
|
+
raw: false,
|
|
31
|
+
full: false,
|
|
32
|
+
sequence: false,
|
|
33
|
+
help: false,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const arg of args) {
|
|
37
|
+
if (arg === '--raw') flags.raw = true
|
|
38
|
+
else if (arg === '--full') flags.full = true
|
|
39
|
+
else if (arg === '--sequence') flags.sequence = true
|
|
40
|
+
else if (arg === '--help' || arg === '-h') flags.help = true
|
|
41
|
+
else if (!arg.startsWith('-')) flags.target = arg
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return flags
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Dynamically import content-reader and semantic-parser.
|
|
49
|
+
* These are transitive deps of @uniweb/build, available in any project workspace.
|
|
50
|
+
*/
|
|
51
|
+
async function loadDependencies() {
|
|
52
|
+
try {
|
|
53
|
+
const [contentReader, semanticParser] = await Promise.all([
|
|
54
|
+
import('@uniweb/content-reader'),
|
|
55
|
+
import('@uniweb/semantic-parser'),
|
|
56
|
+
])
|
|
57
|
+
return {
|
|
58
|
+
markdownToProseMirror: contentReader.markdownToProseMirror,
|
|
59
|
+
parseContent: semanticParser.parseContent,
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
console.error(`${colors.red}✗${colors.reset} Could not load @uniweb/content-reader or @uniweb/semantic-parser.`)
|
|
63
|
+
console.error(` These packages must be installed in the workspace (they come with @uniweb/build).`)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Extract frontmatter and markdown body from a section file.
|
|
70
|
+
* Exact match of content-collector lines 634-639.
|
|
71
|
+
*/
|
|
72
|
+
function extractFrontmatter(content) {
|
|
73
|
+
let frontMatter = {}
|
|
74
|
+
let markdown = content
|
|
75
|
+
|
|
76
|
+
if (content.trim().startsWith('---')) {
|
|
77
|
+
const parts = content.split('---\n')
|
|
78
|
+
if (parts.length >= 3) {
|
|
79
|
+
try {
|
|
80
|
+
frontMatter = yaml.load(parts[1]) || {}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.warn(`${colors.yellow}Warning: YAML parse error: ${err.message}${colors.reset}`)
|
|
83
|
+
frontMatter = {}
|
|
84
|
+
}
|
|
85
|
+
markdown = parts.slice(2).join('---\n')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { frontMatter, markdown }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Split frontmatter into reserved keys and custom params.
|
|
94
|
+
* Exact match of content-collector line 642.
|
|
95
|
+
*/
|
|
96
|
+
function splitParams(frontMatter) {
|
|
97
|
+
const { type, component, preset, input, props, fetch, data, id, background, theme, ...params } = frontMatter
|
|
98
|
+
return {
|
|
99
|
+
type: type || component || null,
|
|
100
|
+
preset: preset || null,
|
|
101
|
+
reserved: { data, id, background, theme, input },
|
|
102
|
+
params,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract inset references from a ProseMirror document.
|
|
108
|
+
* Exact match of content-collector lines 192-218.
|
|
109
|
+
*
|
|
110
|
+
* @param {object} doc - ProseMirror document (mutated in place)
|
|
111
|
+
* @returns {Array} Array of { refId, type, params, description }
|
|
112
|
+
*/
|
|
113
|
+
function extractInsets(doc) {
|
|
114
|
+
if (!doc?.content || !Array.isArray(doc.content)) return []
|
|
115
|
+
|
|
116
|
+
const insets = []
|
|
117
|
+
let refIndex = 0
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < doc.content.length; i++) {
|
|
120
|
+
const node = doc.content[i]
|
|
121
|
+
if (node.type === 'inset_ref') {
|
|
122
|
+
const { component, alt, ...params } = node.attrs || {}
|
|
123
|
+
const refId = `inset_${refIndex++}`
|
|
124
|
+
insets.push({
|
|
125
|
+
refId,
|
|
126
|
+
type: component,
|
|
127
|
+
params: Object.keys(params).length > 0 ? params : {},
|
|
128
|
+
description: alt || null,
|
|
129
|
+
})
|
|
130
|
+
// Replace in-place with placeholder
|
|
131
|
+
doc.content[i] = {
|
|
132
|
+
type: 'inset_placeholder',
|
|
133
|
+
attrs: { refId },
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return insets
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Guarantee item has flat content structure.
|
|
143
|
+
* Keep in sync with @uniweb/runtime/src/prepare-props.js
|
|
144
|
+
*/
|
|
145
|
+
function guaranteeItemStructure(item) {
|
|
146
|
+
return {
|
|
147
|
+
title: item.title || '',
|
|
148
|
+
pretitle: item.pretitle || '',
|
|
149
|
+
subtitle: item.subtitle || '',
|
|
150
|
+
paragraphs: item.paragraphs || [],
|
|
151
|
+
links: item.links || [],
|
|
152
|
+
imgs: item.imgs || [],
|
|
153
|
+
lists: item.lists || [],
|
|
154
|
+
icons: item.icons || [],
|
|
155
|
+
videos: item.videos || [],
|
|
156
|
+
buttons: item.buttons || [],
|
|
157
|
+
data: item.data || {},
|
|
158
|
+
cards: item.cards || [],
|
|
159
|
+
documents: item.documents || [],
|
|
160
|
+
forms: item.forms || [],
|
|
161
|
+
quotes: item.quotes || [],
|
|
162
|
+
headings: item.headings || [],
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Guarantee content structure exists.
|
|
168
|
+
* Keep in sync with @uniweb/runtime/src/prepare-props.js
|
|
169
|
+
*/
|
|
170
|
+
function guaranteeContentStructure(parsedContent) {
|
|
171
|
+
const content = parsedContent || {}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
title: content.title || '',
|
|
175
|
+
pretitle: content.pretitle || '',
|
|
176
|
+
subtitle: content.subtitle || '',
|
|
177
|
+
subtitle2: content.subtitle2 || '',
|
|
178
|
+
alignment: content.alignment || null,
|
|
179
|
+
paragraphs: content.paragraphs || [],
|
|
180
|
+
links: content.links || [],
|
|
181
|
+
imgs: content.imgs || [],
|
|
182
|
+
lists: content.lists || [],
|
|
183
|
+
icons: content.icons || [],
|
|
184
|
+
videos: content.videos || [],
|
|
185
|
+
insets: content.insets || [],
|
|
186
|
+
buttons: content.buttons || [],
|
|
187
|
+
data: content.data || {},
|
|
188
|
+
cards: content.cards || [],
|
|
189
|
+
documents: content.documents || [],
|
|
190
|
+
forms: content.forms || [],
|
|
191
|
+
quotes: content.quotes || [],
|
|
192
|
+
headings: content.headings || [],
|
|
193
|
+
items: (content.items || []).map(guaranteeItemStructure),
|
|
194
|
+
sequence: content.sequence || [],
|
|
195
|
+
raw: content.raw,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Remove empty fields from content for clean output.
|
|
201
|
+
*/
|
|
202
|
+
function removeEmptyFields(obj) {
|
|
203
|
+
const result = {}
|
|
204
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
205
|
+
if (key === 'raw') continue // Always skip raw ProseMirror in clean output
|
|
206
|
+
if (value === null || value === undefined) continue
|
|
207
|
+
if (value === '') continue
|
|
208
|
+
if (Array.isArray(value) && value.length === 0) continue
|
|
209
|
+
if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) continue
|
|
210
|
+
|
|
211
|
+
// Recursively clean items
|
|
212
|
+
if (key === 'items' && Array.isArray(value)) {
|
|
213
|
+
result[key] = value.map(item => removeEmptyFields(item))
|
|
214
|
+
} else {
|
|
215
|
+
result[key] = value
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return result
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Process a single markdown file and return its parsed shape.
|
|
223
|
+
*/
|
|
224
|
+
function processFile(fileContent, fileName, deps, options) {
|
|
225
|
+
const { raw, full, sequence } = options
|
|
226
|
+
const { markdownToProseMirror, parseContent } = deps
|
|
227
|
+
|
|
228
|
+
const { frontMatter, markdown } = extractFrontmatter(fileContent)
|
|
229
|
+
const { type, preset, reserved, params } = splitParams(frontMatter)
|
|
230
|
+
|
|
231
|
+
// Parse markdown to ProseMirror
|
|
232
|
+
const doc = markdownToProseMirror(markdown)
|
|
233
|
+
|
|
234
|
+
// Extract insets (mutates doc)
|
|
235
|
+
const insets = extractInsets(doc)
|
|
236
|
+
|
|
237
|
+
// Build result
|
|
238
|
+
const result = {}
|
|
239
|
+
|
|
240
|
+
if (type) result.type = type
|
|
241
|
+
if (preset) result.preset = preset
|
|
242
|
+
|
|
243
|
+
// Include non-empty reserved fields
|
|
244
|
+
for (const [key, value] of Object.entries(reserved)) {
|
|
245
|
+
if (value !== undefined && value !== null) result[key] = value
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Include params if any
|
|
249
|
+
if (Object.keys(params).length > 0) result.params = params
|
|
250
|
+
|
|
251
|
+
// Check if file is a child section
|
|
252
|
+
if (basename(fileName).startsWith('@')) {
|
|
253
|
+
result.child = true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (raw) {
|
|
257
|
+
// Raw mode: return ProseMirror AST
|
|
258
|
+
result.prosemirror = doc
|
|
259
|
+
if (insets.length > 0) result.insets = insets
|
|
260
|
+
return result
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Parse to flat content shape
|
|
264
|
+
const parsed = parseContent(doc)
|
|
265
|
+
|
|
266
|
+
// Apply guarantees
|
|
267
|
+
let content = guaranteeContentStructure(parsed)
|
|
268
|
+
|
|
269
|
+
if (!full) {
|
|
270
|
+
content = removeEmptyFields(content)
|
|
271
|
+
} else {
|
|
272
|
+
// In full mode, still remove raw
|
|
273
|
+
delete content.raw
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!sequence) {
|
|
277
|
+
delete content.sequence
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
result.content = content
|
|
281
|
+
|
|
282
|
+
if (insets.length > 0) result.insets = insets
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Natural sort comparator for filenames (handles numeric prefixes).
|
|
289
|
+
*/
|
|
290
|
+
function naturalSort(a, b) {
|
|
291
|
+
return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Main inspect entry point.
|
|
296
|
+
*/
|
|
297
|
+
export async function inspect(args) {
|
|
298
|
+
const flags = parseArgs(args)
|
|
299
|
+
|
|
300
|
+
if (flags.help || !flags.target) {
|
|
301
|
+
console.log(`
|
|
302
|
+
Usage: uniweb inspect <path> [options]
|
|
303
|
+
|
|
304
|
+
Inspect the parsed content shape of a markdown file or page folder.
|
|
305
|
+
|
|
306
|
+
Arguments:
|
|
307
|
+
<path> Path to a .md file or a page folder
|
|
308
|
+
|
|
309
|
+
Options:
|
|
310
|
+
--raw Show ProseMirror AST instead of flat content shape
|
|
311
|
+
--full Include empty fields (matches runtime guarantees)
|
|
312
|
+
--sequence Include sequence array (document-order elements)
|
|
313
|
+
-h, --help Show this help
|
|
314
|
+
`)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const target = resolve(process.cwd(), flags.target)
|
|
319
|
+
const deps = await loadDependencies()
|
|
320
|
+
|
|
321
|
+
let stat
|
|
322
|
+
try {
|
|
323
|
+
stat = statSync(target)
|
|
324
|
+
} catch {
|
|
325
|
+
console.error(`${colors.red}✗${colors.reset} Not found: ${flags.target}`)
|
|
326
|
+
process.exit(1)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const options = { raw: flags.raw, full: flags.full, sequence: flags.sequence }
|
|
330
|
+
|
|
331
|
+
if (stat.isFile()) {
|
|
332
|
+
if (extname(target) !== '.md') {
|
|
333
|
+
console.error(`${colors.red}✗${colors.reset} Expected a .md file: ${flags.target}`)
|
|
334
|
+
process.exit(1)
|
|
335
|
+
}
|
|
336
|
+
const content = readFileSync(target, 'utf8')
|
|
337
|
+
const result = processFile(content, basename(target), deps, options)
|
|
338
|
+
console.log(JSON.stringify(result, null, 2))
|
|
339
|
+
} else if (stat.isDirectory()) {
|
|
340
|
+
const files = readdirSync(target)
|
|
341
|
+
.filter(f => extname(f) === '.md' && !f.startsWith('_') && f !== 'README.md')
|
|
342
|
+
.sort(naturalSort)
|
|
343
|
+
|
|
344
|
+
if (files.length === 0) {
|
|
345
|
+
console.error(`${colors.yellow}No .md files found in: ${flags.target}${colors.reset}`)
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const results = files.map(file => {
|
|
350
|
+
const content = readFileSync(resolve(target, file), 'utf8')
|
|
351
|
+
const result = processFile(content, file, deps, options)
|
|
352
|
+
result._file = file
|
|
353
|
+
return result
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
console.log(JSON.stringify(results, null, 2))
|
|
357
|
+
}
|
|
358
|
+
}
|
package/src/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { build } from './commands/build.js'
|
|
|
21
21
|
import { docs } from './commands/docs.js'
|
|
22
22
|
import { doctor } from './commands/doctor.js'
|
|
23
23
|
import { i18n } from './commands/i18n.js'
|
|
24
|
+
import { inspect } from './commands/inspect.js'
|
|
24
25
|
import { add } from './commands/add.js'
|
|
25
26
|
import {
|
|
26
27
|
resolveTemplate,
|
|
@@ -328,6 +329,12 @@ async function main() {
|
|
|
328
329
|
return
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
// Handle inspect command
|
|
333
|
+
if (command === 'inspect') {
|
|
334
|
+
await inspect(args.slice(1))
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
331
338
|
// Handle add command
|
|
332
339
|
if (command === 'add') {
|
|
333
340
|
await add(args.slice(1))
|
|
@@ -568,6 +575,7 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
568
575
|
create [name] Create a new project
|
|
569
576
|
add <type> [name] Add a foundation, site, or extension to a project
|
|
570
577
|
build Build the current project
|
|
578
|
+
inspect <path> Inspect parsed content shape of a markdown file or folder
|
|
571
579
|
docs Generate component documentation
|
|
572
580
|
doctor Diagnose project configuration issues
|
|
573
581
|
i18n <cmd> Internationalization (extract, sync, status)
|
package/src/utils/scaffold.js
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'node:fs/promises'
|
|
|
9
9
|
import { existsSync, readdirSync } from 'node:fs'
|
|
10
10
|
import { join, dirname } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
|
+
import yaml from 'js-yaml'
|
|
12
13
|
import { copyTemplateDirectory, registerVersions } from '../templates/processor.js'
|
|
13
14
|
import { getVersionsForTemplates } from '../versions.js'
|
|
14
15
|
|
|
@@ -103,13 +104,19 @@ export async function applyContent(contentDir, targetDir, context, options = {})
|
|
|
103
104
|
'.gitignore',
|
|
104
105
|
])
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
// Config files that should be merged, not overwritten.
|
|
108
|
+
// Keys listed here are preserved from the scaffolded version.
|
|
109
|
+
const MERGE_FILES = {
|
|
110
|
+
'site.yml': ['name', 'foundation'],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await copyContentRecursive(contentDir, targetDir, context, STRUCTURAL_FILES, MERGE_FILES, options)
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
/**
|
|
110
117
|
* Recursively copy content files, skipping structural files
|
|
111
118
|
*/
|
|
112
|
-
async function copyContentRecursive(sourceDir, targetDir, context, structuralFiles, options) {
|
|
119
|
+
async function copyContentRecursive(sourceDir, targetDir, context, structuralFiles, mergeFiles, options) {
|
|
113
120
|
await fs.mkdir(targetDir, { recursive: true })
|
|
114
121
|
|
|
115
122
|
const entries = readdirSync(sourceDir, { withFileTypes: true })
|
|
@@ -119,7 +126,7 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
|
|
|
119
126
|
|
|
120
127
|
if (entry.isDirectory()) {
|
|
121
128
|
const targetSubDir = join(targetDir, entry.name)
|
|
122
|
-
await copyContentRecursive(sourcePath, targetSubDir, context, structuralFiles, options)
|
|
129
|
+
await copyContentRecursive(sourcePath, targetSubDir, context, structuralFiles, mergeFiles, options)
|
|
123
130
|
} else {
|
|
124
131
|
// Determine the output filename (strip .hbs extension)
|
|
125
132
|
const outputName = entry.name.endsWith('.hbs')
|
|
@@ -131,13 +138,30 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
|
|
|
131
138
|
|
|
132
139
|
const targetPath = join(targetDir, outputName)
|
|
133
140
|
|
|
141
|
+
// Get new content (process .hbs or read as-is)
|
|
142
|
+
let newContent
|
|
134
143
|
if (entry.name.endsWith('.hbs')) {
|
|
135
|
-
// Process through Handlebars
|
|
136
144
|
const Handlebars = (await import('handlebars')).default
|
|
137
|
-
const
|
|
138
|
-
const template = Handlebars.compile(
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
const raw = await fs.readFile(sourcePath, 'utf-8')
|
|
146
|
+
const template = Handlebars.compile(raw)
|
|
147
|
+
newContent = template(context)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Merge config files instead of overwriting
|
|
151
|
+
const preserveKeys = mergeFiles[outputName]
|
|
152
|
+
if (preserveKeys && existsSync(targetPath)) {
|
|
153
|
+
const existingContent = await fs.readFile(targetPath, 'utf-8')
|
|
154
|
+
const existing = yaml.load(existingContent) || {}
|
|
155
|
+
const incoming = yaml.load(newContent || await fs.readFile(sourcePath, 'utf-8')) || {}
|
|
156
|
+
|
|
157
|
+
// Template values as base, preserve specified keys from scaffolded version
|
|
158
|
+
const merged = { ...incoming }
|
|
159
|
+
for (const key of preserveKeys) {
|
|
160
|
+
if (existing[key] !== undefined) merged[key] = existing[key]
|
|
161
|
+
}
|
|
162
|
+
await fs.writeFile(targetPath, yaml.dump(merged, { lineWidth: -1 }))
|
|
163
|
+
} else if (newContent !== undefined) {
|
|
164
|
+
await fs.writeFile(targetPath, newContent)
|
|
141
165
|
} else {
|
|
142
166
|
// Copy as-is
|
|
143
167
|
await fs.copyFile(sourcePath, targetPath)
|
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
// Foundation Configuration
|
|
2
|
+
// Schema: schemas/foundation.schema.json
|
|
3
|
+
//
|
|
4
|
+
// ─── CSS Custom Properties ──────────────────────────────────────────────────────
|
|
5
|
+
// Export `vars` to declare CSS custom properties that sites can override in theme.yml:
|
|
6
|
+
//
|
|
7
|
+
// export const vars = {
|
|
8
|
+
// 'header-height': { default: '4rem', description: 'Fixed header height' },
|
|
9
|
+
// 'max-content-width': { default: '80rem', description: 'Max content width' },
|
|
10
|
+
// }
|
|
11
|
+
//
|
|
12
|
+
// Sites override in theme.yml: vars: { header-height: 5rem }
|
|
13
|
+
// Components use: height: var(--header-height)
|
|
14
|
+
|
|
1
15
|
{{#if isExtension}}
|
|
2
16
|
export default {
|
|
3
17
|
extension: true,
|
|
4
18
|
}
|
|
5
19
|
{{else}}
|
|
6
|
-
export default {
|
|
20
|
+
export default {
|
|
21
|
+
// ─── Layout ─────────────────────────────────────────────────────────────────
|
|
22
|
+
// Create layouts in src/layouts/MyLayout/index.jsx (auto-discovered).
|
|
23
|
+
// defaultLayout: 'MyLayout',
|
|
24
|
+
|
|
25
|
+
// ─── Props ──────────────────────────────────────────────────────────────────
|
|
26
|
+
// Foundation-wide data accessible via website.foundationProps in components.
|
|
27
|
+
// props: {},
|
|
28
|
+
}
|
|
7
29
|
{{/if}}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Site Configuration
|
|
2
|
+
# Full reference: uniweb docs site | Schema: schemas/site.schema.json
|
|
3
|
+
|
|
1
4
|
name: {{projectName}}
|
|
2
5
|
|
|
3
6
|
{{#if foundationRef}}
|
|
@@ -6,5 +9,51 @@ foundation: {{foundationRef}}
|
|
|
6
9
|
{{/if}}
|
|
7
10
|
index: home
|
|
8
11
|
|
|
12
|
+
# ─── Page Ordering ─────────────────────────────────────────────────────────────
|
|
13
|
+
# Control which pages appear in navigation and in what order.
|
|
14
|
+
# pages: [home, about, ...] # home first, about second, rest auto-discovered
|
|
15
|
+
# pages: [home, ..., contact] # home first, contact last, rest in middle
|
|
16
|
+
# pages: [home, about] # Strict: only these pages in nav (others still built)
|
|
17
|
+
# Or just set the homepage and auto-discover the rest:
|
|
18
|
+
# index: home
|
|
19
|
+
|
|
20
|
+
# ─── Content Paths ─────────────────────────────────────────────────────────────
|
|
21
|
+
# Mount external content directories into your site's page tree.
|
|
22
|
+
# Paths resolve relative to this file.
|
|
23
|
+
#
|
|
24
|
+
# paths:
|
|
25
|
+
# pages/docs: ../../../docs # Mount docs repo at /docs route
|
|
26
|
+
# pages/blog: ../../blog-content # Mount blog content at /blog route
|
|
27
|
+
# layout: ./custom-layout # Custom layout directory
|
|
28
|
+
# collections: ./data # Collections directory
|
|
29
|
+
|
|
30
|
+
# ─── Data Sources ─────────────────────────────────────────────────────────────
|
|
31
|
+
# Define collections (local markdown folders) or fetch remote/local JSON data.
|
|
32
|
+
# Pages and sections reference sources by name via `data: source-name`.
|
|
33
|
+
#
|
|
34
|
+
# collections:
|
|
35
|
+
# articles:
|
|
36
|
+
# path: collections/articles # Folder of .md entity files
|
|
37
|
+
# sort: date desc # Sort by frontmatter field
|
|
38
|
+
#
|
|
39
|
+
# fetch:
|
|
40
|
+
# - url: https://api.example.com/team # Remote JSON
|
|
41
|
+
# schema: team # Access as content.data.team
|
|
42
|
+
# - path: /data/config.json # Local file from public/
|
|
43
|
+
|
|
44
|
+
# ─── Extensions ────────────────────────────────────────────────────────────────
|
|
45
|
+
# Load additional foundations (section types) via URL.
|
|
46
|
+
# extensions:
|
|
47
|
+
# - https://cdn.example.com/effects/foundation.js
|
|
48
|
+
|
|
49
|
+
# ─── Layout ────────────────────────────────────────────────────────────────────
|
|
50
|
+
# Override the foundation's default layout per-site.
|
|
51
|
+
# layout: DocsLayout
|
|
52
|
+
# Or per-page in page.yml: layout: { name: MarketingLayout, hide: [left] }
|
|
53
|
+
|
|
54
|
+
# ─── Base Path ─────────────────────────────────────────────────────────────────
|
|
55
|
+
# For subdirectory deployments (e.g., https://example.com/docs/).
|
|
56
|
+
# base: /docs/
|
|
57
|
+
|
|
9
58
|
build:
|
|
10
59
|
prerender: true
|