@uniweb/build 0.11.5 → 0.11.6
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/content/index.js +6 -5
- package/src/site/xref-registry.js +0 -192
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@uniweb/theming": "0.1.3"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@uniweb/runtime": "0.8.
|
|
61
|
+
"@uniweb/runtime": "0.8.8",
|
|
62
62
|
"@uniweb/content-reader": "1.1.7",
|
|
63
63
|
"@uniweb/schemas": "0.2.1"
|
|
64
64
|
},
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@tailwindcss/vite": "^4.0.0",
|
|
70
70
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
71
71
|
"vite-plugin-svgr": "^4.0.0",
|
|
72
|
-
"@uniweb/core": "0.7.
|
|
72
|
+
"@uniweb/core": "0.7.7"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
|
75
75
|
"vite": {
|
package/src/content/index.js
CHANGED
|
@@ -31,8 +31,9 @@ export {
|
|
|
31
31
|
singularize,
|
|
32
32
|
} from '../site/data-fetcher.js'
|
|
33
33
|
|
|
34
|
-
// Cross-reference registry
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
34
|
+
// Cross-reference registry moved out of build into kit
|
|
35
|
+
// (@uniweb/kit/xref). Foundations that need cross-references import
|
|
36
|
+
// `buildXrefRegistry` from there and re-export it via
|
|
37
|
+
// `foundation.xref.build`; the runtime calls it during initialization
|
|
38
|
+
// (setup.js / ssr-renderer.js). See framework/kit/src/xref/registry.js
|
|
39
|
+
// for the implementation.
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-reference registry — populated at content-collection time.
|
|
3
|
-
*
|
|
4
|
-
* Walks the parsed document tree to find every block-level element
|
|
5
|
-
* carrying a {#id} attribute, infers the element's kind from its node
|
|
6
|
-
* type, and records the entry with a per-kind counter. The result
|
|
7
|
-
* attaches to siteContent.xref and is consumed by the framework's
|
|
8
|
-
* <Ref> component to render `[#id]` cross-references.
|
|
9
|
-
*
|
|
10
|
-
* Built-in kinds:
|
|
11
|
-
* - heading → 'section' (hierarchical counter: 1, 1.1, 1.1.1, 2, …)
|
|
12
|
-
* - image → 'figure' (flat arabic counter)
|
|
13
|
-
* - math_display → 'equation' (flat arabic counter)
|
|
14
|
-
* - table → 'table' (flat arabic counter)
|
|
15
|
-
*
|
|
16
|
-
* Foundation extensions: foundations may declare additional kinds via
|
|
17
|
-
* `foundation.xref.kinds`. The id-collection pass uses the foundation's
|
|
18
|
-
* declared `prefix` (e.g. `thm` → `theorem`) to classify ids that don't
|
|
19
|
-
* land on a built-in element type.
|
|
20
|
-
*
|
|
21
|
-
* The registry is per-document, not per-page: counters span the whole
|
|
22
|
-
* document so authors get consistent numbering even when their document
|
|
23
|
-
* spans multiple pages. (For page-local counter resets, foundations can
|
|
24
|
-
* declare `resetOn: 'chapter'` on a kind — applied during render, not
|
|
25
|
-
* here.)
|
|
26
|
-
*
|
|
27
|
-
* Output shape:
|
|
28
|
-
* {
|
|
29
|
-
* entries: {
|
|
30
|
-
* <id>: {
|
|
31
|
-
* id, // the id itself
|
|
32
|
-
* kind, // 'figure' | 'equation' | 'section' | 'table' | <foundation-declared>
|
|
33
|
-
* counter, // number for flat kinds, dotted string for hierarchical
|
|
34
|
-
* counterText, // displayable counter ('3', '3.2')
|
|
35
|
-
* sourcePath, // page route the id was declared on (for back-refs)
|
|
36
|
-
* caption, // (figures, tables) caption attr, when set
|
|
37
|
-
* text, // (sections) heading's plain text content
|
|
38
|
-
* latex, // (equations) latex source for the equation
|
|
39
|
-
* },
|
|
40
|
-
* },
|
|
41
|
-
* }
|
|
42
|
-
*
|
|
43
|
-
* `caption`, `text`, and `latex` are populated only for the elements
|
|
44
|
-
* that carry them — they're undefined on other kinds. List sections
|
|
45
|
-
* (ListOfFigures / ListOfTables / TableOfContents) read these to render
|
|
46
|
-
* "Figure 3 — A diagram of mitosis ........ 47" entries without
|
|
47
|
-
* re-walking the document tree. Other consumers (the framework's <Ref>
|
|
48
|
-
* component, Typst / LaTeX cross-reference emitters) ignore them.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
const KIND_BY_TYPE = {
|
|
52
|
-
heading: 'section',
|
|
53
|
-
image: 'figure',
|
|
54
|
-
math_display: 'equation',
|
|
55
|
-
table: 'table',
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function buildXrefRegistry(siteContent, options = {}) {
|
|
59
|
-
const { foundationKinds = {}, onWarn = (msg) => console.warn(msg) } = options
|
|
60
|
-
|
|
61
|
-
const entries = {}
|
|
62
|
-
const flatCounters = {} // kind -> next integer counter (figure, equation, table, foundation-declared kinds)
|
|
63
|
-
const sectionStack = [] // hierarchical counters per heading level
|
|
64
|
-
|
|
65
|
-
// Index foundation prefixes for id-prefix inference.
|
|
66
|
-
const prefixToKind = {}
|
|
67
|
-
for (const [kind, meta] of Object.entries(foundationKinds)) {
|
|
68
|
-
if (meta?.prefix) prefixToKind[meta.prefix] = kind
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function nextFlat(kind) {
|
|
72
|
-
flatCounters[kind] = (flatCounters[kind] || 0) + 1
|
|
73
|
-
return flatCounters[kind]
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Track the shallowest heading depth seen — chapter content commonly
|
|
77
|
-
// starts at h2 (chapter title is rendered separately as h1 by the
|
|
78
|
-
// foundation), so the first encountered level becomes the document's
|
|
79
|
-
// top counter level. Re-anchoring: if a later heading appears at a
|
|
80
|
-
// shallower level than `topLevel`, drop topLevel down to match.
|
|
81
|
-
let topLevel = null
|
|
82
|
-
|
|
83
|
-
function nextHierarchical(level) {
|
|
84
|
-
if (topLevel == null || level < topLevel) topLevel = level
|
|
85
|
-
const depth = level - topLevel + 1 // 1-based depth from the document's top level
|
|
86
|
-
|
|
87
|
-
// Trim deeper levels when surfacing back to a shallower heading.
|
|
88
|
-
while (sectionStack.length >= depth) sectionStack.pop()
|
|
89
|
-
while (sectionStack.length < depth - 1) sectionStack.push(1) // implicit parents start at 1
|
|
90
|
-
sectionStack.push((sectionStack[depth - 1] || 0) + 1)
|
|
91
|
-
sectionStack.length = depth
|
|
92
|
-
return sectionStack.slice().join('.')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Collect the plain text content of a node tree — used to capture a
|
|
96
|
-
// heading's displayable text for ListOfSections-style entries. Walks
|
|
97
|
-
// the standard ProseMirror text shape (text nodes carry `.text`;
|
|
98
|
-
// structural nodes recurse via `.content`).
|
|
99
|
-
function collectTextContent(node) {
|
|
100
|
-
if (!node || typeof node !== 'object') return ''
|
|
101
|
-
if (node.type === 'text' && typeof node.text === 'string') return node.text
|
|
102
|
-
if (Array.isArray(node.content)) {
|
|
103
|
-
return node.content.map(collectTextContent).join('')
|
|
104
|
-
}
|
|
105
|
-
return ''
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function inferKind(node) {
|
|
109
|
-
// 1. Node-type-based: built-ins.
|
|
110
|
-
const builtin = KIND_BY_TYPE[node.type]
|
|
111
|
-
if (builtin) return builtin
|
|
112
|
-
// 2. Explicit kind attribute on the node ({.kind} or kind=…).
|
|
113
|
-
if (node.attrs?.kind && (KIND_BY_TYPE[node.attrs.kind] || foundationKinds[node.attrs.kind])) {
|
|
114
|
-
return node.attrs.kind
|
|
115
|
-
}
|
|
116
|
-
// 3. Foundation-declared prefix on the id.
|
|
117
|
-
const id = node.attrs?.id
|
|
118
|
-
if (id && id.includes('-')) {
|
|
119
|
-
const prefix = id.slice(0, id.indexOf('-'))
|
|
120
|
-
if (prefixToKind[prefix]) return prefixToKind[prefix]
|
|
121
|
-
}
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function visit(node, sourcePath) {
|
|
126
|
-
if (!node || typeof node !== 'object') return
|
|
127
|
-
|
|
128
|
-
// Headings get a hierarchical counter regardless of whether they
|
|
129
|
-
// carry an id — the {#id} just labels them; the counter itself is
|
|
130
|
-
// assigned by tree position.
|
|
131
|
-
let counter = null
|
|
132
|
-
let counterText = null
|
|
133
|
-
if (node.type === 'heading') {
|
|
134
|
-
const level = Math.max(1, Math.min(6, node.attrs?.level || 1))
|
|
135
|
-
counterText = nextHierarchical(level)
|
|
136
|
-
counter = counterText
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const id = node.attrs?.id
|
|
140
|
-
if (id) {
|
|
141
|
-
const kind = inferKind(node)
|
|
142
|
-
if (!kind) {
|
|
143
|
-
onWarn(`[xref] {#${id}} on unrecognized element type "${node.type}" — ignored`)
|
|
144
|
-
} else if (entries[id]) {
|
|
145
|
-
onWarn(`[xref] duplicate id "${id}" — keeping first registration`)
|
|
146
|
-
} else {
|
|
147
|
-
if (counter == null) {
|
|
148
|
-
counter = nextFlat(kind)
|
|
149
|
-
counterText = String(counter)
|
|
150
|
-
}
|
|
151
|
-
const entry = { id, kind, counter, counterText, sourcePath: sourcePath || '' }
|
|
152
|
-
// Per-kind metadata. List sections (ListOfFigures, ListOfTables,
|
|
153
|
-
// TableOfContents) read these directly so they don't have to
|
|
154
|
-
// re-walk the parsed tree to find captions and headings.
|
|
155
|
-
const captionAttr = node.attrs?.caption
|
|
156
|
-
if (kind === 'figure' || kind === 'table') {
|
|
157
|
-
if (captionAttr) entry.caption = String(captionAttr)
|
|
158
|
-
}
|
|
159
|
-
if (node.type === 'heading') {
|
|
160
|
-
const text = collectTextContent(node)
|
|
161
|
-
if (text) entry.text = text
|
|
162
|
-
}
|
|
163
|
-
if (node.type === 'math_display') {
|
|
164
|
-
const latex = node.attrs?.latex
|
|
165
|
-
if (latex) entry.latex = String(latex)
|
|
166
|
-
}
|
|
167
|
-
entries[id] = entry
|
|
168
|
-
}
|
|
169
|
-
} else if (node.type === 'heading') {
|
|
170
|
-
// Heading without an id still advances the counter — but we don't
|
|
171
|
-
// register it (no way to reference it). The advance is needed so
|
|
172
|
-
// that the *next* labeled heading gets the right hierarchical
|
|
173
|
-
// counter.
|
|
174
|
-
// counter already computed above; just no entry.
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (Array.isArray(node.content)) {
|
|
178
|
-
for (const child of node.content) visit(child, sourcePath)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
for (const page of siteContent.pages || []) {
|
|
183
|
-
for (const section of page.sections || []) {
|
|
184
|
-
const content = section.content
|
|
185
|
-
if (content?.type === 'doc' && Array.isArray(content.content)) {
|
|
186
|
-
for (const child of content.content) visit(child, page.route)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return { entries }
|
|
192
|
-
}
|