@uniweb/build 0.6.19 → 0.6.21
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 +3 -3
- package/src/generate-entry.js +5 -1
- package/src/prerender.js +1 -1
- package/src/schema.js +2 -0
- package/src/site/asset-processor.js +18 -1
- package/src/site/content-collector.js +133 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.21",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
53
|
"@uniweb/content-reader": "1.1.2",
|
|
54
|
-
"@uniweb/runtime": "0.5.
|
|
54
|
+
"@uniweb/runtime": "0.5.22",
|
|
55
55
|
"@uniweb/schemas": "0.2.1"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@tailwindcss/vite": "^4.0.0",
|
|
62
62
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
63
63
|
"vite-plugin-svgr": "^4.0.0",
|
|
64
|
-
"@uniweb/core": "0.4.
|
|
64
|
+
"@uniweb/core": "0.4.8"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"vite": {
|
package/src/generate-entry.js
CHANGED
|
@@ -108,8 +108,12 @@ function generateEntrySource(components, options = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Foundation capabilities import (for custom Layout, props, etc.)
|
|
111
|
+
// Use namespace import to merge named exports (Layout, layouts) into capabilities.
|
|
112
|
+
// This ensures both `export const layouts = {...}` (named) and
|
|
113
|
+
// `export default { layouts: {...} }` (default) work correctly.
|
|
111
114
|
if (foundationExports) {
|
|
112
|
-
lines.push(`import
|
|
115
|
+
lines.push(`import * as _foundationModule from '${foundationExports.path}'`)
|
|
116
|
+
lines.push(`const capabilities = { ..._foundationModule.default, ...(_foundationModule.Layout && { Layout: _foundationModule.Layout }), ...(_foundationModule.layouts && { layouts: _foundationModule.layouts }) }`)
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
// Component imports
|
package/src/prerender.js
CHANGED
|
@@ -569,7 +569,7 @@ function renderBlocks(blocks) {
|
|
|
569
569
|
* Render page layout for SSR
|
|
570
570
|
*/
|
|
571
571
|
function renderLayout(page, website) {
|
|
572
|
-
const RemoteLayout = website.getRemoteLayout()
|
|
572
|
+
const RemoteLayout = website.getRemoteLayout(page.getLayoutName())
|
|
573
573
|
|
|
574
574
|
const headerBlocks = page.getHeaderBlocks()
|
|
575
575
|
const bodyBlocks = page.getBodyBlocks()
|
package/src/schema.js
CHANGED
|
@@ -111,6 +111,8 @@ export async function loadFoundationConfig(srcDir) {
|
|
|
111
111
|
...module.default,
|
|
112
112
|
vars: module.vars || module.default?.vars,
|
|
113
113
|
Layout: module.Layout || module.default?.Layout,
|
|
114
|
+
layouts: module.layouts || module.default?.layouts,
|
|
115
|
+
defaultLayout: module.default?.defaultLayout,
|
|
114
116
|
}
|
|
115
117
|
} catch (error) {
|
|
116
118
|
console.warn(`Warning: Failed to load foundation config ${filePath}:`, error.message)
|
|
@@ -207,6 +207,8 @@ export function rewriteContentPaths(content, pathMapping) {
|
|
|
207
207
|
const result = JSON.parse(JSON.stringify(content))
|
|
208
208
|
|
|
209
209
|
function walk(node) {
|
|
210
|
+
if (!node) return
|
|
211
|
+
|
|
210
212
|
// Rewrite image src
|
|
211
213
|
if (node.type === 'image' && node.attrs?.src) {
|
|
212
214
|
const newPath = pathMapping[node.attrs.src]
|
|
@@ -310,13 +312,28 @@ export function rewriteSiteContentPaths(siteContent, pathMapping) {
|
|
|
310
312
|
result.pages.forEach(processPage)
|
|
311
313
|
}
|
|
312
314
|
|
|
313
|
-
// Process
|
|
315
|
+
// Process named layout section sets
|
|
316
|
+
if (result.layouts) {
|
|
317
|
+
for (const [name, panels] of Object.entries(result.layouts)) {
|
|
318
|
+
for (const panel of ['header', 'footer', 'left', 'right']) {
|
|
319
|
+
if (panels[panel]) processPage(panels[panel])
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Process flat header/footer/left/right (backward compat)
|
|
314
325
|
if (result.header) {
|
|
315
326
|
processPage(result.header)
|
|
316
327
|
}
|
|
317
328
|
if (result.footer) {
|
|
318
329
|
processPage(result.footer)
|
|
319
330
|
}
|
|
331
|
+
if (result.left) {
|
|
332
|
+
processPage(result.left)
|
|
333
|
+
}
|
|
334
|
+
if (result.right) {
|
|
335
|
+
processPage(result.right)
|
|
336
|
+
}
|
|
320
337
|
|
|
321
338
|
// Remove the assets manifest from output (no longer needed at runtime)
|
|
322
339
|
delete result.assets
|
|
@@ -765,7 +765,7 @@ async function processExplicitSections(sectionsConfig, pagePath, siteRoot, paren
|
|
|
765
765
|
* @param {Object} options.versionContext - Version context from parent { version, versionMeta, scope }
|
|
766
766
|
* @returns {Object} Page data with assets manifest
|
|
767
767
|
*/
|
|
768
|
-
async function processPage(pagePath, pageName, siteRoot, { isIndex = false, parentRoute = '/', parentFetch = null, versionContext = null } = {}) {
|
|
768
|
+
async function processPage(pagePath, pageName, siteRoot, { isIndex = false, parentRoute = '/', parentFetch = null, versionContext = null, layoutName = null } = {}) {
|
|
769
769
|
const pageConfig = await readYamlFile(join(pagePath, 'page.yml'))
|
|
770
770
|
|
|
771
771
|
// Note: We no longer skip hidden pages here - they still exist as valid pages,
|
|
@@ -925,7 +925,15 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
925
925
|
const sourcePath = isIndex ? folderRoute : null
|
|
926
926
|
|
|
927
927
|
// Extract configuration
|
|
928
|
-
const { seo = {}, layout
|
|
928
|
+
const { seo = {}, layout: layoutConfig, ...restConfig } = pageConfig
|
|
929
|
+
|
|
930
|
+
// Resolve layout name: page.yml layout (string or object.name) > inherited from parent > null
|
|
931
|
+
const pageLayoutName = typeof layoutConfig === 'string' ? layoutConfig
|
|
932
|
+
: layoutConfig?.name || null
|
|
933
|
+
const resolvedLayoutName = pageLayoutName || layoutName || null
|
|
934
|
+
|
|
935
|
+
// Layout panel visibility (from object form of layout config)
|
|
936
|
+
const layoutObj = typeof layoutConfig === 'object' && layoutConfig !== null ? layoutConfig : {}
|
|
929
937
|
|
|
930
938
|
// For dynamic routes, determine the parent's data schema
|
|
931
939
|
// This tells prerender which data array to iterate over
|
|
@@ -960,12 +968,13 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
960
968
|
hideInHeader: pageConfig.hideInHeader || false, // Hide from header nav
|
|
961
969
|
hideInFooter: pageConfig.hideInFooter || false, // Hide from footer nav
|
|
962
970
|
|
|
963
|
-
// Layout options (per-page overrides)
|
|
971
|
+
// Layout options (named layout + per-page overrides)
|
|
964
972
|
layout: {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
973
|
+
...(resolvedLayoutName ? { name: resolvedLayoutName } : {}),
|
|
974
|
+
header: layoutObj.header !== false, // Show header (default true)
|
|
975
|
+
footer: layoutObj.footer !== false, // Show footer (default true)
|
|
976
|
+
leftPanel: layoutObj.leftPanel !== false, // Show left panel (default true)
|
|
977
|
+
rightPanel: layoutObj.rightPanel !== false // Show right panel (default true)
|
|
969
978
|
},
|
|
970
979
|
|
|
971
980
|
seo: {
|
|
@@ -1045,7 +1054,7 @@ function determineIndexPage(orderConfig, availableFolders) {
|
|
|
1045
1054
|
* @param {string} contentMode - 'sections' (default) or 'pages' (md files are child pages)
|
|
1046
1055
|
* @returns {Promise<Object>} { pages, assetCollection, iconCollection, notFound, versionedScopes }
|
|
1047
1056
|
*/
|
|
1048
|
-
async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig = {}, parentFetch = null, versionContext = null, contentMode = 'sections', mounts = null) {
|
|
1057
|
+
async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig = {}, parentFetch = null, versionContext = null, contentMode = 'sections', mounts = null, parentLayoutName = null) {
|
|
1049
1058
|
const entries = await readdir(dirPath)
|
|
1050
1059
|
const pages = []
|
|
1051
1060
|
let assetCollection = {
|
|
@@ -1072,6 +1081,10 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1072
1081
|
const { config: dirConfig, mode: dirMode } = await readFolderConfig(entryPath, contentMode)
|
|
1073
1082
|
const numericOrder = typeof dirConfig.order === 'number' ? dirConfig.order : undefined
|
|
1074
1083
|
|
|
1084
|
+
// Extract layout name from folder config (folder.yml layout: or page.yml layout:)
|
|
1085
|
+
const folderLayout = typeof dirConfig.layout === 'string' ? dirConfig.layout
|
|
1086
|
+
: dirConfig.layout?.name || null
|
|
1087
|
+
|
|
1075
1088
|
pageFolders.push({
|
|
1076
1089
|
name: entry,
|
|
1077
1090
|
path: entryPath,
|
|
@@ -1081,7 +1094,8 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1081
1094
|
childOrderConfig: {
|
|
1082
1095
|
pages: dirConfig.pages,
|
|
1083
1096
|
index: dirConfig.index
|
|
1084
|
-
}
|
|
1097
|
+
},
|
|
1098
|
+
childLayoutName: folderLayout
|
|
1085
1099
|
})
|
|
1086
1100
|
}
|
|
1087
1101
|
|
|
@@ -1090,6 +1104,8 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1090
1104
|
for (const [routeSegment, mountPath] of mounts) {
|
|
1091
1105
|
if (!pageFolders.some(f => f.name === routeSegment)) {
|
|
1092
1106
|
const { config: mountConfig } = await readFolderConfig(mountPath, 'pages')
|
|
1107
|
+
const mountLayout = typeof mountConfig.layout === 'string' ? mountConfig.layout
|
|
1108
|
+
: mountConfig.layout?.name || null
|
|
1093
1109
|
pageFolders.push({
|
|
1094
1110
|
name: routeSegment,
|
|
1095
1111
|
path: mountPath,
|
|
@@ -1099,7 +1115,8 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1099
1115
|
childOrderConfig: {
|
|
1100
1116
|
pages: mountConfig.pages,
|
|
1101
1117
|
index: mountConfig.index
|
|
1102
|
-
}
|
|
1118
|
+
},
|
|
1119
|
+
childLayoutName: mountLayout
|
|
1103
1120
|
})
|
|
1104
1121
|
}
|
|
1105
1122
|
}
|
|
@@ -1140,7 +1157,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1140
1157
|
versionedScopes.set(parentRoute, versionMeta)
|
|
1141
1158
|
|
|
1142
1159
|
for (const folder of orderedFolders) {
|
|
1143
|
-
const { name: entry, path: entryPath, childOrderConfig } = folder
|
|
1160
|
+
const { name: entry, path: entryPath, childOrderConfig, childLayoutName } = folder
|
|
1144
1161
|
|
|
1145
1162
|
if (isVersionFolder(entry)) {
|
|
1146
1163
|
const versionInfo = versionMeta.versions.find(v => v.id === entry)
|
|
@@ -1153,7 +1170,8 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1153
1170
|
|
|
1154
1171
|
const subResult = await collectPagesRecursive(
|
|
1155
1172
|
entryPath, versionRoute, siteRoot, childOrderConfig, parentFetch,
|
|
1156
|
-
{ version: versionInfo, versionMeta, scope: parentRoute }
|
|
1173
|
+
{ version: versionInfo, versionMeta, scope: parentRoute },
|
|
1174
|
+
'sections', null, childLayoutName || parentLayoutName
|
|
1157
1175
|
)
|
|
1158
1176
|
|
|
1159
1177
|
pages.push(...subResult.pages)
|
|
@@ -1164,7 +1182,8 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1164
1182
|
}
|
|
1165
1183
|
} else {
|
|
1166
1184
|
const result = await processPage(entryPath, entry, siteRoot, {
|
|
1167
|
-
isIndex: false, parentRoute, parentFetch
|
|
1185
|
+
isIndex: false, parentRoute, parentFetch,
|
|
1186
|
+
layoutName: childLayoutName || parentLayoutName
|
|
1168
1187
|
})
|
|
1169
1188
|
if (result) {
|
|
1170
1189
|
pages.push(result.page)
|
|
@@ -1228,18 +1247,25 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1228
1247
|
page.route = parentRoute
|
|
1229
1248
|
}
|
|
1230
1249
|
|
|
1250
|
+
// Inherit layout name from parent (folder.yml or site.yml cascade)
|
|
1251
|
+
if (parentLayoutName && !page.layout.name) {
|
|
1252
|
+
page.layout.name = parentLayoutName
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1231
1255
|
pages.push(page)
|
|
1232
1256
|
}
|
|
1233
1257
|
|
|
1234
1258
|
// Process subdirectories
|
|
1235
1259
|
for (const folder of orderedFolders) {
|
|
1236
|
-
const { name: entry, path: entryPath, dirConfig, dirMode, childOrderConfig } = folder
|
|
1260
|
+
const { name: entry, path: entryPath, dirConfig, dirMode, childOrderConfig, childLayoutName } = folder
|
|
1237
1261
|
const isIndex = entry === indexName
|
|
1262
|
+
const effectiveLayout = childLayoutName || parentLayoutName
|
|
1238
1263
|
|
|
1239
1264
|
if (dirMode === 'sections') {
|
|
1240
1265
|
// Subdirectory overrides to page mode — process normally
|
|
1241
1266
|
const result = await processPage(entryPath, entry, siteRoot, {
|
|
1242
|
-
isIndex, parentRoute, parentFetch, versionContext
|
|
1267
|
+
isIndex, parentRoute, parentFetch, versionContext,
|
|
1268
|
+
layoutName: effectiveLayout
|
|
1243
1269
|
})
|
|
1244
1270
|
|
|
1245
1271
|
if (result) {
|
|
@@ -1252,7 +1278,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1252
1278
|
const childDirPath = mounts?.get(entry) || entryPath
|
|
1253
1279
|
const childParentRoute = isIndex ? parentRoute : page.route
|
|
1254
1280
|
const childFetch = page.fetch || parentFetch
|
|
1255
|
-
const subResult = await collectPagesRecursive(childDirPath, childParentRoute, siteRoot, childOrderConfig, childFetch, versionContext, 'sections', null)
|
|
1281
|
+
const subResult = await collectPagesRecursive(childDirPath, childParentRoute, siteRoot, childOrderConfig, childFetch, versionContext, 'sections', null, effectiveLayout)
|
|
1256
1282
|
pages.push(...subResult.pages)
|
|
1257
1283
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1258
1284
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -1266,6 +1292,9 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1266
1292
|
? parentRoute
|
|
1267
1293
|
: parentRoute === '/' ? `/${entry}` : `${parentRoute}/${entry}`
|
|
1268
1294
|
|
|
1295
|
+
// Resolve layout for container page
|
|
1296
|
+
const containerLayoutObj = typeof dirConfig.layout === 'object' && dirConfig.layout !== null ? dirConfig.layout : {}
|
|
1297
|
+
|
|
1269
1298
|
const containerPage = {
|
|
1270
1299
|
route: containerRoute,
|
|
1271
1300
|
sourcePath: isIndex ? (parentRoute === '/' ? `/${entry}` : `${parentRoute}/${entry}`) : null,
|
|
@@ -1285,10 +1314,11 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1285
1314
|
hideInHeader: dirConfig.hideInHeader || false,
|
|
1286
1315
|
hideInFooter: dirConfig.hideInFooter || false,
|
|
1287
1316
|
layout: {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1317
|
+
...(effectiveLayout ? { name: effectiveLayout } : {}),
|
|
1318
|
+
header: containerLayoutObj.header !== false,
|
|
1319
|
+
footer: containerLayoutObj.footer !== false,
|
|
1320
|
+
leftPanel: containerLayoutObj.leftPanel !== false,
|
|
1321
|
+
rightPanel: containerLayoutObj.rightPanel !== false
|
|
1292
1322
|
},
|
|
1293
1323
|
seo: {
|
|
1294
1324
|
noindex: dirConfig.seo?.noindex || false,
|
|
@@ -1305,7 +1335,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1305
1335
|
|
|
1306
1336
|
// Recurse in folder mode
|
|
1307
1337
|
const childDirPath = mounts?.get(entry) || entryPath
|
|
1308
|
-
const subResult = await collectPagesRecursive(childDirPath, containerRoute, siteRoot, childOrderConfig, parentFetch, versionContext, 'pages', null)
|
|
1338
|
+
const subResult = await collectPagesRecursive(childDirPath, containerRoute, siteRoot, childOrderConfig, parentFetch, versionContext, 'pages', null, effectiveLayout)
|
|
1309
1339
|
pages.push(...subResult.pages)
|
|
1310
1340
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1311
1341
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -1340,8 +1370,9 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1340
1370
|
|
|
1341
1371
|
// Second pass: process each page folder
|
|
1342
1372
|
for (const folder of orderedFolders) {
|
|
1343
|
-
const { name: entry, path: entryPath, dirConfig, dirMode, childOrderConfig } = folder
|
|
1373
|
+
const { name: entry, path: entryPath, dirConfig, dirMode, childOrderConfig, childLayoutName } = folder
|
|
1344
1374
|
const isIndex = entry === indexPageName
|
|
1375
|
+
const effectiveLayout = childLayoutName || parentLayoutName
|
|
1345
1376
|
|
|
1346
1377
|
if (dirMode === 'pages') {
|
|
1347
1378
|
// Child directory switches to folder mode (has folder.yml) —
|
|
@@ -1350,6 +1381,9 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1350
1381
|
? parentRoute
|
|
1351
1382
|
: parentRoute === '/' ? `/${entry}` : `${parentRoute}/${entry}`
|
|
1352
1383
|
|
|
1384
|
+
// Resolve layout for container page
|
|
1385
|
+
const containerLayoutObj = typeof dirConfig.layout === 'object' && dirConfig.layout !== null ? dirConfig.layout : {}
|
|
1386
|
+
|
|
1353
1387
|
const containerPage = {
|
|
1354
1388
|
route: containerRoute,
|
|
1355
1389
|
sourcePath: isIndex ? (parentRoute === '/' ? `/${entry}` : `${parentRoute}/${entry}`) : null,
|
|
@@ -1369,10 +1403,11 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1369
1403
|
hideInHeader: dirConfig.hideInHeader || false,
|
|
1370
1404
|
hideInFooter: dirConfig.hideInFooter || false,
|
|
1371
1405
|
layout: {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1406
|
+
...(effectiveLayout ? { name: effectiveLayout } : {}),
|
|
1407
|
+
header: containerLayoutObj.header !== false,
|
|
1408
|
+
footer: containerLayoutObj.footer !== false,
|
|
1409
|
+
leftPanel: containerLayoutObj.leftPanel !== false,
|
|
1410
|
+
rightPanel: containerLayoutObj.rightPanel !== false
|
|
1376
1411
|
},
|
|
1377
1412
|
seo: {
|
|
1378
1413
|
noindex: dirConfig.seo?.noindex || false,
|
|
@@ -1392,7 +1427,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1392
1427
|
}
|
|
1393
1428
|
|
|
1394
1429
|
const childDirPath = mounts?.get(entry) || entryPath
|
|
1395
|
-
const subResult = await collectPagesRecursive(childDirPath, containerRoute, siteRoot, childOrderConfig, parentFetch, versionContext, 'pages', null)
|
|
1430
|
+
const subResult = await collectPagesRecursive(childDirPath, containerRoute, siteRoot, childOrderConfig, parentFetch, versionContext, 'pages', null, effectiveLayout)
|
|
1396
1431
|
pages.push(...subResult.pages)
|
|
1397
1432
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1398
1433
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -1402,7 +1437,8 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1402
1437
|
} else {
|
|
1403
1438
|
// Sections mode — process directory as a page (existing behavior)
|
|
1404
1439
|
const result = await processPage(entryPath, entry, siteRoot, {
|
|
1405
|
-
isIndex, parentRoute, parentFetch, versionContext
|
|
1440
|
+
isIndex, parentRoute, parentFetch, versionContext,
|
|
1441
|
+
layoutName: effectiveLayout
|
|
1406
1442
|
})
|
|
1407
1443
|
|
|
1408
1444
|
if (result) {
|
|
@@ -1424,7 +1460,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1424
1460
|
? (hasExplicitOrder ? parentRoute : (page.sourcePath || page.route))
|
|
1425
1461
|
: page.route
|
|
1426
1462
|
const childFetch = page.fetch || parentFetch
|
|
1427
|
-
const subResult = await collectPagesRecursive(childDirPath, childParentRoute, siteRoot, childOrderConfig, childFetch, versionContext, dirMode, null)
|
|
1463
|
+
const subResult = await collectPagesRecursive(childDirPath, childParentRoute, siteRoot, childOrderConfig, childFetch, versionContext, dirMode, null, effectiveLayout)
|
|
1428
1464
|
pages.push(...subResult.pages)
|
|
1429
1465
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1430
1466
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -1481,37 +1517,35 @@ async function loadFoundationVars(foundationPath) {
|
|
|
1481
1517
|
}
|
|
1482
1518
|
|
|
1483
1519
|
/**
|
|
1484
|
-
* Collect
|
|
1485
|
-
*
|
|
1486
|
-
* Layout panels (header, footer, left, right) are persistent regions
|
|
1487
|
-
* that appear on every page. They live in layout/ parallel to pages/.
|
|
1520
|
+
* Collect panel data (header, footer, left, right) from a single directory.
|
|
1488
1521
|
*
|
|
1489
|
-
* Supports two forms:
|
|
1490
|
-
* - Folder:
|
|
1491
|
-
* - File shorthand:
|
|
1522
|
+
* Supports two forms per panel:
|
|
1523
|
+
* - Folder: dir/header/ (directory with .md files, like a page)
|
|
1524
|
+
* - File shorthand: dir/header.md (single markdown file)
|
|
1492
1525
|
* Folder takes priority when both exist.
|
|
1493
1526
|
*
|
|
1494
|
-
* @param {string}
|
|
1527
|
+
* @param {string} dir - Directory to scan for panel files
|
|
1495
1528
|
* @param {string} siteRoot - Path to site root
|
|
1529
|
+
* @param {string} routePrefix - Route prefix for panel pages (e.g., '/layout' or '/layout/marketing')
|
|
1496
1530
|
* @returns {Promise<Object>} { header, footer, left, right }
|
|
1497
1531
|
*/
|
|
1498
|
-
async function
|
|
1532
|
+
async function collectPanelsFromDir(dir, siteRoot, routePrefix = '/layout') {
|
|
1499
1533
|
const result = { header: null, footer: null, left: null, right: null }
|
|
1500
1534
|
|
|
1501
|
-
if (!existsSync(
|
|
1535
|
+
if (!existsSync(dir)) return result
|
|
1502
1536
|
|
|
1503
1537
|
const knownPanels = ['header', 'footer', 'left', 'right']
|
|
1504
|
-
const entries = await readdir(
|
|
1538
|
+
const entries = await readdir(dir)
|
|
1505
1539
|
|
|
1506
1540
|
for (const panel of knownPanels) {
|
|
1507
1541
|
// Folder form (higher priority)
|
|
1508
1542
|
if (entries.includes(panel)) {
|
|
1509
|
-
const entryPath = join(
|
|
1543
|
+
const entryPath = join(dir, panel)
|
|
1510
1544
|
const stats = await stat(entryPath)
|
|
1511
1545
|
if (stats.isDirectory()) {
|
|
1512
1546
|
const pageResult = await processPage(entryPath, panel, siteRoot, {
|
|
1513
1547
|
isIndex: false,
|
|
1514
|
-
parentRoute:
|
|
1548
|
+
parentRoute: routePrefix
|
|
1515
1549
|
})
|
|
1516
1550
|
if (pageResult) {
|
|
1517
1551
|
result[panel] = pageResult.page
|
|
@@ -1520,13 +1554,13 @@ async function collectLayoutPanels(layoutDir, siteRoot) {
|
|
|
1520
1554
|
}
|
|
1521
1555
|
}
|
|
1522
1556
|
|
|
1523
|
-
// File shorthand:
|
|
1557
|
+
// File shorthand: header.md
|
|
1524
1558
|
const mdFile = `${panel}.md`
|
|
1525
1559
|
if (entries.includes(mdFile)) {
|
|
1526
|
-
const filePath = join(
|
|
1560
|
+
const filePath = join(dir, mdFile)
|
|
1527
1561
|
const { section } = await processMarkdownFile(filePath, '1', siteRoot, panel)
|
|
1528
1562
|
result[panel] = {
|
|
1529
|
-
route:
|
|
1563
|
+
route: `${routePrefix}/${panel}`,
|
|
1530
1564
|
title: panel.charAt(0).toUpperCase() + panel.slice(1),
|
|
1531
1565
|
description: '',
|
|
1532
1566
|
layout: { header: true, footer: true, leftPanel: true, rightPanel: true },
|
|
@@ -1538,6 +1572,50 @@ async function collectLayoutPanels(layoutDir, siteRoot) {
|
|
|
1538
1572
|
return result
|
|
1539
1573
|
}
|
|
1540
1574
|
|
|
1575
|
+
/**
|
|
1576
|
+
* Collect layout panels from the layout/ directory, including named layout subdirectories.
|
|
1577
|
+
*
|
|
1578
|
+
* Root-level files/folders are the "default" layout's panels.
|
|
1579
|
+
* Subdirectories (other than the four known panel names) are named layouts,
|
|
1580
|
+
* each self-contained with its own panel set.
|
|
1581
|
+
*
|
|
1582
|
+
* @param {string} layoutDir - Path to layout directory
|
|
1583
|
+
* @param {string} siteRoot - Path to site root
|
|
1584
|
+
* @returns {Promise<Object>} { layouts, header, footer, left, right }
|
|
1585
|
+
*/
|
|
1586
|
+
async function collectLayouts(layoutDir, siteRoot) {
|
|
1587
|
+
if (!existsSync(layoutDir)) {
|
|
1588
|
+
return { layouts: null, header: null, footer: null, left: null, right: null }
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// Collect root-level panels (= "default" layout, backward compat)
|
|
1592
|
+
const defaultPanels = await collectPanelsFromDir(layoutDir, siteRoot, '/layout')
|
|
1593
|
+
|
|
1594
|
+
// Scan for named layout subdirectories
|
|
1595
|
+
const entries = await readdir(layoutDir, { withFileTypes: true })
|
|
1596
|
+
const knownPanels = new Set(['header', 'footer', 'left', 'right'])
|
|
1597
|
+
const namedLayouts = {}
|
|
1598
|
+
|
|
1599
|
+
for (const entry of entries) {
|
|
1600
|
+
if (!entry.isDirectory()) continue
|
|
1601
|
+
if (knownPanels.has(entry.name)) continue // Skip panel folders (belong to default)
|
|
1602
|
+
if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue
|
|
1603
|
+
|
|
1604
|
+
// This is a named layout subdirectory
|
|
1605
|
+
const subdir = join(layoutDir, entry.name)
|
|
1606
|
+
namedLayouts[entry.name] = await collectPanelsFromDir(subdir, siteRoot, `/layout/${entry.name}`)
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if (Object.keys(namedLayouts).length === 0) {
|
|
1610
|
+
// No named layouts — backward compatible mode
|
|
1611
|
+
return { layouts: null, ...defaultPanels }
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Named layouts mode: root panels become "default", subdirs are named
|
|
1615
|
+
const layouts = { default: defaultPanels, ...namedLayouts }
|
|
1616
|
+
return { layouts, ...defaultPanels }
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1541
1619
|
/**
|
|
1542
1620
|
* Collect all site content
|
|
1543
1621
|
*
|
|
@@ -1595,12 +1673,16 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
1595
1673
|
// Determine root content mode from folder.yml/page.yml presence in pages directory
|
|
1596
1674
|
const { mode: rootContentMode } = await readFolderConfig(pagesPath, 'sections')
|
|
1597
1675
|
|
|
1598
|
-
// Collect layout panels from layout/ directory
|
|
1599
|
-
const { header, footer, left, right } = await
|
|
1676
|
+
// Collect layout panels from layout/ directory (including named layout subdirectories)
|
|
1677
|
+
const { layouts, header, footer, left, right } = await collectLayouts(layoutPath, sitePath)
|
|
1678
|
+
|
|
1679
|
+
// Site-level layout name (from site.yml layout: field)
|
|
1680
|
+
const siteLayoutName = typeof siteConfig.layout === 'string' ? siteConfig.layout
|
|
1681
|
+
: siteConfig.layout?.name || null
|
|
1600
1682
|
|
|
1601
1683
|
// Recursively collect all pages
|
|
1602
1684
|
const { pages, assetCollection, iconCollection, notFound, versionedScopes } =
|
|
1603
|
-
await collectPagesRecursive(pagesPath, '/', sitePath, siteOrderConfig, null, null, rootContentMode, mounts)
|
|
1685
|
+
await collectPagesRecursive(pagesPath, '/', sitePath, siteOrderConfig, null, null, rootContentMode, mounts, siteLayoutName)
|
|
1604
1686
|
|
|
1605
1687
|
// Deduplicate: remove content-less container pages whose route duplicates
|
|
1606
1688
|
// a content-bearing page (e.g., a promoted index page)
|
|
@@ -1679,6 +1761,9 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
1679
1761
|
css: themeCSS
|
|
1680
1762
|
},
|
|
1681
1763
|
pages,
|
|
1764
|
+
// Named layout section sets (null if no named layouts — backward compat)
|
|
1765
|
+
layouts,
|
|
1766
|
+
// Flat panel fields (always present for backward compat)
|
|
1682
1767
|
header,
|
|
1683
1768
|
footer,
|
|
1684
1769
|
left,
|