@uniweb/build 0.6.17 → 0.6.18
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/i18n/index.js +2 -6
- package/src/site/content-collector.js +279 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.18",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
53
|
"@uniweb/schemas": "0.2.1",
|
|
54
|
-
"@uniweb/
|
|
55
|
-
"@uniweb/
|
|
54
|
+
"@uniweb/runtime": "0.5.21",
|
|
55
|
+
"@uniweb/content-reader": "1.1.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/i18n/index.js
CHANGED
|
@@ -169,21 +169,17 @@ async function resolveLocales(configLocales, localesPath) {
|
|
|
169
169
|
/**
|
|
170
170
|
* Extract manifest from site content and write to file
|
|
171
171
|
* @param {string} siteRoot - Site root directory
|
|
172
|
+
* @param {Object} siteContent - Collected site content (from collectSiteContent)
|
|
172
173
|
* @param {Object} options - Options
|
|
173
174
|
* @returns {Object} { manifest, report }
|
|
174
175
|
*/
|
|
175
|
-
export async function extractManifest(siteRoot, options = {}) {
|
|
176
|
+
export async function extractManifest(siteRoot, siteContent, options = {}) {
|
|
176
177
|
const {
|
|
177
178
|
localesDir = DEFAULTS.localesDir,
|
|
178
|
-
siteContentPath = join(siteRoot, 'dist', 'site-content.json'),
|
|
179
179
|
verbose = false,
|
|
180
180
|
dryRun = false
|
|
181
181
|
} = options
|
|
182
182
|
|
|
183
|
-
// Load site content
|
|
184
|
-
const siteContentRaw = await readFile(siteContentPath, 'utf-8')
|
|
185
|
-
const siteContent = JSON.parse(siteContentRaw)
|
|
186
|
-
|
|
187
183
|
// Extract translatable content
|
|
188
184
|
const manifest = extractTranslatableContent(siteContent)
|
|
189
185
|
|
|
@@ -271,23 +271,130 @@ function compareFilenames(a, b) {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
274
|
+
* Extract the name from a config array item.
|
|
275
|
+
* Handles both string entries ("hero") and object entries ({ features: [...] }).
|
|
276
|
+
* @param {*} item - Array item from sections: or pages: config
|
|
277
|
+
* @returns {string|null} The name, or null if not a valid entry
|
|
278
|
+
*/
|
|
279
|
+
function extractItemName(item) {
|
|
280
|
+
if (typeof item === 'string') return item
|
|
281
|
+
if (typeof item === 'object' && item !== null) {
|
|
282
|
+
const keys = Object.keys(item)
|
|
283
|
+
if (keys.length === 1) return keys[0]
|
|
284
|
+
}
|
|
285
|
+
return null
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Parse a config array that may contain '...' rest markers.
|
|
290
|
+
*
|
|
291
|
+
* Returns structured info:
|
|
292
|
+
* - mode 'strict': no '...' — only listed items visible in navigation
|
|
293
|
+
* - mode 'inclusive': '...' present — pinned items + auto-discovered rest
|
|
294
|
+
* - mode 'all': array is just ['...'] — equivalent to omitting config
|
|
295
|
+
*
|
|
296
|
+
* @param {Array} arr - Config array (may contain '...' strings and/or objects)
|
|
297
|
+
* @returns {{ mode: 'strict'|'inclusive'|'all', before: Array, after: Array }|null}
|
|
298
|
+
*/
|
|
299
|
+
function parseWildcardArray(arr) {
|
|
300
|
+
if (!Array.isArray(arr) || arr.length === 0) return null
|
|
301
|
+
|
|
302
|
+
const firstRestIndex = arr.indexOf('...')
|
|
303
|
+
if (firstRestIndex === -1) {
|
|
304
|
+
return { mode: 'strict', before: [...arr], after: [] }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Find last '...' index
|
|
308
|
+
let lastRestIndex = firstRestIndex
|
|
309
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
310
|
+
if (arr[i] === '...') { lastRestIndex = i; break }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const before = arr.slice(0, firstRestIndex).filter(x => x !== '...')
|
|
314
|
+
const after = arr.slice(lastRestIndex + 1).filter(x => x !== '...')
|
|
315
|
+
|
|
316
|
+
if (before.length === 0 && after.length === 0) {
|
|
317
|
+
return { mode: 'all', before: [], after: [] }
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { mode: 'inclusive', before, after }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Apply wildcard-aware ordering to a list of named items.
|
|
276
325
|
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
326
|
+
* - strict: listed items first in listed order, then unlisted (all items returned)
|
|
327
|
+
* - inclusive: before items, then rest (in existing order), then after items
|
|
328
|
+
* - all/null: return items unchanged
|
|
279
329
|
*
|
|
280
330
|
* @param {Array} items - Items with a .name property
|
|
281
|
-
* @param {Array
|
|
282
|
-
* @returns {Array} Reordered items
|
|
331
|
+
* @param {{ mode: string, before: Array, after: Array }|null} parsed - From parseWildcardArray
|
|
332
|
+
* @returns {Array} Reordered items
|
|
283
333
|
*/
|
|
284
|
-
function
|
|
285
|
-
if (!
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
334
|
+
function applyWildcardOrder(items, parsed) {
|
|
335
|
+
if (!parsed || parsed.mode === 'all') return items
|
|
336
|
+
|
|
337
|
+
const itemMap = new Map(items.map(i => [i.name, i]))
|
|
338
|
+
const beforeNames = parsed.before.map(extractItemName).filter(Boolean)
|
|
339
|
+
const afterNames = parsed.after.map(extractItemName).filter(Boolean)
|
|
340
|
+
const allPinnedNames = new Set([...beforeNames, ...afterNames])
|
|
341
|
+
|
|
342
|
+
const beforeItems = beforeNames.filter(n => itemMap.has(n)).map(n => itemMap.get(n))
|
|
343
|
+
const afterItems = afterNames.filter(n => itemMap.has(n)).map(n => itemMap.get(n))
|
|
344
|
+
const rest = items.filter(i => !allPinnedNames.has(i.name))
|
|
345
|
+
|
|
346
|
+
if (parsed.mode === 'strict') {
|
|
347
|
+
// Listed items first, then unlisted (hiding is applied separately)
|
|
348
|
+
return [...beforeItems, ...rest]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Inclusive: before + rest + after
|
|
352
|
+
return [...beforeItems, ...rest, ...afterItems]
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Find the markdown file for a section name, handling numeric prefixes.
|
|
357
|
+
* Tries exact match first ("hero.md"), then prefix-based ("1-hero.md").
|
|
358
|
+
*
|
|
359
|
+
* @param {string} pagePath - Directory containing section files
|
|
360
|
+
* @param {string} sectionName - Logical section name (e.g., 'hero')
|
|
361
|
+
* @param {string[]} [cachedFiles] - Pre-read directory listing (optimization)
|
|
362
|
+
* @returns {{ filePath: string, stableName: string, prefix: string|null }|null}
|
|
363
|
+
*/
|
|
364
|
+
function findSectionFile(pagePath, sectionName, cachedFiles) {
|
|
365
|
+
const exactPath = join(pagePath, `${sectionName}.md`)
|
|
366
|
+
if (existsSync(exactPath)) {
|
|
367
|
+
return { filePath: exactPath, stableName: sectionName, prefix: null }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const files = cachedFiles || []
|
|
371
|
+
for (const file of files) {
|
|
372
|
+
if (!isMarkdownFile(file)) continue
|
|
373
|
+
const { name } = parse(file)
|
|
374
|
+
const { prefix, name: parsedName } = parseNumericPrefix(name)
|
|
375
|
+
if (parsedName === sectionName) {
|
|
376
|
+
return { filePath: join(pagePath, file), stableName: sectionName, prefix }
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return null
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Extract a direct child's folder name from its route, relative to parentRoute.
|
|
385
|
+
* Returns null for the index page (route === parentRoute) or non-direct-children.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} route - Page route (e.g., '/about')
|
|
388
|
+
* @param {string} parentRoute - Parent route (e.g., '/')
|
|
389
|
+
* @returns {string|null}
|
|
390
|
+
*/
|
|
391
|
+
function getDirectChildName(route, parentRoute) {
|
|
392
|
+
if (!route || route === parentRoute) return null
|
|
393
|
+
const prefix = parentRoute === '/' ? '/' : parentRoute + '/'
|
|
394
|
+
if (!route.startsWith(prefix)) return null
|
|
395
|
+
const rest = route.slice(prefix.length)
|
|
396
|
+
if (rest.includes('/')) return null
|
|
397
|
+
return rest
|
|
291
398
|
}
|
|
292
399
|
|
|
293
400
|
/**
|
|
@@ -482,6 +589,9 @@ async function processExplicitSections(sectionsConfig, pagePath, siteRoot, paren
|
|
|
482
589
|
}
|
|
483
590
|
let lastModified = null
|
|
484
591
|
|
|
592
|
+
// Cache directory listing for prefix-based file resolution
|
|
593
|
+
const cachedFiles = await readdir(pagePath)
|
|
594
|
+
|
|
485
595
|
let index = 1
|
|
486
596
|
for (const item of sectionsConfig) {
|
|
487
597
|
let sectionName
|
|
@@ -507,13 +617,14 @@ async function processExplicitSections(sectionsConfig, pagePath, siteRoot, paren
|
|
|
507
617
|
// Build section ID
|
|
508
618
|
const id = parentId ? `${parentId}.${index}` : String(index)
|
|
509
619
|
|
|
510
|
-
// Look for the markdown file
|
|
511
|
-
const
|
|
512
|
-
if (!
|
|
620
|
+
// Look for the markdown file (exact match or prefix-based, e.g., "hero" → "1-hero.md")
|
|
621
|
+
const found = findSectionFile(pagePath, sectionName, cachedFiles)
|
|
622
|
+
if (!found) {
|
|
513
623
|
console.warn(`[content-collector] Section file not found: ${sectionName}.md`)
|
|
514
624
|
index++
|
|
515
625
|
continue
|
|
516
626
|
}
|
|
627
|
+
const filePath = found.filePath
|
|
517
628
|
|
|
518
629
|
// Process the section
|
|
519
630
|
// Use sectionName as stable ID for scroll targeting (e.g., "hero", "features")
|
|
@@ -579,8 +690,9 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
579
690
|
|
|
580
691
|
// Check for explicit sections configuration
|
|
581
692
|
const { sections: sectionsConfig } = pageConfig
|
|
693
|
+
const sectionsParsed = Array.isArray(sectionsConfig) ? parseWildcardArray(sectionsConfig) : null
|
|
582
694
|
|
|
583
|
-
if (sectionsConfig === undefined || sectionsConfig === '*') {
|
|
695
|
+
if (sectionsConfig === undefined || sectionsConfig === '*' || sectionsParsed?.mode === 'all') {
|
|
584
696
|
// Default behavior: discover all .md files, sort by numeric prefix
|
|
585
697
|
const files = await readdir(pagePath)
|
|
586
698
|
const mdFiles = files.filter(isMarkdownFile).sort(compareFilenames)
|
|
@@ -609,8 +721,79 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
609
721
|
// Build hierarchy from dot notation
|
|
610
722
|
hierarchicalSections = buildSectionHierarchy(sections)
|
|
611
723
|
|
|
724
|
+
} else if (sectionsParsed?.mode === 'inclusive') {
|
|
725
|
+
// Inclusive: pinned sections + auto-discovered rest via '...' wildcard
|
|
726
|
+
const files = await readdir(pagePath)
|
|
727
|
+
const mdFiles = files.filter(isMarkdownFile).sort(compareFilenames)
|
|
728
|
+
|
|
729
|
+
// Build name → file info map from discovered files
|
|
730
|
+
const discoveredMap = new Map()
|
|
731
|
+
for (const file of mdFiles) {
|
|
732
|
+
const { name } = parse(file)
|
|
733
|
+
const { prefix, name: stableName } = parseNumericPrefix(name)
|
|
734
|
+
const key = stableName || name
|
|
735
|
+
if (!discoveredMap.has(key)) {
|
|
736
|
+
discoveredMap.set(key, { file, prefix, stableName: key })
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Create items with .name property for applyWildcardOrder
|
|
741
|
+
const allItems = [...discoveredMap.keys()].map(name => ({ name }))
|
|
742
|
+
const ordered = applyWildcardOrder(allItems, sectionsParsed)
|
|
743
|
+
|
|
744
|
+
// Collect subsection configs from the original array (e.g., { features: [a, b] })
|
|
745
|
+
const subsectionConfigs = new Map()
|
|
746
|
+
for (const item of [...sectionsParsed.before, ...sectionsParsed.after]) {
|
|
747
|
+
if (typeof item === 'object' && item !== null) {
|
|
748
|
+
const keys = Object.keys(item)
|
|
749
|
+
if (keys.length === 1) {
|
|
750
|
+
subsectionConfigs.set(keys[0], item[keys[0]])
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Process sections in wildcard-expanded order
|
|
756
|
+
const sections = []
|
|
757
|
+
let sectionIndex = 1
|
|
758
|
+
for (const { name } of ordered) {
|
|
759
|
+
const entry = discoveredMap.get(name)
|
|
760
|
+
if (!entry) {
|
|
761
|
+
console.warn(`[content-collector] Section '${name}' not found in ${pagePath}`)
|
|
762
|
+
continue
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const id = String(sectionIndex)
|
|
766
|
+
const { section, assetCollection: sectionAssets, iconCollection: sectionIcons } =
|
|
767
|
+
await processMarkdownFile(join(pagePath, entry.file), id, siteRoot, entry.stableName)
|
|
768
|
+
sections.push(section)
|
|
769
|
+
pageAssetCollection = mergeAssetCollections(pageAssetCollection, sectionAssets)
|
|
770
|
+
pageIconCollection = mergeIconCollections(pageIconCollection, sectionIcons)
|
|
771
|
+
|
|
772
|
+
// Track last modified
|
|
773
|
+
const fileStat = await stat(join(pagePath, entry.file))
|
|
774
|
+
if (!lastModified || fileStat.mtime > lastModified) {
|
|
775
|
+
lastModified = fileStat.mtime
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Process subsections if configured (e.g., { features: [logocloud, stats] })
|
|
779
|
+
const subsections = subsectionConfigs.get(name)
|
|
780
|
+
if (Array.isArray(subsections) && subsections.length > 0) {
|
|
781
|
+
const subResult = await processExplicitSections(subsections, pagePath, siteRoot, id)
|
|
782
|
+
section.subsections = subResult.sections
|
|
783
|
+
pageAssetCollection = mergeAssetCollections(pageAssetCollection, subResult.assetCollection)
|
|
784
|
+
pageIconCollection = mergeIconCollections(pageIconCollection, subResult.iconCollection)
|
|
785
|
+
if (subResult.lastModified && (!lastModified || subResult.lastModified > lastModified)) {
|
|
786
|
+
lastModified = subResult.lastModified
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
sectionIndex++
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
hierarchicalSections = buildSectionHierarchy(sections)
|
|
794
|
+
|
|
612
795
|
} else if (Array.isArray(sectionsConfig) && sectionsConfig.length > 0) {
|
|
613
|
-
//
|
|
796
|
+
// Strict: explicit sections array (only listed sections processed)
|
|
614
797
|
const result = await processExplicitSections(sectionsConfig, pagePath, siteRoot)
|
|
615
798
|
hierarchicalSections = result.sections
|
|
616
799
|
pageAssetCollection = result.assetCollection
|
|
@@ -722,9 +905,13 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
722
905
|
function determineIndexPage(orderConfig, availableFolders) {
|
|
723
906
|
const { pages: pagesArray, index: indexName } = orderConfig || {}
|
|
724
907
|
|
|
725
|
-
// 1. Explicit pages array - first item is index
|
|
908
|
+
// 1. Explicit pages array - first non-'...' item is index
|
|
726
909
|
if (Array.isArray(pagesArray) && pagesArray.length > 0) {
|
|
727
|
-
|
|
910
|
+
const parsed = parseWildcardArray(pagesArray)
|
|
911
|
+
if (parsed && parsed.before.length > 0) {
|
|
912
|
+
return extractItemName(parsed.before[0])
|
|
913
|
+
}
|
|
914
|
+
// Array starts with '...' or is ['...'] — no index from pages, fall through
|
|
728
915
|
}
|
|
729
916
|
|
|
730
917
|
// 2. Explicit index property
|
|
@@ -788,7 +975,6 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
788
975
|
// Read folder.yml or page.yml to determine mode and get config
|
|
789
976
|
const { config: dirConfig, mode: dirMode } = await readFolderConfig(entryPath, contentMode)
|
|
790
977
|
const numericOrder = typeof dirConfig.order === 'number' ? dirConfig.order : undefined
|
|
791
|
-
const childOrderArray = Array.isArray(dirConfig.order) ? dirConfig.order : undefined
|
|
792
978
|
|
|
793
979
|
pageFolders.push({
|
|
794
980
|
name: entry,
|
|
@@ -798,8 +984,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
798
984
|
dirMode,
|
|
799
985
|
childOrderConfig: {
|
|
800
986
|
pages: dirConfig.pages,
|
|
801
|
-
index: dirConfig.index
|
|
802
|
-
order: childOrderArray
|
|
987
|
+
index: dirConfig.index
|
|
803
988
|
}
|
|
804
989
|
})
|
|
805
990
|
}
|
|
@@ -813,8 +998,20 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
813
998
|
return a.name.localeCompare(b.name)
|
|
814
999
|
})
|
|
815
1000
|
|
|
816
|
-
// Apply
|
|
817
|
-
|
|
1001
|
+
// Apply ordering: pages: (wildcard-aware) > order: [array] (backward compat) > default
|
|
1002
|
+
let orderedFolders
|
|
1003
|
+
let strictPageNames = null
|
|
1004
|
+
|
|
1005
|
+
const pagesParsed = Array.isArray(orderConfig?.pages) ? parseWildcardArray(orderConfig.pages) : null
|
|
1006
|
+
|
|
1007
|
+
if (pagesParsed && pagesParsed.mode !== 'all') {
|
|
1008
|
+
orderedFolders = applyWildcardOrder(pageFolders, pagesParsed)
|
|
1009
|
+
if (pagesParsed.mode === 'strict') {
|
|
1010
|
+
strictPageNames = new Set(pagesParsed.before.map(extractItemName).filter(Boolean))
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
orderedFolders = pageFolders
|
|
1014
|
+
}
|
|
818
1015
|
|
|
819
1016
|
// Check if this directory contains version folders (versioned section)
|
|
820
1017
|
const folderNames = orderedFolders.map(f => f.name)
|
|
@@ -879,12 +1076,28 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
879
1076
|
}
|
|
880
1077
|
}
|
|
881
1078
|
|
|
882
|
-
// Apply
|
|
883
|
-
|
|
1079
|
+
// Apply ordering: pages: (wildcard-aware) > order: [array] (backward compat) > default
|
|
1080
|
+
let orderedMdPages
|
|
1081
|
+
let strictPageNamesFM = null
|
|
884
1082
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1083
|
+
const pagesParsedFM = Array.isArray(orderConfig?.pages) ? parseWildcardArray(orderConfig.pages) : null
|
|
1084
|
+
|
|
1085
|
+
if (pagesParsedFM && pagesParsedFM.mode !== 'all') {
|
|
1086
|
+
orderedMdPages = applyWildcardOrder(mdPageItems, pagesParsedFM)
|
|
1087
|
+
if (pagesParsedFM.mode === 'strict') {
|
|
1088
|
+
strictPageNamesFM = new Set(pagesParsedFM.before.map(extractItemName).filter(Boolean))
|
|
1089
|
+
}
|
|
1090
|
+
} else {
|
|
1091
|
+
orderedMdPages = mdPageItems
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// In folder mode, determine index: pages: first item, or explicit index:
|
|
1095
|
+
let indexName = null
|
|
1096
|
+
if (pagesParsedFM && pagesParsedFM.before.length > 0) {
|
|
1097
|
+
indexName = extractItemName(pagesParsedFM.before[0])
|
|
1098
|
+
} else {
|
|
1099
|
+
indexName = orderConfig?.index || null
|
|
1100
|
+
}
|
|
888
1101
|
|
|
889
1102
|
// Add md-file-pages
|
|
890
1103
|
for (const { name, result } of orderedMdPages) {
|
|
@@ -984,6 +1197,17 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
984
1197
|
}
|
|
985
1198
|
}
|
|
986
1199
|
|
|
1200
|
+
// When pages: is strict (no '...'), hide unlisted direct children from navigation
|
|
1201
|
+
if (strictPageNamesFM) {
|
|
1202
|
+
for (const page of pages) {
|
|
1203
|
+
const childName = getDirectChildName(page.route, parentRoute)
|
|
1204
|
+
|| (page.sourcePath ? getDirectChildName(page.sourcePath, parentRoute) : null)
|
|
1205
|
+
if (childName && !strictPageNamesFM.has(childName) && !page.hidden) {
|
|
1206
|
+
page.hidden = true
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
987
1211
|
return { pages, assetCollection, iconCollection, notFound, versionedScopes }
|
|
988
1212
|
}
|
|
989
1213
|
|
|
@@ -1092,6 +1316,17 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1092
1316
|
}
|
|
1093
1317
|
}
|
|
1094
1318
|
|
|
1319
|
+
// When pages: is strict (no '...'), hide unlisted direct children from navigation
|
|
1320
|
+
if (strictPageNames) {
|
|
1321
|
+
for (const page of pages) {
|
|
1322
|
+
const childName = getDirectChildName(page.route, parentRoute)
|
|
1323
|
+
|| (page.sourcePath ? getDirectChildName(page.sourcePath, parentRoute) : null)
|
|
1324
|
+
if (childName && !strictPageNames.has(childName) && !page.hidden) {
|
|
1325
|
+
page.hidden = true
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1095
1330
|
return { pages, assetCollection, iconCollection, notFound, versionedScopes }
|
|
1096
1331
|
}
|
|
1097
1332
|
|
|
@@ -1232,8 +1467,7 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
1232
1467
|
// Extract page ordering config from site.yml
|
|
1233
1468
|
const siteOrderConfig = {
|
|
1234
1469
|
pages: siteConfig.pages,
|
|
1235
|
-
index: siteConfig.index
|
|
1236
|
-
order: Array.isArray(siteConfig.order) ? siteConfig.order : undefined
|
|
1470
|
+
index: siteConfig.index
|
|
1237
1471
|
}
|
|
1238
1472
|
|
|
1239
1473
|
// Determine root content mode from folder.yml/page.yml presence in pages directory
|
|
@@ -1291,8 +1525,11 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
1291
1525
|
page.parent = parentPage ? parentPage.route : null
|
|
1292
1526
|
}
|
|
1293
1527
|
|
|
1294
|
-
//
|
|
1295
|
-
|
|
1528
|
+
// Page order is determined by per-level sorting during collection:
|
|
1529
|
+
// 1. Numeric 'order' property in page.yml (lower first, within each level)
|
|
1530
|
+
// 2. pages: array in parent config (wildcard-aware, overrides numeric order)
|
|
1531
|
+
// 3. order: [array] in parent config (non-strict, backward compat)
|
|
1532
|
+
// No global re-sort — collection order is authoritative.
|
|
1296
1533
|
|
|
1297
1534
|
// Log asset summary
|
|
1298
1535
|
const assetCount = Object.keys(assetCollection.assets).length
|
|
@@ -1335,4 +1572,12 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
1335
1572
|
}
|
|
1336
1573
|
}
|
|
1337
1574
|
|
|
1575
|
+
// Exported for testing
|
|
1576
|
+
export {
|
|
1577
|
+
extractItemName,
|
|
1578
|
+
parseWildcardArray,
|
|
1579
|
+
applyWildcardOrder,
|
|
1580
|
+
getDirectChildName
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1338
1583
|
export default collectSiteContent
|