@uniweb/build 0.66.1 → 0.67.1
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 -6
- package/src/schema.js +64 -30
- package/src/site/build-site-data.js +2 -2
- package/src/site/content-collector.js +20 -60
- package/src/site/plugin.js +2 -2
- package/src/site/schemaless-data.js +4 -3
- package/src/uwx/site-project.js +3 -2
- package/src/uwx/site.js +1 -2
- package/src/utils/infer-title.js +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.1",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
60
|
"@uniweb/content-reader": "^1.2.5",
|
|
61
|
-
"@uniweb/schemas": "^0.8.0",
|
|
62
|
-
"@uniweb/projections": "^0.7.6",
|
|
63
61
|
"@uniweb/content-writer": "^0.3.4",
|
|
62
|
+
"@uniweb/schemas": "^0.8.1",
|
|
64
63
|
"@uniweb/semantic-parser": "^1.4.1",
|
|
64
|
+
"@uniweb/projections": "^0.7.7",
|
|
65
65
|
"@uniweb/theming": "^0.1.15"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
|
-
"@uniweb/runtime": "^0.27.
|
|
68
|
+
"@uniweb/runtime": "^0.27.10"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"@tailwindcss/vite": "^4.0.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
76
76
|
"vite-plugin-svgr": "^4.0.0",
|
|
77
|
-
"@uniweb/core": "^0.32.
|
|
78
|
-
"@uniweb/runtime": "^0.27.
|
|
77
|
+
"@uniweb/core": "^0.32.2",
|
|
78
|
+
"@uniweb/runtime": "^0.27.10"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
81
81
|
"vite": {
|
package/src/schema.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createHash } from 'node:crypto'
|
|
|
16
16
|
import { isFontVar } from '@uniweb/theming'
|
|
17
17
|
import { join, dirname, extname, basename } from 'node:path'
|
|
18
18
|
import { pathToFileURL } from 'node:url'
|
|
19
|
-
import {
|
|
19
|
+
import { describeChildren, describeVisuals } from '@uniweb/schemas/component'
|
|
20
20
|
import { collectSchemaRefs, buildDataSchemaMap, ownSchemaRefs } from './resolve-data-schema.js'
|
|
21
21
|
import {
|
|
22
22
|
composeSupports,
|
|
@@ -547,41 +547,35 @@ function hasEntryFile(dirPath, dirName) {
|
|
|
547
547
|
/**
|
|
548
548
|
* Create an implicit empty meta for a section type discovered without meta.js
|
|
549
549
|
*/
|
|
550
|
-
function createImplicitMeta(
|
|
551
|
-
return {
|
|
550
|
+
function createImplicitMeta() {
|
|
551
|
+
return {}
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
/**
|
|
555
|
-
* Build a component entry
|
|
555
|
+
* Build a component entry: the `meta.js` default export, plus the build's `name`
|
|
556
|
+
* and `path`.
|
|
556
557
|
*
|
|
557
|
-
* ⭐ `
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
558
|
+
* ⭐ `title` IS THERE ONLY WHEN THE DEVELOPER WROTE ONE, so its absence says the
|
|
559
|
+
* component is unnamed. That difference decides whether a name can be translated:
|
|
560
|
+
* an authored title is the foundation's own words and is shown verbatim in every UI
|
|
561
|
+
* language, while for an unnamed component a consumer has better strings — its
|
|
562
|
+
* `family`'s translated label, else `inferTitle(name)` from `@uniweb/schemas/component`,
|
|
563
|
+
* the words this build used to write in.
|
|
562
564
|
*
|
|
563
|
-
* ⛔
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
* section's `family` has a better string available — one it can localize. A
|
|
567
|
-
* foundation that never named its Hero would otherwise ship the English
|
|
568
|
-
* `"Hero"` to every author in the world.
|
|
569
|
-
*
|
|
570
|
-
* ⚖️ The build itself reads neither field. This is a statement ABOUT the title,
|
|
571
|
-
* for whoever renders it.
|
|
565
|
+
* ⛔ Until 2026-09-27 the build filled `title` from the name whenever `meta.js`
|
|
566
|
+
* declared none, and added `titleInferred: true` to restore the fact the filling-in
|
|
567
|
+
* erased. Consumers derive the title where they use it instead [Diego, 2026-09-27].
|
|
572
568
|
*/
|
|
573
569
|
function buildComponentEntry(name, relativePath, meta) {
|
|
574
|
-
|
|
570
|
+
// ⭐ `name` and `path` are the build's facts, so they are set AFTER the spread: a
|
|
571
|
+
// `meta.js` declaring either cannot change them. ⛔ Until 2026-09-27 they came first,
|
|
572
|
+
// and a `meta.js` `name` became the entry's `name` — which an editor reads as the
|
|
573
|
+
// section type — while the schema stayed keyed by the folder's name.
|
|
574
|
+
return {
|
|
575
|
+
...meta,
|
|
575
576
|
name,
|
|
576
577
|
path: relativePath,
|
|
577
|
-
...meta,
|
|
578
|
-
}
|
|
579
|
-
// Apply title inference if meta has no explicit title
|
|
580
|
-
if (!entry.title) {
|
|
581
|
-
entry.title = inferTitle(name)
|
|
582
|
-
entry.titleInferred = true
|
|
583
578
|
}
|
|
584
|
-
return entry
|
|
585
579
|
}
|
|
586
580
|
|
|
587
581
|
/**
|
|
@@ -637,7 +631,7 @@ async function discoverSectionsInPath(srcDir, sectionsRelPath) {
|
|
|
637
631
|
const name = basename(entry.name, ext)
|
|
638
632
|
if (!isComponentFileName(name)) continue
|
|
639
633
|
|
|
640
|
-
const meta = createImplicitMeta(
|
|
634
|
+
const meta = createImplicitMeta()
|
|
641
635
|
components[name] = {
|
|
642
636
|
...buildComponentEntry(name, sectionsRelPath, meta),
|
|
643
637
|
// Bare file: the entry file IS the file itself (not inside a subdirectory)
|
|
@@ -662,7 +656,7 @@ async function discoverSectionsInPath(srcDir, sectionsRelPath) {
|
|
|
662
656
|
components[entry.name] = buildComponentEntry(entry.name, relativePath, result.meta)
|
|
663
657
|
} else if (hasEntryFile(dirPath, entry.name)) {
|
|
664
658
|
// No meta.js but has entry file — implicit section type at root
|
|
665
|
-
components[entry.name] = buildComponentEntry(entry.name, relativePath, createImplicitMeta(
|
|
659
|
+
components[entry.name] = buildComponentEntry(entry.name, relativePath, createImplicitMeta())
|
|
666
660
|
}
|
|
667
661
|
}
|
|
668
662
|
|
|
@@ -730,7 +724,7 @@ export async function discoverLayoutsInPath(srcDir, layoutsRelPath = LAYOUTS_PAT
|
|
|
730
724
|
const name = basename(entry.name, ext)
|
|
731
725
|
if (!isLayoutName(name)) continue
|
|
732
726
|
|
|
733
|
-
const meta = createImplicitMeta(
|
|
727
|
+
const meta = createImplicitMeta()
|
|
734
728
|
layouts[name] = {
|
|
735
729
|
...buildComponentEntry(name, layoutsRelPath, meta),
|
|
736
730
|
entryFile: entry.name,
|
|
@@ -750,7 +744,7 @@ export async function discoverLayoutsInPath(srcDir, layoutsRelPath = LAYOUTS_PAT
|
|
|
750
744
|
if (result.meta.hidden) continue
|
|
751
745
|
layouts[entry.name] = buildComponentEntry(entry.name, relativePath, result.meta)
|
|
752
746
|
} else if (hasEntryFile(dirPath, entry.name)) {
|
|
753
|
-
layouts[entry.name] = buildComponentEntry(entry.name, relativePath, createImplicitMeta(
|
|
747
|
+
layouts[entry.name] = buildComponentEntry(entry.name, relativePath, createImplicitMeta())
|
|
754
748
|
}
|
|
755
749
|
}
|
|
756
750
|
|
|
@@ -936,6 +930,45 @@ function reportSupports(srcDir, authored, derived, emitted) {
|
|
|
936
930
|
}
|
|
937
931
|
}
|
|
938
932
|
|
|
933
|
+
/**
|
|
934
|
+
* Warn about `children` and `visuals` declarations an editor cannot use — read with
|
|
935
|
+
* `@uniweb/schemas`, the grammar the editor reads them with. The entry is emitted as
|
|
936
|
+
* written either way; these are warnings, never refusals.
|
|
937
|
+
*
|
|
938
|
+
* ⭐ A `children.types` entry must name one of this foundation's section types. A hidden
|
|
939
|
+
* one is not a section type at all, and a child of a type nothing provides renders
|
|
940
|
+
* "Component not found". The one false alarm is a type an extension provides, which
|
|
941
|
+
* this foundation cannot see — the message says so.
|
|
942
|
+
*
|
|
943
|
+
* @param {Object} components - discovered section types, by name
|
|
944
|
+
*/
|
|
945
|
+
export function reportPlacementDeclarations(components) {
|
|
946
|
+
for (const [name, entry] of Object.entries(components)) {
|
|
947
|
+
const children = describeChildren(entry)
|
|
948
|
+
const visuals = describeVisuals(entry)
|
|
949
|
+
for (const problem of [...children.problems, ...visuals.problems]) {
|
|
950
|
+
console.warn(`Warning: ${name} (meta.js): ${problem}`)
|
|
951
|
+
}
|
|
952
|
+
for (const type of children.types || []) {
|
|
953
|
+
if (components[type]) continue
|
|
954
|
+
console.warn(
|
|
955
|
+
`Warning: ${name} (meta.js): children.types names "${type}", which is not a section type of this ` +
|
|
956
|
+
`foundation — hidden, misspelled, or provided by an extension. A child section of a type nothing ` +
|
|
957
|
+
`provides renders "Component not found".`
|
|
958
|
+
)
|
|
959
|
+
}
|
|
960
|
+
// `grid` is a section key framework reserves for the layout of a section's children,
|
|
961
|
+
// so a param by that name never receives what an author writes as `grid:`.
|
|
962
|
+
if (entry.params && typeof entry.params === 'object' && Object.hasOwn(entry.params, 'grid')) {
|
|
963
|
+
console.warn(
|
|
964
|
+
`Warning: ${name} (meta.js): a param named "grid" never receives the author's grid: — it is a section ` +
|
|
965
|
+
`key reserved for the layout of child sections. Offer layouts with children: { grid: [...] } and ` +
|
|
966
|
+
`lay the children out with kit's ChildGrid, or rename the param.`
|
|
967
|
+
)
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
939
972
|
/**
|
|
940
973
|
* Build complete schema for a foundation
|
|
941
974
|
* Returns { _self: { identity + config }, ComponentName: componentMeta, ... }
|
|
@@ -959,6 +992,7 @@ export async function buildSchema(srcDir, sectionPaths, derivedSupports = null)
|
|
|
959
992
|
|
|
960
993
|
// Discover section types
|
|
961
994
|
const components = await discoverComponents(srcDir, sectionPaths)
|
|
995
|
+
reportPlacementDeclarations(components)
|
|
962
996
|
|
|
963
997
|
// Resolve the data schemas the foundation DEFINES — every file in its `schemas/`
|
|
964
998
|
// folder (`ownSchemaRefs`) — and the ones its section bindings reference, carried
|
|
@@ -60,8 +60,8 @@ import {
|
|
|
60
60
|
*
|
|
61
61
|
* ⇒ The rule stands on its own: we do not know which consumers
|
|
62
62
|
* re-derive, so we always ship enough for the ones that do.
|
|
63
|
-
* - `data/<collection>.json` (+
|
|
64
|
-
*
|
|
63
|
+
* - `data/<collection>.json` (+ each record's own file,
|
|
64
|
+
* `data/<collection>/<$name>.json`) — same shape `processQueries` produces today.
|
|
65
65
|
* - `records/<path under the records directory>` — the records' co-located assets, where
|
|
66
66
|
* the compiled records point.
|
|
67
67
|
* - `assets/<media>` — processed images / video posters / PDF
|
|
@@ -39,6 +39,7 @@ import { resolveExtensionUrls } from './extension-urls.js'
|
|
|
39
39
|
import { buildTheme, extractFoundationVars } from '../theme/index.js'
|
|
40
40
|
import { resolveDefaultLocale, resolvePublishableLocales, validateLanguageConfig, pageRouteQuery, currentFor } from '@uniweb/core'
|
|
41
41
|
import { parseFrontmatter } from '../utils/frontmatter.js'
|
|
42
|
+
import { parseGrid } from '@uniweb/schemas/grid'
|
|
42
43
|
|
|
43
44
|
// Try to import content-reader, fall back to simplified parser
|
|
44
45
|
let markdownToProseMirror
|
|
@@ -456,60 +457,6 @@ async function readYamlFile(filePath) {
|
|
|
456
457
|
}
|
|
457
458
|
}
|
|
458
459
|
|
|
459
|
-
/**
|
|
460
|
-
* Extract inset references from a ProseMirror document.
|
|
461
|
-
*
|
|
462
|
-
* Walks the document recursively for `inset_ref` nodes (produced by
|
|
463
|
-
* content-reader for the `{params}` /
|
|
464
|
-
* `[text](@ComponentName){params}` / `[@key]{params}` forms). Each ref
|
|
465
|
-
* is removed and replaced in-place with an `inset_placeholder` node
|
|
466
|
-
* carrying a unique refId. The extracted refs are returned as an array.
|
|
467
|
-
*
|
|
468
|
-
* Inline insets (mid-paragraph) are kept as inline placeholders so the
|
|
469
|
-
* paragraph's text flow is preserved; block-level insets (own line)
|
|
470
|
-
* stay at the document root. Both share the same refId/getInset(refId)
|
|
471
|
-
* lookup machinery — only the position differs.
|
|
472
|
-
*
|
|
473
|
-
* @param {Object} doc - ProseMirror document (mutated in place)
|
|
474
|
-
* @returns {Array} Array of { refId, type, params, description, embedKind }
|
|
475
|
-
*/
|
|
476
|
-
function extractInsets(doc) {
|
|
477
|
-
if (!doc?.content || !Array.isArray(doc.content)) return []
|
|
478
|
-
|
|
479
|
-
const insets = []
|
|
480
|
-
let refIndex = 0
|
|
481
|
-
|
|
482
|
-
function visit(nodes) {
|
|
483
|
-
if (!Array.isArray(nodes)) return
|
|
484
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
485
|
-
const node = nodes[i]
|
|
486
|
-
if (!node) continue
|
|
487
|
-
if (node.type === 'inset_ref') {
|
|
488
|
-
const { component, alt, embedKind, ...params } = node.attrs || {}
|
|
489
|
-
const refId = `inset_${refIndex++}`
|
|
490
|
-
insets.push({
|
|
491
|
-
refId,
|
|
492
|
-
type: component,
|
|
493
|
-
embedKind: embedKind || 'visual',
|
|
494
|
-
params: Object.keys(params).length > 0 ? params : {},
|
|
495
|
-
title: alt || null,
|
|
496
|
-
})
|
|
497
|
-
nodes[i] = {
|
|
498
|
-
type: 'inset_placeholder',
|
|
499
|
-
attrs: { refId, embedKind: embedKind || 'visual' },
|
|
500
|
-
}
|
|
501
|
-
continue
|
|
502
|
-
}
|
|
503
|
-
if (Array.isArray(node.content)) {
|
|
504
|
-
visit(node.content)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
visit(doc.content)
|
|
510
|
-
return insets
|
|
511
|
-
}
|
|
512
|
-
|
|
513
460
|
/**
|
|
514
461
|
* Check if a filename uses the @ prefix (child section convention).
|
|
515
462
|
* @-prefixed files are excluded from auto-discovered top-level sections —
|
|
@@ -1065,13 +1012,28 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
|
|
|
1065
1012
|
// `query`, `fetch` and `data` are never params: `query:` / `fetch:` declare the
|
|
1066
1013
|
// section's own data, and a leftover `data:` is refused (`declaredFetch`).
|
|
1067
1014
|
const { type, preset, input, props, fetch, query, data, id: frontmatterId, ...params } = frontMatter
|
|
1015
|
+
// ⭐ `grid` — the layout of the section's child sections, `3` or `'40/60'` [Diego,
|
|
1016
|
+
// 2026-09-27] — is a name framework reserves, and it stays in the params like
|
|
1017
|
+
// `background`: that is where it is stored and synced, with no field of its own.
|
|
1018
|
+
// `@uniweb/core` lifts it to `block.grid` (a component never receives it as a param)
|
|
1019
|
+
// and kit's `ChildGrid` lays the children out. ⛔ For a few hours on 2026-09-27 it was
|
|
1020
|
+
// taken out of the params into a field the sync wire could not carry.
|
|
1021
|
+
if (params.grid !== undefined && !parseGrid(params.grid)) {
|
|
1022
|
+
console.warn(
|
|
1023
|
+
`[content-collector] ${relative(siteRoot, filePath)}: grid: ${JSON.stringify(params.grid)} is not a layout — ` +
|
|
1024
|
+
`write a column count (grid: 3) or relative widths (grid: '40/60'). The component's own layout applies.`
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1068
1027
|
|
|
1069
|
-
// Convert markdown to ProseMirror
|
|
1028
|
+
// Convert markdown to ProseMirror. ⭐ Insets stay as the author wrote them —
|
|
1029
|
+
// `inset_ref`, `inset_block` — and `@uniweb/core` lifts both when it builds the
|
|
1030
|
+
// section's Block. ⛔ Until 2026-09-27 this extracted each leaf `inset_ref` into a
|
|
1031
|
+
// section-level `insets[]` and left a placeholder, so any document that did not
|
|
1032
|
+
// come through here (a free-form translation, a record body) kept raw references
|
|
1033
|
+
// nothing resolved, and the same section's content differed between a build and a
|
|
1034
|
+
// document stored as written.
|
|
1070
1035
|
const proseMirrorContent = markdownToProseMirror(markdown)
|
|
1071
1036
|
|
|
1072
|
-
// Extract @ component references → insets (mutates doc)
|
|
1073
|
-
const insets = extractInsets(proseMirrorContent)
|
|
1074
|
-
|
|
1075
1037
|
// `query: team` → `fetch: { query: team }`; a list, one config per name, each under
|
|
1076
1038
|
// its own `as` — the one helper every level uses. Unrelated to a section type's
|
|
1077
1039
|
// `meta.js` `data:`, which declares the `content.data` keys a component receives and
|
|
@@ -1092,7 +1054,6 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
|
|
|
1092
1054
|
params: { ...params, ...props },
|
|
1093
1055
|
content: proseMirrorContent,
|
|
1094
1056
|
fetch: parseFetchConfig(resolvedFetch, relative(siteRoot, filePath), { level: 'section' }),
|
|
1095
|
-
...(insets.length > 0 ? { insets } : {}),
|
|
1096
1057
|
subsections: []
|
|
1097
1058
|
}
|
|
1098
1059
|
|
|
@@ -2994,7 +2955,6 @@ export {
|
|
|
2994
2955
|
parseWildcardArray,
|
|
2995
2956
|
applyWildcardOrder,
|
|
2996
2957
|
getDirectChildName,
|
|
2997
|
-
extractInsets,
|
|
2998
2958
|
readYamlFile,
|
|
2999
2959
|
readFolderConfig,
|
|
3000
2960
|
isMarkdownFile,
|
package/src/site/plugin.js
CHANGED
|
@@ -1335,8 +1335,8 @@ export function siteContentPlugin(options = {}) {
|
|
|
1335
1335
|
}
|
|
1336
1336
|
|
|
1337
1337
|
// Handle localized collection data (e.g., /fr/data/articles.json), and a
|
|
1338
|
-
//
|
|
1339
|
-
//
|
|
1338
|
+
// query's per-record files (/fr/data/articles/<$name>.json) — one record
|
|
1339
|
+
// each, translated the same way.
|
|
1340
1340
|
const localeDataMatch = req.url.match(new RegExp(`^\\/(${LOCALE_RE})\\/${DATA_DIR}\\/(.+\\.json)$`))
|
|
1341
1341
|
if (localeDataMatch) {
|
|
1342
1342
|
const locale = localeDataMatch[1]
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* that resolves a data schema syncs as folder entities — content a backend
|
|
7
7
|
* genuinely consumes, queryable, editable, with a `brief:` for its lean shape.
|
|
8
8
|
* A **schema-less** collection has no entity model, so its compiled
|
|
9
|
-
* `dist/data/<name>.json` (plus
|
|
10
|
-
* as files instead. That fallback tier is all this module is about.
|
|
9
|
+
* `dist/data/<name>.json` (plus its per-record files, `<name>/<$name>.json`) is
|
|
10
|
+
* delivered as files instead. That fallback tier is all this module is about.
|
|
11
11
|
*
|
|
12
12
|
* { data: { "<relpath under dist/data>": <json> } } // schema-less only
|
|
13
13
|
*
|
|
@@ -67,7 +67,8 @@ function queryOf(relPath) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* Gather the schema-less set from a built site's dist/ — in memory, one entry per
|
|
71
|
+
* file; nothing is bundled (see the header).
|
|
71
72
|
*
|
|
72
73
|
* @param {string} distDir - the site's built dist/ directory
|
|
73
74
|
* @param {string[]} [schemalessNames] - collection names with no data schema (from
|
package/src/uwx/site-project.js
CHANGED
|
@@ -428,8 +428,9 @@ function reinlineInsets(content, insets) {
|
|
|
428
428
|
* `preset` / `input` / `fetch` / `id` (`stable_id`); the body is the section's
|
|
429
429
|
* content (insets re-inlined) serialized to markdown. Idempotent.
|
|
430
430
|
*
|
|
431
|
-
* Note: `$children` (
|
|
432
|
-
*
|
|
431
|
+
* Note: `$children` (a section's child sections) are NOT written here — the page
|
|
432
|
+
* walk (`pageSectionsToFiles`) writes each to its own file and nests it under its
|
|
433
|
+
* parent in `page.yml::sections:`, in stored order. This writes one section.
|
|
433
434
|
*
|
|
434
435
|
* @param {object} params
|
|
435
436
|
* @param {string} params.filePath
|
package/src/uwx/site.js
CHANGED
|
@@ -312,8 +312,7 @@ function mapSectionData(section) {
|
|
|
312
312
|
setIf(data, 'preset', section.preset ?? undefined)
|
|
313
313
|
setIf(data, 'input', section.input ?? undefined)
|
|
314
314
|
if (Object.keys(params).length > 0) data.params = params
|
|
315
|
-
data.content = section.content
|
|
316
|
-
if (section.insets && section.insets.length > 0) data.insets = section.insets
|
|
315
|
+
data.content = section.content // leaf insets in it as written — no `insets` field since 2026-09-27
|
|
317
316
|
setIf(data, 'fetch', section.fetch ?? undefined)
|
|
318
317
|
setIf(data, 'background', background)
|
|
319
318
|
setIf(data, 'theme_override', themeOverride)
|
package/src/utils/infer-title.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Infer display title from PascalCase component name.
|
|
3
|
-
*
|
|
4
|
-
* TeamRoster → "Team Roster"
|
|
5
|
-
* CTA → "CTA"
|
|
6
|
-
* FAQSection → "FAQ Section"
|
|
7
|
-
* Hero → "Hero"
|
|
8
|
-
*
|
|
9
|
-
* @param {string} name - PascalCase component name
|
|
10
|
-
* @returns {string} Human-readable title
|
|
11
|
-
*/
|
|
12
|
-
export function inferTitle(name) {
|
|
13
|
-
return name
|
|
14
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
15
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
16
|
-
}
|