@baiyibai-antora/extensions 1.12.0 → 1.14.0
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/CHANGELOG.adoc +25 -0
- package/README.adoc +17 -1
- package/dist/extensions/attribute-distiller/index.d.ts +6 -0
- package/dist/extensions/attribute-distiller/index.d.ts.map +1 -1
- package/dist/extensions/attribute-distiller/index.js +92 -16
- package/dist/extensions/attribute-distiller/index.js.map +1 -1
- package/dist/extensions/autoterm/index.d.ts +34 -25
- package/dist/extensions/autoterm/index.d.ts.map +1 -1
- package/dist/extensions/autoterm/index.js +52 -215
- package/dist/extensions/autoterm/index.js.map +1 -1
- package/dist/extensions/cite-it/asciidoc.d.ts +29 -0
- package/dist/extensions/cite-it/asciidoc.d.ts.map +1 -0
- package/dist/extensions/cite-it/asciidoc.js +32 -0
- package/dist/extensions/cite-it/asciidoc.js.map +1 -0
- package/dist/extensions/cite-it/assembly-numbering.d.ts +202 -0
- package/dist/extensions/cite-it/assembly-numbering.d.ts.map +1 -0
- package/dist/extensions/cite-it/assembly-numbering.js +385 -0
- package/dist/extensions/cite-it/assembly-numbering.js.map +1 -0
- package/dist/extensions/cite-it/csl.d.ts +45 -0
- package/dist/extensions/cite-it/csl.d.ts.map +1 -0
- package/dist/extensions/cite-it/csl.js +139 -0
- package/dist/extensions/cite-it/csl.js.map +1 -0
- package/dist/extensions/cite-it/index.d.ts +300 -0
- package/dist/extensions/cite-it/index.d.ts.map +1 -0
- package/dist/extensions/cite-it/index.js +1098 -0
- package/dist/extensions/cite-it/index.js.map +1 -0
- package/dist/extensions/cite-it/macro.d.ts +104 -0
- package/dist/extensions/cite-it/macro.d.ts.map +1 -0
- package/dist/extensions/cite-it/macro.js +245 -0
- package/dist/extensions/cite-it/macro.js.map +1 -0
- package/dist/extensions/cite-it/registry.d.ts +64 -0
- package/dist/extensions/cite-it/registry.d.ts.map +1 -0
- package/dist/extensions/cite-it/registry.js +157 -0
- package/dist/extensions/cite-it/registry.js.map +1 -0
- package/dist/extensions/trademark-it/index.d.ts +91 -22
- package/dist/extensions/trademark-it/index.d.ts.map +1 -1
- package/dist/extensions/trademark-it/index.js +239 -242
- package/dist/extensions/trademark-it/index.js.map +1 -1
- package/dist/lib/asciidoc-conditionals.d.ts +59 -0
- package/dist/lib/asciidoc-conditionals.d.ts.map +1 -0
- package/dist/lib/asciidoc-conditionals.js +0 -0
- package/dist/lib/asciidoc-conditionals.js.map +1 -0
- package/dist/lib/asciidoc-indexterms.d.ts +126 -0
- package/dist/lib/asciidoc-indexterms.d.ts.map +1 -0
- package/dist/lib/asciidoc-indexterms.js +261 -0
- package/dist/lib/asciidoc-indexterms.js.map +1 -0
- package/dist/lib/asciidoc-table.d.ts +32 -0
- package/dist/lib/asciidoc-table.d.ts.map +1 -0
- package/dist/lib/asciidoc-table.js +55 -0
- package/dist/lib/asciidoc-table.js.map +1 -0
- package/package.json +19 -4
- package/resources/cite-it/ieee-bracket.csl +519 -0
- package/resources/cite-it/locale-en-US.xml +864 -0
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @baiyibai-antora/extensions - cite-it
|
|
4
|
+
*
|
|
5
|
+
* Resolves `citeit:[key]` citations against BibTeX/BibLaTeX `.bib` files and
|
|
6
|
+
* generates a categorized bibliography partial. Numbering is a PDF/Antora
|
|
7
|
+
* Assembler-flattened-output concept only; ordinary, non-assembled HTML
|
|
8
|
+
* citations render a templated author/year fragment instead, carrying no
|
|
9
|
+
* number. See macro.ts and assembly-numbering.ts for the two rendering
|
|
10
|
+
* paths this splits into.
|
|
11
|
+
*
|
|
12
|
+
* A category is defined entirely by which `.bib` file(s) an entry lives in,
|
|
13
|
+
* never by a data field inside the entry. Each category is an ordered list
|
|
14
|
+
* of source files: a shared collected file first, then zero or more
|
|
15
|
+
* component-local override files; later files' entries override earlier
|
|
16
|
+
* files' entries by citation key, the same override-by-key semantics
|
|
17
|
+
* autoterm's `mergeTerminology` already establishes.
|
|
18
|
+
*
|
|
19
|
+
* Each category generates its OWN partial (`categoryPartialName`,
|
|
20
|
+
* `<output_partial>-<ref_section>.adoc`). A component author routes a
|
|
21
|
+
* category to whichever page they choose, simply by including that
|
|
22
|
+
* category's own partial there; different categories may genuinely live
|
|
23
|
+
* on different pages, and citations correctly cross-link to whichever
|
|
24
|
+
* page actually hosts the cited entry (assembly-numbering.ts's
|
|
25
|
+
* `findBibliographyScopes`/`rewriteCiteMarkers`), not just a single
|
|
26
|
+
* hardcoded bibliography page.
|
|
27
|
+
*
|
|
28
|
+
* A component-version participates automatically, with no opt-in step at
|
|
29
|
+
* all: once the playbook-level `categories` scaffold (below) gives a
|
|
30
|
+
* component-version at least one category, it is scanned and gets its
|
|
31
|
+
* generated partial(s), whether or not its own antora.yml mentions
|
|
32
|
+
* cite-it at all. `ext.cite_it` in a component-version's antora.yml is
|
|
33
|
+
* purely an OPTIONAL override, the same standing as `enabled`, `style`,
|
|
34
|
+
* `output_partial`, and `write_to_disk`: present only when a component
|
|
35
|
+
* needs to diverge from the playbook default, never required just to
|
|
36
|
+
* take part.
|
|
37
|
+
*
|
|
38
|
+
* EARLIER DESIGN, REPLACED: this extension originally required an
|
|
39
|
+
* explicit `ext.cite_it` block, even an empty one, mirroring
|
|
40
|
+
* trademark-it's own opt-in gate (heavy per-page scanning and
|
|
41
|
+
* generated-partial injection are real per-build cost, so a component
|
|
42
|
+
* shouldn't pay it just because a sibling component happens to use
|
|
43
|
+
* cite-it). In practice this produced a bad failure mode: a page citing
|
|
44
|
+
* a key with `citeit:[key]` in a component-version that forgot the
|
|
45
|
+
* `ext.cite_it` block either silently rendered an unrelated component's
|
|
46
|
+
* entry (if the key happened to collide) or a generic "unknown key"
|
|
47
|
+
* message that read like a typo rather than "this component-version
|
|
48
|
+
* never participated" - either way, with no diagnostic trail under the
|
|
49
|
+
* `quiet: true` every real registration uses. Removed rather than
|
|
50
|
+
* patched further: every component-version now participates by default,
|
|
51
|
+
* so there is no opt-in state left to forget. The performance tradeoff
|
|
52
|
+
* this accepts (every component-version with categories available now
|
|
53
|
+
* gets scanned, not just ones that explicitly asked to be) is a real
|
|
54
|
+
* cost, not a free lunch; it was decided against keeping the gate.
|
|
55
|
+
*
|
|
56
|
+
* `categories` in the PLAYBOOK-level `cite_it` config (below) is the
|
|
57
|
+
* real scaffold, mirroring how `trademark_file`/`terminology_file` work
|
|
58
|
+
* for trademark-it/autoterm: declared once, inherited by every
|
|
59
|
+
* component-version automatically. A component-version's own
|
|
60
|
+
* `ext.cite_it.categories`, if present at all, is entirely optional and
|
|
61
|
+
* purely additive to the scaffold, matched by `ref_section` (see
|
|
62
|
+
* mergeCategoriesConfig): naming a section already in the scaffold
|
|
63
|
+
* extends it (its `ref_files`/`ref_force` append to the scaffold's),
|
|
64
|
+
* and naming a new section adds a category local to that component
|
|
65
|
+
* alone. A component-version with
|
|
66
|
+
* genuinely no categories anywhere, no playbook scaffold and no local
|
|
67
|
+
* `ext.cite_it.categories`, is still skipped: there is nothing to scan
|
|
68
|
+
* for.
|
|
69
|
+
*
|
|
70
|
+
* Configuration in antora-playbook.yml:
|
|
71
|
+
*
|
|
72
|
+
* antora:
|
|
73
|
+
* extensions:
|
|
74
|
+
* - require: '@baiyibai-antora/extensions/cite-it'
|
|
75
|
+
* cite_it:
|
|
76
|
+
* enabled: true
|
|
77
|
+
* # Only 'ieee-bracket' is bundled and supported in this version.
|
|
78
|
+
* style: 'ieee-bracket'
|
|
79
|
+
* # Base filename for each category's own generated partial
|
|
80
|
+
* # (e.g. section "standards" -> generated-bibliography-
|
|
81
|
+
* # standards.adoc), one per category, injected into each
|
|
82
|
+
* # participating component-version.
|
|
83
|
+
* output_partial: 'generated-bibliography.adoc'
|
|
84
|
+
* # Write each generated partial to disk in the component's local
|
|
85
|
+
* # worktree too, alongside the source files, for IDE include
|
|
86
|
+
* # resolution. Skipped automatically for a component loaded from
|
|
87
|
+
* # a remote source (no local worktree to write into).
|
|
88
|
+
* write_to_disk: false
|
|
89
|
+
* # The global scaffold: every participating component-version
|
|
90
|
+
* # inherits these categories as-is unless it names the same
|
|
91
|
+
* # ref_section itself, in which case its own entry extends this
|
|
92
|
+
* # one rather than replacing it.
|
|
93
|
+
* categories:
|
|
94
|
+
* - ref_section: standards
|
|
95
|
+
* ref_files: ['cite-it-data:ROOT:partial$standards.bib']
|
|
96
|
+
* - ref_section: literature
|
|
97
|
+
* ref_files: ['cite-it-data:ROOT:partial$literature-shared.bib']
|
|
98
|
+
* ref_force: ['rfc2119']
|
|
99
|
+
*
|
|
100
|
+
* The generated partial itself carries no heading; a category's bibliography
|
|
101
|
+
* is a `[horizontal]` list only. The page including it supplies its own
|
|
102
|
+
* heading, ordinary authored AsciiDoc, immediately above the include line:
|
|
103
|
+
*
|
|
104
|
+
* == Standards References
|
|
105
|
+
*
|
|
106
|
+
* include::partial$generated-bibliography-standards.adoc[]
|
|
107
|
+
*
|
|
108
|
+
* Per-component configuration in antora.yml, under ext.cite_it, opts a
|
|
109
|
+
* component-version into cite-it and MAY extend the playbook-level
|
|
110
|
+
* scaffold. An empty block inherits the scaffold unchanged:
|
|
111
|
+
*
|
|
112
|
+
* ext:
|
|
113
|
+
* cite_it: {}
|
|
114
|
+
*
|
|
115
|
+
* A component needing a local-only addition to an existing section, or a
|
|
116
|
+
* wholly local category the scaffold does not define, adds only that:
|
|
117
|
+
*
|
|
118
|
+
* ext:
|
|
119
|
+
* cite_it:
|
|
120
|
+
* categories:
|
|
121
|
+
* # Extends the scaffold's own "literature" section (above) with one
|
|
122
|
+
* # more file, appended after the scaffold's own ref_files.
|
|
123
|
+
* - ref_section: literature
|
|
124
|
+
* ref_files: ['literature-local.bib']
|
|
125
|
+
* # A section the scaffold does not define at all: local to this
|
|
126
|
+
* # component only.
|
|
127
|
+
* - ref_section: internal-notes
|
|
128
|
+
* ref_files: ['internal-notes.bib']
|
|
129
|
+
*
|
|
130
|
+
* `.bib` files are resolved from the content catalog's `partial` family,
|
|
131
|
+
* the same family autoterm's terminology-table.adoc and trademark-it's
|
|
132
|
+
* trademark-table.adoc already use for shared, non-rendered reference data.
|
|
133
|
+
* Neither of those is independently published either: a `.bib` file's own
|
|
134
|
+
* confidentiality comes from which repository it lives in and who can read
|
|
135
|
+
* that repository, not from its Antora family. A category's `ref_files`
|
|
136
|
+
* list is the SAME shared-then-local-override pattern autoterm/trademark-it
|
|
137
|
+
* already establish:
|
|
138
|
+
*
|
|
139
|
+
* - A resource-ID reference (`component:module:partial$relative`, for
|
|
140
|
+
* example `cite-it-data:ROOT:partial$standards.bib` above) resolves
|
|
141
|
+
* against the full catalog, including a component the SAME playbook
|
|
142
|
+
* added as its own separate `content.sources` entry. This is the
|
|
143
|
+
* intended shape for a shared bibliography maintained in its own
|
|
144
|
+
* repository, the same pattern `baiyibai-autoterm-data` already
|
|
145
|
+
* establishes for autoterm's shared terminology table.
|
|
146
|
+
* - A bare filename (`literature-local.bib` above) resolves against the
|
|
147
|
+
* PARTICIPATING component-version's own `partial` family files only.
|
|
148
|
+
* This is the shape for a component-local override or addition, living
|
|
149
|
+
* alongside that component's own pages, never published on its own,
|
|
150
|
+
* reached only through this resolution (not through `include::`, since
|
|
151
|
+
* `.bib` files are read directly, not transcluded).
|
|
152
|
+
*
|
|
153
|
+
* `ref_force` names citation keys always listed in that category's
|
|
154
|
+
* bibliography even when no page cites them, for example a standard a
|
|
155
|
+
* component must disclose regardless of whether it is quoted inline. It is
|
|
156
|
+
* nested inside the category it targets, not a separate flat list with a
|
|
157
|
+
* back-reference: which heading a force-included citation renders under is
|
|
158
|
+
* simply which category's `ref_force` names it, independent of which
|
|
159
|
+
* category's `ref_files` actually defines the underlying entry. In the
|
|
160
|
+
* example above, `rfc2119` is force-included under the `literature` heading
|
|
161
|
+
* even if its own entry is defined inside the `standards` category's `.bib`
|
|
162
|
+
* file: a component author choosing where a force-included citation appears
|
|
163
|
+
* is a display decision, not a statement about which file happens to hold
|
|
164
|
+
* the data.
|
|
165
|
+
*
|
|
166
|
+
* A `.bib` entry's own `supersededby` field, a custom field naming the
|
|
167
|
+
* citation key of the newer entry replacing it, is not a real BibLaTeX
|
|
168
|
+
* field (see registry.ts) but is read directly from the raw parsed entry
|
|
169
|
+
* regardless. When a category's generated partial actually lists such a
|
|
170
|
+
* key (cited or `ref_force`d), cite-it warns once at generation time,
|
|
171
|
+
* naming the newer key, so an author sees a newer revision exists without
|
|
172
|
+
* reading the `.bib` file directly. Bibtex itself carries no native
|
|
173
|
+
* concept of an outdated reference; this is a cite-it-specific convention,
|
|
174
|
+
* not a standard `.bib` field any external tool is expected to recognize.
|
|
175
|
+
* Deliberately not raised again at each `citeit:[key]` use site in prose,
|
|
176
|
+
* one signal per generated partial is enough; managing `.bib` data itself,
|
|
177
|
+
* including deciding when an entry is superseded, stays outside cite-it's
|
|
178
|
+
* own scope (see the docs page's own "Scope" section).
|
|
179
|
+
*/
|
|
180
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
181
|
+
exports.register = register;
|
|
182
|
+
const log_time_1 = require("../../lib/log-time");
|
|
183
|
+
const asciidoc_conditionals_1 = require("../../lib/asciidoc-conditionals");
|
|
184
|
+
const asciidoc_indexterms_1 = require("../../lib/asciidoc-indexterms");
|
|
185
|
+
const index_1 = require("../trademark-it/index");
|
|
186
|
+
const index_2 = require("../autoterm/index");
|
|
187
|
+
const registry_1 = require("./registry");
|
|
188
|
+
const csl_1 = require("./csl");
|
|
189
|
+
const macro_1 = require("./macro");
|
|
190
|
+
const assembly_numbering_1 = require("./assembly-numbering");
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Logging helpers (mirrors trademark-it/autoterm style)
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
let quietMode = true;
|
|
195
|
+
function logEvent(message) {
|
|
196
|
+
console.log(`[${(0, log_time_1.nowLocal)()}] \x1b[94mEVENT\x1b[0m (cite-it): \x1b[94m${message}\x1b[0m`);
|
|
197
|
+
}
|
|
198
|
+
function makeLogger(hostLogger) {
|
|
199
|
+
return {
|
|
200
|
+
debug: (m) => { if (!quietMode) {
|
|
201
|
+
if (hostLogger?.debug)
|
|
202
|
+
hostLogger.debug(m);
|
|
203
|
+
else
|
|
204
|
+
console.debug(m);
|
|
205
|
+
} },
|
|
206
|
+
info: (m) => { if (!quietMode) {
|
|
207
|
+
if (hostLogger?.info)
|
|
208
|
+
hostLogger.info(m);
|
|
209
|
+
else
|
|
210
|
+
console.log(m);
|
|
211
|
+
} },
|
|
212
|
+
warn: (m) => { if (hostLogger?.warn)
|
|
213
|
+
hostLogger.warn(m);
|
|
214
|
+
else
|
|
215
|
+
console.warn(m); },
|
|
216
|
+
error: (m) => { if (hostLogger?.error)
|
|
217
|
+
hostLogger.error(m);
|
|
218
|
+
else
|
|
219
|
+
console.error(m); },
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Config helpers (mirrors trademark-it/autoterm style)
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
function toBool(v, def) {
|
|
226
|
+
if (v === true)
|
|
227
|
+
return true;
|
|
228
|
+
if (v === false)
|
|
229
|
+
return false;
|
|
230
|
+
if (typeof v === 'string') {
|
|
231
|
+
const s = v.trim().toLowerCase();
|
|
232
|
+
if (['true', '1', 'yes', 'on'].includes(s))
|
|
233
|
+
return true;
|
|
234
|
+
if (['false', '0', 'no', 'off'].includes(s))
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return def;
|
|
238
|
+
}
|
|
239
|
+
function toStringArray(v) {
|
|
240
|
+
if (Array.isArray(v))
|
|
241
|
+
return v.map(String);
|
|
242
|
+
if (typeof v === 'string' && v.trim())
|
|
243
|
+
return v.split(',').map((s) => s.trim()).filter(Boolean);
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Locates this extension's own entry in `playbook.antora.extensions` by
|
|
248
|
+
* whether it carries `cite_it`/`citeIt` as an own key, not by matching a
|
|
249
|
+
* substring of its `require` string. A require-string match breaks the
|
|
250
|
+
* moment a consumer vendors or re-exports this package under a different
|
|
251
|
+
* name or path: the require string then never contains "cite-it"/
|
|
252
|
+
* "cite_it" at all, `.find()` returns nothing, and every configured
|
|
253
|
+
* setting silently falls back to hardcoded defaults, no warning, no error
|
|
254
|
+
* (confirmed as a real report, not a hypothetical). Checking for the
|
|
255
|
+
* config key itself identifies the right entry regardless of how it was
|
|
256
|
+
* required; the require-string check remains only as a fallback for the
|
|
257
|
+
* case where no entry carries the key at all. An empty `ext.cite_it: {}`
|
|
258
|
+
* override is still caught correctly here, since the key is present even
|
|
259
|
+
* with no options set under it.
|
|
260
|
+
*/
|
|
261
|
+
function findOwnExtEntry(entries) {
|
|
262
|
+
return entries.find((ext) => 'cite_it' in ext || 'citeIt' in ext)
|
|
263
|
+
?? entries.find((ext) => {
|
|
264
|
+
const req = String(ext.require ?? '');
|
|
265
|
+
return req.includes('cite-it') || req.includes('cite_it');
|
|
266
|
+
})
|
|
267
|
+
?? {};
|
|
268
|
+
}
|
|
269
|
+
function pick(obj, keys, def) {
|
|
270
|
+
for (const k of keys) {
|
|
271
|
+
if (obj && obj[k] !== undefined)
|
|
272
|
+
return obj[k];
|
|
273
|
+
}
|
|
274
|
+
return def;
|
|
275
|
+
}
|
|
276
|
+
function parseCategoriesConfig(v) {
|
|
277
|
+
if (!Array.isArray(v))
|
|
278
|
+
return [];
|
|
279
|
+
const result = [];
|
|
280
|
+
for (const raw of v) {
|
|
281
|
+
if (!raw || typeof raw !== 'object')
|
|
282
|
+
continue;
|
|
283
|
+
const r = raw;
|
|
284
|
+
const section = typeof r['ref_section'] === 'string' ? r['ref_section']
|
|
285
|
+
: typeof r['refSection'] === 'string' ? r['refSection'] : '';
|
|
286
|
+
if (!section)
|
|
287
|
+
continue;
|
|
288
|
+
result.push({
|
|
289
|
+
ref_section: section,
|
|
290
|
+
ref_files: toStringArray(r['ref_files'] ?? r['refFiles']),
|
|
291
|
+
ref_force: toStringArray(r['ref_force'] ?? r['refForce']),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
// The playbook-level scaffold and a component's own categories[] merge by
|
|
297
|
+
// ref_section, the same shared-then-local-override precedence autoterm's
|
|
298
|
+
// mergeTerminology and a single category's own ref_files list both already
|
|
299
|
+
// establish: a section named at both levels extends (ref_files/ref_force
|
|
300
|
+
// concatenate, playbook first) rather than replaces, and a section named
|
|
301
|
+
// only at the component level is added as a wholly local category.
|
|
302
|
+
function mergeCategoriesConfig(scaffold, local) {
|
|
303
|
+
const bySection = new Map();
|
|
304
|
+
for (const cat of scaffold)
|
|
305
|
+
bySection.set(cat.ref_section, { ...cat });
|
|
306
|
+
for (const cat of local) {
|
|
307
|
+
const existing = bySection.get(cat.ref_section);
|
|
308
|
+
bySection.set(cat.ref_section, existing ? {
|
|
309
|
+
ref_section: cat.ref_section,
|
|
310
|
+
ref_files: [...existing.ref_files, ...cat.ref_files],
|
|
311
|
+
ref_force: [...existing.ref_force, ...cat.ref_force],
|
|
312
|
+
} : cat);
|
|
313
|
+
}
|
|
314
|
+
return [...bySection.values()].map((cat) => ({
|
|
315
|
+
ref_section: cat.ref_section,
|
|
316
|
+
ref_files: cat.ref_files,
|
|
317
|
+
ref_force: cat.ref_force,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* One generated partial per category, not one combined partial for the
|
|
322
|
+
* whole component-version: the key design feature is that an author decides
|
|
323
|
+
* where each category appears simply by choosing which page includes
|
|
324
|
+
* which category's own partial, and different categories may genuinely
|
|
325
|
+
* live on different pages, with citations correctly cross-linking to
|
|
326
|
+
* whichever page actually hosts the cited entry (see assembly-
|
|
327
|
+
* numbering.ts's `findBibliographyScopes`/`rewriteCiteMarkers`).
|
|
328
|
+
* `generated-bibliography.adoc` + category `standards` becomes
|
|
329
|
+
* `generated-bibliography-standards.adoc`.
|
|
330
|
+
*/
|
|
331
|
+
function categoryPartialName(base, categoryKey) {
|
|
332
|
+
const dot = base.lastIndexOf('.');
|
|
333
|
+
return dot < 0 ? `${base}-${categoryKey}` : `${base.slice(0, dot)}-${categoryKey}${base.slice(dot)}`;
|
|
334
|
+
}
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// `.bib` file resolution
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
const RESOURCE_ID_RE = /[:$]/;
|
|
339
|
+
/** `.bib` files live in the `partial` family: never independently
|
|
340
|
+
* published, and the same family autoterm/trademark-it already use for
|
|
341
|
+
* shared reference data collected from another repository. A resource-ID
|
|
342
|
+
* reference resolves against the full catalog (including a separate
|
|
343
|
+
* `content.sources` entry the playbook adds for a shared data component);
|
|
344
|
+
* a bare filename resolves only against the participating component-
|
|
345
|
+
* version's own `partial` family files (a local override or addition).
|
|
346
|
+
*/
|
|
347
|
+
function resolveBibFiles(refs, contentCatalog, component, version, module, logger) {
|
|
348
|
+
const result = [];
|
|
349
|
+
for (const ref of refs) {
|
|
350
|
+
let file;
|
|
351
|
+
if (RESOURCE_ID_RE.test(ref) && contentCatalog.resolveResource) {
|
|
352
|
+
try {
|
|
353
|
+
file = contentCatalog.resolveResource(ref, { component, version, module }, 'partial');
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
file = undefined;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (!file) {
|
|
360
|
+
file = contentCatalog.getFiles().find((f) => f.src.component === component &&
|
|
361
|
+
f.src.version === version &&
|
|
362
|
+
f.src.family === 'partial' &&
|
|
363
|
+
(f.src.relative === ref || f.src.basename === ref));
|
|
364
|
+
}
|
|
365
|
+
if (!file) {
|
|
366
|
+
logger.warn(`cite-it: .bib file "${ref}" not found (expected family 'partial') for ${component}@${version}`);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
result.push(file);
|
|
370
|
+
}
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
// Orphaned citeit:[key] usage: a component-version that is not scanned
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
/**
|
|
377
|
+
* Checks a SKIPPED (non-participating) component-version's own pages for
|
|
378
|
+
* `citeit:[key]` usage that will never resolve, and records any found into
|
|
379
|
+
* `orphanedCiteKeys` (macro.ts) for the macro to detect at render time and
|
|
380
|
+
* report clearly, instead of either silently borrowing an unrelated
|
|
381
|
+
* component's data through the cross-component fallback (if the key
|
|
382
|
+
* happens to collide) or rendering a generic "unknown key" message that
|
|
383
|
+
* reads like a typo rather than "this component-version is not scanned".
|
|
384
|
+
*
|
|
385
|
+
* There is no opt-in gate to forget any more (see the module docblock):
|
|
386
|
+
* a component-version only reaches here for two real reasons, explicitly
|
|
387
|
+
* disabled (`ext.cite_it.enabled: false`) or genuinely no categories
|
|
388
|
+
* anywhere, no playbook scaffold and no local `ext.cite_it.categories`.
|
|
389
|
+
* Both are still worth flagging if the component-version's own pages cite
|
|
390
|
+
* something anyway, since the citation still cannot resolve.
|
|
391
|
+
*
|
|
392
|
+
* Deliberately does NOT resolve `include::` content, unlike the real scan
|
|
393
|
+
* pass for a participating component (which calls `collectIncludeText`):
|
|
394
|
+
* a wrapper page whose entire content is `include::other-component:...[]`
|
|
395
|
+
* correctly has nothing of its OWN to flag here, since the included
|
|
396
|
+
* content belongs to, and was authored for, a genuinely participating
|
|
397
|
+
* component. This is exactly what keeps the demo site's own
|
|
398
|
+
* include::-based wrapper pages (autoterm.adoc, trademark-it.adoc,
|
|
399
|
+
* cite-it.adoc's own siblings) from being misreported.
|
|
400
|
+
*
|
|
401
|
+
* A cheap substring pre-check gates the real, Asciidoctor-based scan
|
|
402
|
+
* (`scanCiteKeys`): a component-version that never mentions `citeit:[` at
|
|
403
|
+
* all pays nothing beyond a plain string search per page.
|
|
404
|
+
*/
|
|
405
|
+
function checkOrphanedCiteUsage(compKey, reason, contentCatalog, component, version, logger) {
|
|
406
|
+
const pages = contentCatalog.getFiles().filter((f) => f.src.component === component && f.src.version === version && f.src.family === 'page');
|
|
407
|
+
if (pages.length === 0)
|
|
408
|
+
return;
|
|
409
|
+
const componentAttrs = (contentCatalog.getComponents?.() ?? [])
|
|
410
|
+
.find((c) => c.name === component)?.versions
|
|
411
|
+
.find((v) => (v.version ?? v.name) === version)?.asciidoc?.attributes ?? {};
|
|
412
|
+
const found = new Set();
|
|
413
|
+
for (const f of pages) {
|
|
414
|
+
const raw = f.contents.toString('utf8');
|
|
415
|
+
if (!raw.includes('citeit:['))
|
|
416
|
+
continue;
|
|
417
|
+
const headerAttrs = (0, asciidoc_conditionals_1.pageHeaderAttributes)(raw);
|
|
418
|
+
const isDefined = (attr) => {
|
|
419
|
+
if (headerAttrs.has(attr))
|
|
420
|
+
return headerAttrs.get(attr) === true;
|
|
421
|
+
const v = componentAttrs[attr];
|
|
422
|
+
return v !== undefined && v !== null && v !== false;
|
|
423
|
+
};
|
|
424
|
+
const text = (0, index_1.stripNonContent)(raw, isDefined, []);
|
|
425
|
+
for (const key of (0, macro_1.scanCiteKeys)((0, asciidoc_indexterms_1.sanitizeForScan)(text)))
|
|
426
|
+
found.add(key);
|
|
427
|
+
}
|
|
428
|
+
if (found.size === 0)
|
|
429
|
+
return;
|
|
430
|
+
macro_1.orphanedCiteKeys.set(compKey, found);
|
|
431
|
+
logger.warn(`cite-it: ${compKey} - ${reason}, but citeit:[${[...found].join('], citeit:[')}] is used directly on its own page(s); ` +
|
|
432
|
+
'these citations will not resolve for this component-version, regardless of the playbook-level configuration');
|
|
433
|
+
}
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Antora extension entry point
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
function register(context) {
|
|
438
|
+
const ctx = context ?? this;
|
|
439
|
+
const logger = makeLogger(ctx.getLogger ? ctx.getLogger('cite-it') : undefined);
|
|
440
|
+
let cfg = {
|
|
441
|
+
enabled: true,
|
|
442
|
+
quiet: true,
|
|
443
|
+
style: 'ieee-bracket',
|
|
444
|
+
output_partial: 'generated-bibliography.adoc',
|
|
445
|
+
suppress_events: false,
|
|
446
|
+
write_to_disk: false,
|
|
447
|
+
categories: [],
|
|
448
|
+
};
|
|
449
|
+
const extByCompVer = new Map();
|
|
450
|
+
// Per-component-version scan context, captured during contentClassified
|
|
451
|
+
// and reused by the navigationBuilt handler below to compute true
|
|
452
|
+
// first-appearance citation numbers for PDF/assembled output, avoiding
|
|
453
|
+
// recomputing config resolution twice for the same component-version.
|
|
454
|
+
const assemblyScanContexts = new Map();
|
|
455
|
+
// component@version -> citation key -> owning category key. Needed at
|
|
456
|
+
// navigationBuilt time to turn findBibliographyScopes' per-CATEGORY
|
|
457
|
+
// page map into the per-KEY page map rewriteCiteMarkers actually needs.
|
|
458
|
+
const keyCategories = new Map();
|
|
459
|
+
// Populated later, at navigationBuilt time (see below), but declared here
|
|
460
|
+
// so the loadAsciiDoc wrapper registered immediately below can close over
|
|
461
|
+
// them: real content is not known yet when the wrapper is REGISTERED, only
|
|
462
|
+
// by the time it is actually CALLED, which is guaranteed to be later.
|
|
463
|
+
const citationNumbers = new Map();
|
|
464
|
+
const keyScopesByComp = new Map();
|
|
465
|
+
// Populated inside the if-block below, but declared here so
|
|
466
|
+
// ensureAssemblyDataComputed's own navigation self-heal (defined later
|
|
467
|
+
// in this same function) can reach both: the real, unwrapped loader,
|
|
468
|
+
// and this wrapper's own reference, needed to temporarily swap the
|
|
469
|
+
// wrapper back out and reinstall it. See that self-heal's own docblock
|
|
470
|
+
// for why.
|
|
471
|
+
let realLoadAsciiDoc;
|
|
472
|
+
let loadAsciiDocWrapper;
|
|
473
|
+
// The loadAsciiDoc wrapper is registered here, synchronously, at
|
|
474
|
+
// register() time itself, deliberately BEFORE any lifecycle event fires
|
|
475
|
+
// at all. `@antora/assembler`'s own assembleContent reads
|
|
476
|
+
// `context.getFunctions().loadAsciiDoc` exactly ONCE, synchronously, at
|
|
477
|
+
// the top of its own navigationBuilt listener, not lazily at the point it
|
|
478
|
+
// actually calls loadAsciiDoc, confirmed directly by reading
|
|
479
|
+
// assemble-content.js. If cite-it's own replaceFunctions call happened
|
|
480
|
+
// inside ITS OWN navigationBuilt listener instead, as an earlier version
|
|
481
|
+
// of this code did, whichever extension's navigationBuilt listener runs
|
|
482
|
+
// first would win: a playbook that lists cite-it after
|
|
483
|
+
// `@antora/pdf-extension` would silently get unrewritten citeit:[key]
|
|
484
|
+
// literal text in PDF output, no error, no warning, since
|
|
485
|
+
// assembleContent's own loadAsciiDoc reference would already be frozen to
|
|
486
|
+
// the original, unwrapped function before cite-it's replaceFunctions call
|
|
487
|
+
// ever ran. Registering here, at register() time, fires before every
|
|
488
|
+
// playbookBuilt/contentClassified/navigationBuilt listener regardless of
|
|
489
|
+
// where cite-it appears in the extensions list, so this no longer depends
|
|
490
|
+
// on registration order at all.
|
|
491
|
+
if (ctx.getFunctions && ctx.replaceFunctions && ctx.require) {
|
|
492
|
+
// Captured ONCE, right here, BEFORE replaceFunctions below installs the
|
|
493
|
+
// wrapper: reading getFunctions().loadAsciiDoc lazily INSIDE the
|
|
494
|
+
// wrapper itself, instead, would read back the wrapper's own already-
|
|
495
|
+
// installed replacement rather than the original, an infinite
|
|
496
|
+
// recursion confirmed directly (a real "Maximum call stack size
|
|
497
|
+
// exceeded" crash against a live build, not a hypothetical). The
|
|
498
|
+
// fallback to `@antora/asciidoc-loader`'s own export, for the case
|
|
499
|
+
// getFunctions() does not carry a real loadAsciiDoc yet this early in
|
|
500
|
+
// the lifecycle, mirrors exactly what `@antora/assembler`'s own
|
|
501
|
+
// assemble-content.js does for the same reason.
|
|
502
|
+
const realLoadAsciiDocFn = (ctx.getFunctions().loadAsciiDoc ??
|
|
503
|
+
ctx.require('@antora/asciidoc-loader').loadAsciiDoc);
|
|
504
|
+
realLoadAsciiDoc = realLoadAsciiDocFn;
|
|
505
|
+
// Named, not inline, so ensureAssemblyDataComputed's own navigation
|
|
506
|
+
// self-heal can reinstall this SAME reference after temporarily
|
|
507
|
+
// swapping it out; see that function's own docblock for why.
|
|
508
|
+
loadAsciiDocWrapper = (...args) => {
|
|
509
|
+
const [origFile, catalog, asciidocConfig] = args;
|
|
510
|
+
let file = origFile;
|
|
511
|
+
const isAssembled = !!(asciidocConfig?.attributes && 'loader-assembler' in asciidocConfig.attributes);
|
|
512
|
+
if (isAssembled && file?.contents && file.src?.component && file.src?.version) {
|
|
513
|
+
const compKey = `${file.src.component}@${file.src.version}`;
|
|
514
|
+
// @antora/assembler resolves the active profile as
|
|
515
|
+
// assemblyConfig.profile, defaulting to the export's own target
|
|
516
|
+
// backend (e.g. 'pdf') when no profile is configured explicitly
|
|
517
|
+
// — never bare `undefined` in an ordinary PDF build — then bakes
|
|
518
|
+
// it into every assembled document's own real attributes as
|
|
519
|
+
// 'assembler-profile' (assemble-content.js: intrinsicAttributes),
|
|
520
|
+
// right here in this same asciidocConfig. Passed through so
|
|
521
|
+
// ensureAssemblyDataComputed can look up navFiles under the
|
|
522
|
+
// SAME profile key assembler itself resolved, instead of always
|
|
523
|
+
// checking the unnamed default profile slot.
|
|
524
|
+
const profileName = asciidocConfig?.attributes?.['assembler-profile'];
|
|
525
|
+
ensureAssemblyDataComputed(compKey, profileName);
|
|
526
|
+
const numbers = citationNumbers.get(compKey);
|
|
527
|
+
if (numbers && numbers.size > 0) {
|
|
528
|
+
const keyScopes = keyScopesByComp.get(compKey) ?? null;
|
|
529
|
+
const before = file.contents.toString('utf8');
|
|
530
|
+
const after = (0, assembly_numbering_1.rewriteCiteMarkers)(before, numbers, true, keyScopes);
|
|
531
|
+
if (after !== before) {
|
|
532
|
+
file = Object.assign(Object.create(Object.getPrototypeOf(file)), file, { contents: Buffer.from(after, 'utf8') });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// realLoadAsciiDoc is always assigned by this point: this closure is
|
|
537
|
+
// only ever reachable after the synchronous assignment a few lines
|
|
538
|
+
// above it in this same if-block has already run.
|
|
539
|
+
return realLoadAsciiDoc(file, catalog, asciidocConfig ?? {});
|
|
540
|
+
};
|
|
541
|
+
ctx.replaceFunctions({ loadAsciiDoc: loadAsciiDocWrapper });
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
logger.warn('cite-it: replaceFunctions/getFunctions/require unavailable on this extension context; PDF/assembled numbering will not be applied (ordinary HTML citations are unaffected)');
|
|
545
|
+
}
|
|
546
|
+
ctx.once('contentAggregated', ({ contentAggregate }) => {
|
|
547
|
+
for (const item of (contentAggregate ?? [])) {
|
|
548
|
+
const ciExt = item.ext?.['cite_it'];
|
|
549
|
+
if (ciExt && typeof ciExt === 'object') {
|
|
550
|
+
extByCompVer.set(`${item.name}@${item.version}`, ciExt);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
ctx.once('playbookBuilt', async ({ playbook }) => {
|
|
555
|
+
const extEntry = findOwnExtEntry(playbook?.antora?.extensions ?? []);
|
|
556
|
+
const raw = (extEntry.cite_it ?? extEntry.citeIt ?? {});
|
|
557
|
+
cfg = {
|
|
558
|
+
enabled: toBool(pick(raw, ['enabled'], cfg.enabled), cfg.enabled),
|
|
559
|
+
quiet: toBool(pick(raw, ['quiet'], cfg.quiet), cfg.quiet),
|
|
560
|
+
style: String(pick(raw, ['style'], cfg.style)),
|
|
561
|
+
output_partial: String(pick(raw, ['output_partial', 'outputPartial'], cfg.output_partial)),
|
|
562
|
+
suppress_events: toBool(pick(raw, ['suppress_events', 'suppressEvents'], cfg.suppress_events), cfg.suppress_events),
|
|
563
|
+
write_to_disk: toBool(pick(raw, ['write_to_disk', 'writeToDisk'], cfg.write_to_disk), cfg.write_to_disk),
|
|
564
|
+
categories: Array.isArray(raw['categories']) ? parseCategoriesConfig(raw['categories']) : cfg.categories,
|
|
565
|
+
};
|
|
566
|
+
quietMode = cfg.quiet;
|
|
567
|
+
logger.debug(`cite-it config: ${JSON.stringify(cfg)}`);
|
|
568
|
+
});
|
|
569
|
+
ctx.once('contentClassified', async ({ contentCatalog }) => {
|
|
570
|
+
if (!cfg.enabled) {
|
|
571
|
+
logger.debug('cite-it disabled (skipped)');
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (!contentCatalog) {
|
|
575
|
+
logger.error('contentCatalog is missing');
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
logEvent('Executing cite-it');
|
|
579
|
+
const componentVersions = (0, index_1.getComponentVersions)(contentCatalog);
|
|
580
|
+
for (const [component, versions] of componentVersions) {
|
|
581
|
+
for (const version of versions) {
|
|
582
|
+
const compKey = `${component}@${version}`;
|
|
583
|
+
// No opt-in gate: a component-version participates automatically
|
|
584
|
+
// once the playbook-level scaffold (or its own ext.cite_it, if
|
|
585
|
+
// present) gives it at least one category. ext.cite_it in
|
|
586
|
+
// antora.yml is now purely an OPTIONAL override, exactly like
|
|
587
|
+
// enabled/style/output_partial/write_to_disk below, never a
|
|
588
|
+
// requirement to be scanned at all. See the module docblock for
|
|
589
|
+
// the earlier opt-in-gate design this replaced and why.
|
|
590
|
+
const compExt = extByCompVer.get(compKey) ?? {};
|
|
591
|
+
if (!toBool(compExt['enabled'], cfg.enabled)) {
|
|
592
|
+
logger.debug(`cite-it: ${compKey} - skipped (enabled: false)`);
|
|
593
|
+
checkOrphanedCiteUsage(compKey, 'skipped (enabled: false)', contentCatalog, component, version, logger);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
// The playbook-level categories scaffold needs no counterpart here
|
|
597
|
+
// at all: a component's own categories[] is entirely optional,
|
|
598
|
+
// additive to the scaffold by ref_section. See
|
|
599
|
+
// mergeCategoriesConfig for the actual merge.
|
|
600
|
+
const categories = mergeCategoriesConfig(cfg.categories, parseCategoriesConfig(compExt['categories']));
|
|
601
|
+
if (categories.length === 0) {
|
|
602
|
+
// Without the opt-in gate, this branch is reached by every
|
|
603
|
+
// component-version site-wide that has nothing to do with
|
|
604
|
+
// cite-it at all, not just ones that tried to configure
|
|
605
|
+
// something and got it wrong; warning for all of them would
|
|
606
|
+
// flood the build log with noise about components that never
|
|
607
|
+
// showed any intent to use cite-it. Warn only when this
|
|
608
|
+
// component-version's own antora.yml actually carries an
|
|
609
|
+
// ext.cite_it block (real, if misconfigured, intent), debug
|
|
610
|
+
// otherwise, matching every other "nothing to do here" skip
|
|
611
|
+
// above.
|
|
612
|
+
const hasOwnExt = extByCompVer.has(compKey);
|
|
613
|
+
const message = `cite-it: ${compKey} - skipped (no categories: none in the playbook-level scaffold or this component's own ext.cite_it.categories)`;
|
|
614
|
+
if (hasOwnExt)
|
|
615
|
+
logger.warn(message);
|
|
616
|
+
else
|
|
617
|
+
logger.debug(message);
|
|
618
|
+
checkOrphanedCiteUsage(compKey, 'skipped (no categories)', contentCatalog, component, version, logger);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
let files = contentCatalog.getFiles().filter((f) => f.src.component === component && f.src.version === version);
|
|
622
|
+
const compStyle = typeof compExt['style'] === 'string' ? compExt['style'] : cfg.style;
|
|
623
|
+
const compOutPartial = typeof compExt['output_partial'] === 'string' ? compExt['output_partial'] : cfg.output_partial;
|
|
624
|
+
const compWriteToDisk = toBool(compExt['write_to_disk'] ?? compExt['writeToDisk'], cfg.write_to_disk);
|
|
625
|
+
const module = files.find((f) => f.src.family === 'page')?.src.module ?? 'ROOT';
|
|
626
|
+
// A page's own include::partial$<category-partial>[] is resolved
|
|
627
|
+
// as part of the scan below (collectIncludeText walks every
|
|
628
|
+
// include:: directive it finds, including this one, even though
|
|
629
|
+
// its own resolved text is never itself scanned for citations).
|
|
630
|
+
// Each category partial must already exist in the catalog before
|
|
631
|
+
// that resolution is attempted: a real ContentCatalog's
|
|
632
|
+
// resolveResource can memoize a failed lookup, and a resolution
|
|
633
|
+
// attempted before the partial exists would otherwise permanently
|
|
634
|
+
// poison the SAME lookup Antora Assembler needs to perform for
|
|
635
|
+
// real, later, during assembly. Injecting an empty placeholder for
|
|
636
|
+
// EVERY category now, before the scan below and before any real
|
|
637
|
+
// .bib work, means every resolveResource call for any of these
|
|
638
|
+
// resource IDs, whether at scan time or assembly time, finds a
|
|
639
|
+
// real file, first attempt onward - independent of whether this
|
|
640
|
+
// component-version turns out to have any real citation usage at
|
|
641
|
+
// all, which the scan below has not yet determined at this point.
|
|
642
|
+
const categoryPartials = categories.map((cat) => ({
|
|
643
|
+
section: cat.ref_section,
|
|
644
|
+
relative: categoryPartialName(compOutPartial, cat.ref_section),
|
|
645
|
+
}));
|
|
646
|
+
for (const cp of categoryPartials) {
|
|
647
|
+
(0, index_2.injectPartial)(contentCatalog, files, component, version, module, cp.relative, '', false, logger);
|
|
648
|
+
}
|
|
649
|
+
// Re-fetch: the placeholders just added above are now the real
|
|
650
|
+
// targets of any include:: resolution attempted during the scan
|
|
651
|
+
// below, and the later, real injectPartial calls, with the actual
|
|
652
|
+
// content, need their own `existing` lookups to find them too.
|
|
653
|
+
// Both need a file list that reflects the placeholders, not the
|
|
654
|
+
// stale snapshot from before they existed.
|
|
655
|
+
files = contentCatalog.getFiles().filter((f) => f.src.component === component && f.src.version === version);
|
|
656
|
+
// ---------------------------------------------------------------
|
|
657
|
+
// 1. Scan pages (+ resolved includes) for citeit:[key] usage,
|
|
658
|
+
// through a real Asciidoctor conversion (macro.ts's
|
|
659
|
+
// scanCiteKeys), not a hand-rolled regex. Runs BEFORE ever
|
|
660
|
+
// resolving or reading a single .bib file: the playbook-level
|
|
661
|
+
// scaffold applies to every component-version site-wide with
|
|
662
|
+
// no opt-in step (see the module docblock), so an asset-only
|
|
663
|
+
// component-version with no relation to citations at all -
|
|
664
|
+
// vale rule sets, PDF theme/font collections, and the like -
|
|
665
|
+
// still inherits it and used to hit real .bib resolution
|
|
666
|
+
// unconditionally, warning "not found" for files it never had
|
|
667
|
+
// any reason to carry. Scanning first and skipping entirely
|
|
668
|
+
// when nothing was found (see below) means a component-version
|
|
669
|
+
// with zero real citeit:[] usage anywhere never even looks for
|
|
670
|
+
// its category files in the first place, rather than looking
|
|
671
|
+
// and then discovering there was nothing to find. Placeholder
|
|
672
|
+
// injection above still runs unconditionally either way: it
|
|
673
|
+
// does no .bib work and carries no warning of its own, and the
|
|
674
|
+
// resolveResource-poisoning hazard it guards against applies
|
|
675
|
+
// regardless of whether this component-version turns out to
|
|
676
|
+
// have real citation usage.
|
|
677
|
+
// ---------------------------------------------------------------
|
|
678
|
+
const componentAttrs = (contentCatalog.getComponents?.() ?? [])
|
|
679
|
+
.find((c) => c.name === component)?.versions
|
|
680
|
+
.find((v) => (v.version ?? v.name) === version)?.asciidoc?.attributes ?? {};
|
|
681
|
+
const scanFiles = files.filter((f) => f.src.family === 'page');
|
|
682
|
+
const includeVisited = new Set();
|
|
683
|
+
for (const f of scanFiles) {
|
|
684
|
+
const key = `${f.src.component}@${f.src.version}:${f.src.module}:${f.src.family}$${f.src.relative}`;
|
|
685
|
+
includeVisited.add(key);
|
|
686
|
+
}
|
|
687
|
+
const citedKeys = new Set();
|
|
688
|
+
for (const f of scanFiles) {
|
|
689
|
+
const raw = f.contents.toString('utf8');
|
|
690
|
+
const headerAttrs = (0, asciidoc_conditionals_1.pageHeaderAttributes)(raw);
|
|
691
|
+
const isDefined = (attr) => {
|
|
692
|
+
if (headerAttrs.has(attr))
|
|
693
|
+
return headerAttrs.get(attr) === true;
|
|
694
|
+
const v = componentAttrs[attr];
|
|
695
|
+
return v !== undefined && v !== null && v !== false;
|
|
696
|
+
};
|
|
697
|
+
let text = (0, index_1.stripNonContent)(raw, isDefined, []);
|
|
698
|
+
const extra = (0, index_1.collectIncludeText)(text, contentCatalog, component, version, f.src.module ?? 'ROOT', includeVisited, [], undefined, [], f.src.path ?? '');
|
|
699
|
+
if (extra)
|
|
700
|
+
text = `${text}\n${(0, index_1.stripNonContent)(extra, isDefined, [])}`;
|
|
701
|
+
// Same concatenation-then-convert hazard sanitizeForScan already
|
|
702
|
+
// fixed for assembly-numbering.ts's own scan (63a6538): a page
|
|
703
|
+
// and its resolved include target each carrying a `=`-level-0
|
|
704
|
+
// title makes the combined text an invalid multi-document
|
|
705
|
+
// concatenation before it ever reaches scanCiteKeys' own real
|
|
706
|
+
// processor.convert() call. This call site concatenates the same
|
|
707
|
+
// way but was missed in that fix; confirmed live, "Executing
|
|
708
|
+
// cite-it" is where the level-0/incomplete-row errors actually
|
|
709
|
+
// fired, not assembly-numbering.ts's own computeCitationNumbers.
|
|
710
|
+
for (const key of (0, macro_1.scanCiteKeys)((0, asciidoc_indexterms_1.sanitizeForScan)(text)))
|
|
711
|
+
citedKeys.add(key);
|
|
712
|
+
}
|
|
713
|
+
// A category's own ref_force list lists a key in the generated
|
|
714
|
+
// bibliography even when no page cites it at all (see the
|
|
715
|
+
// CiteCategoryConfig field docblock), so zero cited keys does not
|
|
716
|
+
// always mean zero real work; only skip when NEITHER a real
|
|
717
|
+
// citation nor a forced entry gives this component-version
|
|
718
|
+
// anything to generate. citedKeys.size === 0 already means the
|
|
719
|
+
// scan above found nothing to report as orphaned either, so no
|
|
720
|
+
// separate checkOrphanedCiteUsage call is needed on this path.
|
|
721
|
+
const anyForced = categories.some((cat) => cat.ref_force.length > 0);
|
|
722
|
+
if (citedKeys.size === 0 && !anyForced) {
|
|
723
|
+
const hasOwnExt = extByCompVer.has(compKey);
|
|
724
|
+
const message = `cite-it: ${compKey} - skipped (no citeit:[key] usage anywhere and no ref_force entries in any category)`;
|
|
725
|
+
if (hasOwnExt)
|
|
726
|
+
logger.warn(message);
|
|
727
|
+
else
|
|
728
|
+
logger.debug(message);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
// ---------------------------------------------------------------
|
|
732
|
+
// 2. Resolve, parse, and merge each category's .bib files.
|
|
733
|
+
// Files listed earlier in a category's own files[] are the
|
|
734
|
+
// shared base; later files override by citation key, mirroring
|
|
735
|
+
// autoterm's mergeTerminology semantics exactly.
|
|
736
|
+
// ---------------------------------------------------------------
|
|
737
|
+
const combinedEntries = new Map();
|
|
738
|
+
const keyCategory = new Map();
|
|
739
|
+
const categoryKeys = new Map();
|
|
740
|
+
for (const cat of categories) {
|
|
741
|
+
const bibFiles = resolveBibFiles(cat.ref_files, contentCatalog, component, version, module, logger);
|
|
742
|
+
let catEntries = new Map();
|
|
743
|
+
for (const bf of bibFiles) {
|
|
744
|
+
const { entries, warnings } = (0, registry_1.parseCiteFile)(bf.contents.toString('utf8'), `${compKey}:${bf.src.path ?? bf.src.relative ?? ''}`);
|
|
745
|
+
for (const w of warnings)
|
|
746
|
+
logger.warn(`cite-it: ${w}`);
|
|
747
|
+
const { merged } = (0, registry_1.mergeCiteRegistry)(catEntries, entries);
|
|
748
|
+
catEntries = merged;
|
|
749
|
+
}
|
|
750
|
+
const keys = new Set();
|
|
751
|
+
for (const [key, entry] of catEntries) {
|
|
752
|
+
if (keyCategory.has(key) && keyCategory.get(key) !== cat.ref_section) {
|
|
753
|
+
logger.warn(`cite-it: ${compKey} - citation key "${key}" appears in both category "${keyCategory.get(key)}" and "${cat.ref_section}"; the later category's membership wins`);
|
|
754
|
+
}
|
|
755
|
+
keyCategory.set(key, cat.ref_section);
|
|
756
|
+
combinedEntries.set(key, entry);
|
|
757
|
+
keys.add(key);
|
|
758
|
+
}
|
|
759
|
+
categoryKeys.set(cat.ref_section, keys);
|
|
760
|
+
}
|
|
761
|
+
// A key claimed by more than one category was added to every
|
|
762
|
+
// claiming category's own Set above, since each category's loop
|
|
763
|
+
// only knows about categories processed before it. keyCategory now
|
|
764
|
+
// holds each conflicted key's final, later-wins owner; remove the
|
|
765
|
+
// key from every OTHER category's Set so only its real owner lists
|
|
766
|
+
// it, matching the warning already logged above.
|
|
767
|
+
for (const [cat, keys] of categoryKeys) {
|
|
768
|
+
for (const key of [...keys]) {
|
|
769
|
+
if (keyCategory.get(key) !== cat)
|
|
770
|
+
keys.delete(key);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
// Each category's own ref_force list reassigns a key's category
|
|
774
|
+
// membership outright, the same mechanism as the natural conflict
|
|
775
|
+
// resolution above: strip the key from its previous category's Set,
|
|
776
|
+
// then add it to THIS category's Set, the one whose own ref_force
|
|
777
|
+
// names it. A key's rendered row lives in exactly one category at a
|
|
778
|
+
// time, so this reassignment, not an additive union, is what "force
|
|
779
|
+
// this citation under this heading" actually means. Nesting
|
|
780
|
+
// ref_force inside its own category means the target is never
|
|
781
|
+
// ambiguous: there is no separate back-reference field to get wrong.
|
|
782
|
+
//
|
|
783
|
+
// A key with no entry anywhere warns rather than silently rendering
|
|
784
|
+
// nowhere: a visible mistake beats a citation named in ref_force
|
|
785
|
+
// deliberately just vanishing over a typo.
|
|
786
|
+
for (const cat of categories) {
|
|
787
|
+
for (const key of cat.ref_force) {
|
|
788
|
+
if (!combinedEntries.has(key)) {
|
|
789
|
+
logger.warn(`cite-it: ${compKey} - ref_force names unknown key "${key}" in category "${cat.ref_section}"; not found in any configured category's data`);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const previousSection = keyCategory.get(key);
|
|
793
|
+
if (previousSection && previousSection !== cat.ref_section) {
|
|
794
|
+
categoryKeys.get(previousSection)?.delete(key);
|
|
795
|
+
}
|
|
796
|
+
keyCategory.set(key, cat.ref_section);
|
|
797
|
+
categoryKeys.get(cat.ref_section)?.add(key);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (!cfg.suppress_events) {
|
|
801
|
+
logEvent(`${compKey} - ${combinedEntries.size} bibliography entr${combinedEntries.size === 1 ? 'y' : 'ies'} loaded across ${categories.length} categor${categories.length === 1 ? 'y' : 'ies'}`);
|
|
802
|
+
}
|
|
803
|
+
for (const key of citedKeys) {
|
|
804
|
+
if (!combinedEntries.has(key)) {
|
|
805
|
+
logger.warn(`cite-it: ${compKey} - citeit:[${key}] cites an unknown key; not found in any configured category`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
// ---------------------------------------------------------------
|
|
809
|
+
// 3. Populate the shared cache the citeit:[key] macro reads at
|
|
810
|
+
// real conversion time (macro.ts). Formatted only for keys
|
|
811
|
+
// actually used (cited, or ref_force), same set the
|
|
812
|
+
// generated partial lists.
|
|
813
|
+
// ---------------------------------------------------------------
|
|
814
|
+
const refForceKeys = new Set(categories.flatMap((cat) => cat.ref_force));
|
|
815
|
+
const usedKeysByCategory = new Map();
|
|
816
|
+
const allUsedKeys = [];
|
|
817
|
+
for (const cat of categories) {
|
|
818
|
+
const keys = [...(categoryKeys.get(cat.ref_section) ?? new Set())]
|
|
819
|
+
.filter((k) => citedKeys.has(k) || refForceKeys.has(k))
|
|
820
|
+
.sort((a, b) => a.localeCompare(b));
|
|
821
|
+
usedKeysByCategory.set(cat.ref_section, keys);
|
|
822
|
+
allUsedKeys.push(...keys);
|
|
823
|
+
}
|
|
824
|
+
// A key's entry carrying a supersededby field (registry.ts) warns
|
|
825
|
+
// once here, at generation time, scoped to the keys a category's
|
|
826
|
+
// partial actually lists (cited or ref_force'd), not every entry
|
|
827
|
+
// the registry happens to hold: an unused entry going stale is not
|
|
828
|
+
// this component-version's problem to flag. Deliberately not
|
|
829
|
+
// raised again at each citeit:[key] use site (macro.ts): one clear
|
|
830
|
+
// signal per generated partial, not one per occurrence in prose.
|
|
831
|
+
for (const [section, keys] of usedKeysByCategory) {
|
|
832
|
+
for (const key of keys) {
|
|
833
|
+
const supersededBy = combinedEntries.get(key)?.['supersededby'];
|
|
834
|
+
if (typeof supersededBy === 'string' && supersededBy) {
|
|
835
|
+
logger.warn(`cite-it: ${compKey} - citation "${key}" in category "${section}" carries supersededby "${supersededBy}"; a newer revision is available`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const formattedByKey = (0, csl_1.formatBibliographyEntries)(compStyle, combinedEntries, allUsedKeys);
|
|
840
|
+
const cacheData = { entriesByKey: combinedEntries, formattedByKey };
|
|
841
|
+
macro_1.citeCache.set(compKey, cacheData);
|
|
842
|
+
assemblyScanContexts.set(compKey, {
|
|
843
|
+
contentCatalog: contentCatalog,
|
|
844
|
+
component,
|
|
845
|
+
version,
|
|
846
|
+
componentAttrs,
|
|
847
|
+
module,
|
|
848
|
+
categoryPartials,
|
|
849
|
+
});
|
|
850
|
+
keyCategories.set(compKey, keyCategory);
|
|
851
|
+
// ---------------------------------------------------------------
|
|
852
|
+
// 4. Generate and inject each category's own bibliography
|
|
853
|
+
// partial. A component author routes different categories to
|
|
854
|
+
// different pages simply by choosing which page includes
|
|
855
|
+
// which category's partial.
|
|
856
|
+
// ---------------------------------------------------------------
|
|
857
|
+
for (const cp of categoryPartials) {
|
|
858
|
+
const cat = categories.find((c) => c.ref_section === cp.section);
|
|
859
|
+
const keys = usedKeysByCategory.get(cat.ref_section) ?? [];
|
|
860
|
+
const content = generateCategoryPartial(keys, formattedByKey);
|
|
861
|
+
(0, index_2.injectPartial)(contentCatalog, files, component, version, module, cp.relative, content, compWriteToDisk, logger);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
logEvent('cite-it finished');
|
|
866
|
+
});
|
|
867
|
+
// ---------------------------------------------------------------------
|
|
868
|
+
// PDF/assembled numbering. See assembly-numbering.ts's own module
|
|
869
|
+
// docblock for the full reasoning behind the numbering mechanism itself.
|
|
870
|
+
//
|
|
871
|
+
// Computed LAZILY, memoized per component-version, on the first assembled
|
|
872
|
+
// loadAsciiDoc call the wrapper above sees for that component-version,
|
|
873
|
+
// rather than eagerly in a navigationBuilt listener as an earlier version
|
|
874
|
+
// of this code did (the earlier version genuinely raced against
|
|
875
|
+
// `@antora/assembler`'s own navigationBuilt listener and lost against a
|
|
876
|
+
// real playbook where `@antora/pdf-extension` preceded cite-it in
|
|
877
|
+
// `antora.extensions`, leaving every citeit:[key] unrewritten with no
|
|
878
|
+
// error). Computing lazily on first actual use, inside this same wrapper
|
|
879
|
+
// invocation, sidesteps that race entirely regardless of extension order.
|
|
880
|
+
//
|
|
881
|
+
// A DIFFERENT bug surfaced later, unrelated to that race: this function
|
|
882
|
+
// used to always resolve navigation as `componentVersion?.navigation`,
|
|
883
|
+
// falling back to a hardcoded, always-unnamed assembler-profile lookup
|
|
884
|
+
// (`assemblerProfiles.get(compKey).get(undefined)`) for any navFiles
|
|
885
|
+
// override. Verified directly against @antora/assembler's own source
|
|
886
|
+
// (assemble-content.js/build-navigation.js): `componentVersion.navigation`
|
|
887
|
+
// is only ever assigned by @antora/navigation-builder for a component
|
|
888
|
+
// version that owns at least one real `nav`-family file — it stays
|
|
889
|
+
// permanently undefined otherwise, by design, not a timing artifact.
|
|
890
|
+
// Assembler itself resolves the active profile as `assemblyConfig.profile`,
|
|
891
|
+
// which DEFAULTS TO THE TARGET BACKEND NAME (e.g. 'pdf') whenever no
|
|
892
|
+
// profile is configured explicitly, never bare `undefined` in an ordinary
|
|
893
|
+
// PDF build, and looks up that component-version's navFiles override
|
|
894
|
+
// under THAT key first, falling back to the unnamed slot only as a second
|
|
895
|
+
// choice (`componentVersionProfiles?.get(baseModel.profile) ??
|
|
896
|
+
// componentVersionProfiles?.get()`). A hardcoded `.get(undefined)` lookup
|
|
897
|
+
// here therefore misses the real override in the common case. Assembler
|
|
898
|
+
// also bakes the resolved profile name into every assembled document's
|
|
899
|
+
// own real attributes as `assembler-profile` (assemble-content.js's
|
|
900
|
+
// intrinsicAttributes, merged into what the wrapper above already
|
|
901
|
+
// receives as `asciidocConfig.attributes`) specifically so a consumer
|
|
902
|
+
// like this one can read back the SAME profile it resolved, rather than
|
|
903
|
+
// needing to reimplement its resolution logic; `profileName` below is
|
|
904
|
+
// that value, threaded through from the wrapper.
|
|
905
|
+
//
|
|
906
|
+
// A THIRD gap surfaced later still, confirmed live against a real Antora
|
|
907
|
+
// build: a playbook can register @antora/pdf-extension programmatically,
|
|
908
|
+
// at normal register()-phase time, instead of through the playbook's
|
|
909
|
+
// own antora.extensions list, with no ordering race either way. With no
|
|
910
|
+
// ext.assembler block anywhere (assemblerProfiles legitimately empty)
|
|
911
|
+
// and real, unmodified Antora navigation driving assembly correctly,
|
|
912
|
+
// `componentVersion.navigation` still read empty here. Assembler's own
|
|
913
|
+
// no-profile resolution
|
|
914
|
+
// (assemble-content.js's generateSelectAssemblerProfile) never actually
|
|
915
|
+
// reads componentVersion.navigation FIRST — it reads
|
|
916
|
+
// `navigationCatalog?.getNavigation(componentVersion.name,
|
|
917
|
+
// componentVersion.version) ?? componentVersion.navigation`, the
|
|
918
|
+
// NavigationCatalog instance @antora/navigation-builder's own
|
|
919
|
+
// buildNavigation returns and generate-site.js stores as the
|
|
920
|
+
// navigationCatalog context variable, alongside (not only through)
|
|
921
|
+
// componentVersion.navigation. This function only ever checked the
|
|
922
|
+
// fallback half of that chain; matching assembler's own real
|
|
923
|
+
// precedence, primary source first, is the fix, not a guess: it can
|
|
924
|
+
// only find more, never less, than the fallback-only lookup already
|
|
925
|
+
// did.
|
|
926
|
+
//
|
|
927
|
+
// A FOURTH gap: even navigationCatalog can read empty. Confirmed live,
|
|
928
|
+
// not a codebase bug: componentVersion.navigation itself reads undefined
|
|
929
|
+
// at this exact read point despite a real nav-family file existing in
|
|
930
|
+
// the content catalog, confirmed as the SAME object reference two
|
|
931
|
+
// independent ways (this function's own
|
|
932
|
+
// manual lookup and contentCatalog.getComponentVersion()) — not a stale
|
|
933
|
+
// copy, not the wrong object. Rather than depend further on exactly
|
|
934
|
+
// when/how something else populates it, self-heal: call
|
|
935
|
+
// @antora/navigation-builder's own buildNavigation directly (exposed via
|
|
936
|
+
// getFunctions(), the same mechanism buildAlternateNavigation below
|
|
937
|
+
// already uses), which re-derives navigation from the catalog's own
|
|
938
|
+
// nav-family files and reassigns componentVersion.navigation as a
|
|
939
|
+
// genuine side effect, discarding buildNavigation's own returned
|
|
940
|
+
// NavigationCatalog (a fresh, separate instance every call) rather than
|
|
941
|
+
// touching the site's real one.
|
|
942
|
+
// ---------------------------------------------------------------------
|
|
943
|
+
function ensureAssemblyDataComputed(compKey, profileName) {
|
|
944
|
+
if (citationNumbers.has(compKey))
|
|
945
|
+
return; // already computed, or in progress
|
|
946
|
+
const scanCtx = assemblyScanContexts.get(compKey);
|
|
947
|
+
if (!scanCtx)
|
|
948
|
+
return;
|
|
949
|
+
citationNumbers.set(compKey, new Map()); // reentrancy guard, replaced below
|
|
950
|
+
// scanCtx.contentCatalog is typed against assembly-numbering.ts's own
|
|
951
|
+
// narrower ContentCatalog interface (no getComponents); the stored
|
|
952
|
+
// runtime object is the same real catalog assemblyScanContexts.set(...)
|
|
953
|
+
// captured, just narrowed at that point too, so this cast back to the
|
|
954
|
+
// wider local interface is safe.
|
|
955
|
+
const contentCatalog = scanCtx.contentCatalog;
|
|
956
|
+
const components = contentCatalog.getComponents?.() ?? [];
|
|
957
|
+
const contextVars = ctx.getVariables?.();
|
|
958
|
+
const assemblerProfiles = contextVars?.assemblerProfiles;
|
|
959
|
+
const navigationCatalog = contextVars?.navigationCatalog;
|
|
960
|
+
const componentVersion = components
|
|
961
|
+
.find((c) => c.name === scanCtx.component)?.versions
|
|
962
|
+
.find((v) => (v.version ?? v.name) === scanCtx.version);
|
|
963
|
+
let liveNavigation = navigationCatalog?.getNavigation(scanCtx.component, scanCtx.version);
|
|
964
|
+
if ((!liveNavigation || !liveNavigation.length) && ctx.getFunctions) {
|
|
965
|
+
// Self-heal: confirmed live, not a codebase bug, that
|
|
966
|
+
// componentVersion.navigation, and
|
|
967
|
+
// navigationCatalog's own record of it, can BOTH read empty here
|
|
968
|
+
// even though a real nav-family file exists in the content catalog
|
|
969
|
+
// and the SAME object reference is confirmed live two independent
|
|
970
|
+
// ways (this manual lookup and contentCatalog.getComponentVersion())
|
|
971
|
+
// — ruling out a stale copy or wrong-object bug on cite-it's own
|
|
972
|
+
// side. buildNavigation is self-contained and idempotent: it
|
|
973
|
+
// re-derives navigation straight from the catalog's own nav-family
|
|
974
|
+
// files and re-assigns componentVersion.navigation directly,
|
|
975
|
+
// discarding its own returned NavigationCatalog (a fresh, separate
|
|
976
|
+
// instance every call) rather than touching the site's real one.
|
|
977
|
+
// Same getFunctions() mechanism buildAlternateNavigation below
|
|
978
|
+
// already uses.
|
|
979
|
+
//
|
|
980
|
+
// buildNavigation's own internal implementation reads its loadAsciiDoc
|
|
981
|
+
// through this SAME live, shared function table (getFunctions'
|
|
982
|
+
// $unsafe accessor, by design, so it picks up any real replacement -
|
|
983
|
+
// including this extension's own wrapper). This self-heal fires from
|
|
984
|
+
// INSIDE that wrapper, mid-conversion of some other real page, so
|
|
985
|
+
// without intervention buildNavigation's own internal parse of each
|
|
986
|
+
// nav.adoc would recurse straight back through the wrapper, nested
|
|
987
|
+
// inside an already-in-progress loadAsciiDoc call. Sidestepped by
|
|
988
|
+
// swapping the live table back to the real, unwrapped loader for the
|
|
989
|
+
// duration of this one synchronous call only, then always restoring
|
|
990
|
+
// the wrapper again, success or failure.
|
|
991
|
+
try {
|
|
992
|
+
const buildNav = ctx.getFunctions().buildNavigation;
|
|
993
|
+
if (typeof buildNav === 'function' && realLoadAsciiDoc) {
|
|
994
|
+
ctx.replaceFunctions?.({ loadAsciiDoc: realLoadAsciiDoc });
|
|
995
|
+
try {
|
|
996
|
+
buildNav(contentCatalog, componentVersion?.asciidoc);
|
|
997
|
+
}
|
|
998
|
+
finally {
|
|
999
|
+
if (loadAsciiDocWrapper)
|
|
1000
|
+
ctx.replaceFunctions?.({ loadAsciiDoc: loadAsciiDocWrapper });
|
|
1001
|
+
}
|
|
1002
|
+
liveNavigation = componentVersion?.navigation;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
logger.warn(`cite-it: ${compKey} - buildNavigation fallback failed: ${String(err)}`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const defaultNavigation = (liveNavigation ??
|
|
1010
|
+
componentVersion?.navigation ??
|
|
1011
|
+
[]);
|
|
1012
|
+
let navigation = defaultNavigation;
|
|
1013
|
+
// Same two-step lookup assembler itself performs: the resolved active
|
|
1014
|
+
// profile first (never bare undefined for an ordinary export whose
|
|
1015
|
+
// config omits `profile:` explicitly, see the docblock above), the
|
|
1016
|
+
// unnamed default slot only as a fallback.
|
|
1017
|
+
const componentVersionProfiles = assemblerProfiles?.get(`${scanCtx.version}@${scanCtx.component}`);
|
|
1018
|
+
const profileData = componentVersionProfiles?.get(profileName) ?? componentVersionProfiles?.get(undefined);
|
|
1019
|
+
if (profileData?.navFiles?.length && ctx.require && ctx.getFunctions) {
|
|
1020
|
+
try {
|
|
1021
|
+
// Mirrors @antora/assembler's own resolution exactly
|
|
1022
|
+
// (assemble-content.js's generateSelectAssemblerProfile):
|
|
1023
|
+
// buildAlternateNavigation from the shared generator functions if
|
|
1024
|
+
// some other extension already replaced it, otherwise straight
|
|
1025
|
+
// from the real @antora/navigation-builder module.
|
|
1026
|
+
const fxns = ctx.getFunctions();
|
|
1027
|
+
const buildAlternateNavigation = fxns.buildAlternateNavigation ??
|
|
1028
|
+
ctx.require('@antora/navigation-builder').buildAlternateNavigation;
|
|
1029
|
+
navigation = (buildAlternateNavigation(contentCatalog, componentVersion, profileData.navFiles, {}) ?? []);
|
|
1030
|
+
}
|
|
1031
|
+
catch (err) {
|
|
1032
|
+
logger.warn(`cite-it: ${compKey} - could not resolve the assembler profile's alternate navigation, falling back to the default navigation for numbering: ${String(err)}`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const numbers = (0, assembly_numbering_1.computeCitationNumbers)(navigation, scanCtx);
|
|
1036
|
+
citationNumbers.set(compKey, numbers);
|
|
1037
|
+
logger.debug(`cite-it: ${compKey} - computed ${numbers.size} true first-appearance citation number(s) for assembled output`);
|
|
1038
|
+
const categoryScopes = (0, assembly_numbering_1.findBibliographyScopes)(navigation, scanCtx);
|
|
1039
|
+
const keyCategory = keyCategories.get(compKey) ?? new Map();
|
|
1040
|
+
const keyScopes = new Map();
|
|
1041
|
+
const missingScopeCategories = new Set();
|
|
1042
|
+
for (const [key] of numbers) {
|
|
1043
|
+
const cat = keyCategory.get(key);
|
|
1044
|
+
const scope = cat ? categoryScopes.get(cat) : undefined;
|
|
1045
|
+
if (scope) {
|
|
1046
|
+
keyScopes.set(key, scope);
|
|
1047
|
+
}
|
|
1048
|
+
else if (cat) {
|
|
1049
|
+
missingScopeCategories.add(cat);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
keyScopesByComp.set(compKey, keyScopes);
|
|
1053
|
+
for (const cat of missingScopeCategories) {
|
|
1054
|
+
logger.warn(`cite-it: ${compKey} - could not find the page that includes category "${cat}"'s generated partial; its citation links will render as plain numbers, not clickable links`);
|
|
1055
|
+
}
|
|
1056
|
+
// The generated bibliography partial's own citeit:[key] row labels
|
|
1057
|
+
// need rewriting too, and specifically now, before any assembly-time
|
|
1058
|
+
// loadAsciiDoc call resolves an include:: of it: Antora's include
|
|
1059
|
+
// processor reads an included partial's content directly from the
|
|
1060
|
+
// catalog, never back through loadAsciiDoc, so the wrapper above never
|
|
1061
|
+
// gets a chance to touch it otherwise (see rewritePartialForAssembly's
|
|
1062
|
+
// own docblock for the full story). Safe to do here, synchronously,
|
|
1063
|
+
// before returning: this function itself is always called from inside
|
|
1064
|
+
// the wrapper, before that same invocation's real loadAsciiDoc call.
|
|
1065
|
+
(0, assembly_numbering_1.rewritePartialForAssembly)(scanCtx, numbers);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* One category's own generated partial content: the bibliography list
|
|
1070
|
+
* only, no heading. A component author decides where THIS category
|
|
1071
|
+
* appears by choosing which page includes `partial$<categoryPartialName
|
|
1072
|
+
* (...)>`, independent of every other category, and supplies that page's
|
|
1073
|
+
* own heading directly as ordinary authored AsciiDoc immediately above
|
|
1074
|
+
* the include line. Returns '' for an empty category (caller skips
|
|
1075
|
+
* injecting it at all).
|
|
1076
|
+
*/
|
|
1077
|
+
function generateCategoryPartial(keys, formattedByKey) {
|
|
1078
|
+
if (keys.length === 0)
|
|
1079
|
+
return '';
|
|
1080
|
+
// The cite-it-bibliography role is a real theming hook, not
|
|
1081
|
+
// decoration: dlist_role_theming.rb (the PDF theme's own converter
|
|
1082
|
+
// extension, since asciidoctor-pdf's dlist rendering has no per-role
|
|
1083
|
+
// lookup of its own) reads role_cite-it-bibliography_* theme keys for
|
|
1084
|
+
// this exact role to control term boldness and text alignment for PDF
|
|
1085
|
+
// output, and it renders as an ordinary CSS-targetable class in HTML.
|
|
1086
|
+
const lines = ['[horizontal.cite-it-bibliography]'];
|
|
1087
|
+
for (const key of keys) {
|
|
1088
|
+
const text = formattedByKey.get(key) ?? `(no formatted text for "${key}")`;
|
|
1089
|
+
// The [[cite-<key>]] anchor is what an inline citeit:[key] citation
|
|
1090
|
+
// links to for assembled/PDF output (rewriteCiteMarkers, asLink:
|
|
1091
|
+
// true), see assembly-numbering.ts. Harmless, invisible in ordinary
|
|
1092
|
+
// HTML rendering.
|
|
1093
|
+
lines.push(`[[cite-${key}]]citeit:[${key}]:: ${text}`);
|
|
1094
|
+
}
|
|
1095
|
+
lines.push('');
|
|
1096
|
+
return lines.join('\n');
|
|
1097
|
+
}
|
|
1098
|
+
//# sourceMappingURL=index.js.map
|