@heroiclands/package-build 22.3.0 → 22.4.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.md +59 -0
- package/CONTENT.md +182 -306
- package/bin/content-build.mjs +40 -23
- package/bin/package-build.mjs +4 -3
- package/content-config.mjs +82 -180
- package/docs/api.md +27 -32
- package/docs/commands.md +54 -19
- package/docs/configuration.md +102 -132
- package/docs/content-format.md +25 -15
- package/docs/getting-started.md +5 -4
- package/docs/project-setup.md +1 -1
- package/engine/content-links.mjs +25 -60
- package/engine/diagnostics.mjs +2 -2
- package/engine/field-reference.mjs +141 -0
- package/engine/homepage.mjs +91 -172
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-vocabulary.mjs +0 -20
- package/engine/pdf-build.mjs +2 -2
- package/engine/site-build.mjs +64 -225
- package/engine/site-config.mjs +15 -53
- package/engine/site-root.mjs +26 -70
- package/package.json +1 -1
- package/types/content-config.d.mts +14 -8
- package/types/engine/content-links.d.mts +12 -23
- package/types/engine/diagnostics.d.mts +2 -2
- package/types/engine/field-reference.d.mts +78 -0
- package/types/engine/homepage.d.mts +51 -91
- package/types/engine/note-vocabulary.d.mts +0 -12
- package/types/engine/site-build.d.mts +28 -129
- package/types/engine/site-config.d.mts +10 -19
- package/types/engine/site-root.d.mts +11 -36
package/engine/homepage.mjs
CHANGED
|
@@ -20,24 +20,19 @@
|
|
|
20
20
|
* written by a person: what the module is, which system it needs, how to install
|
|
21
21
|
* it, where its source lives. Nothing about it is derived.
|
|
22
22
|
*
|
|
23
|
-
* **Authored, not assembled.**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* the package's name is not written twice.
|
|
23
|
+
* **Authored, not assembled.** The things that matter most on a front page
|
|
24
|
+
* cannot be derived: that Kethira requires buying the book from Keléstia, what
|
|
25
|
+
* Thalorna's setting *is*, which of twenty pages a reader should start with. So
|
|
26
|
+
* the only thing defaulted here is the title, from `packageBuild.manifest.title`,
|
|
27
|
+
* so that the package's name is not written twice. Everything else is the body,
|
|
28
|
+
* which is why a `landing:` card block is refused: the homepage is a page with a
|
|
29
|
+
* body, rendered as one, and an index of what the package publishes is a `doc`
|
|
30
|
+
* note carrying a content table.
|
|
32
31
|
*
|
|
33
32
|
* **Dispatched by `type`, not by filename.** A fixed `homepage.md` the walker
|
|
34
33
|
* special-cased would be the anomaly: notes are routed by frontmatter, not by
|
|
35
34
|
* location, and `NOTE_SCHEMAS` already routes `doc`, `macro`, `being` and the
|
|
36
|
-
* map types. `homepage` is one more entry whose compile step emits a page.
|
|
37
|
-
* deliberately not `README.md`: `landing: readme` already means "a `README.md`
|
|
38
|
-
* is its section's landing page", and `sohl-thalorna/assets/content/README.md`
|
|
39
|
-
* is a developer explainer about the source tree — adopting that name would make
|
|
40
|
-
* Thalorna's public front page its build documentation.
|
|
35
|
+
* map types. `homepage` is one more entry whose compile step emits a page.
|
|
41
36
|
*
|
|
42
37
|
* **Engine, not `sohl/`.** The `engine/` ÷ `sohl/` line separates *note-format*
|
|
43
38
|
* knowledge from *game-system* knowledge, and a homepage is note format: it
|
|
@@ -47,27 +42,24 @@
|
|
|
47
42
|
* type living in the SoHL registry would be unavailable to HM3 and to every HM3
|
|
48
43
|
* module, which is most of the packages that need a homepage and nothing else.
|
|
49
44
|
*
|
|
50
|
-
* **
|
|
51
|
-
*
|
|
52
|
-
* `/<package
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* to
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* compiles into no document, so it carries no compendium UUID and appears in no
|
|
63
|
-
* pack and in no link-manifest entry.
|
|
45
|
+
* **Published at the package root, addressed like every other note.** A
|
|
46
|
+
* homepage is written as the mount's `_index.md`, so Hugo's `home` kind renders
|
|
47
|
+
* it at `/<package>/` — the address a reader expects a package's front page at,
|
|
48
|
+
* and the one `package.json`'s `homepage` states. It still declares a
|
|
49
|
+
* `shortcode` — conventionally {@link HOMEPAGE_SHORTCODE} — because that is what
|
|
50
|
+
* a link is written with: `[[homepage-root|Text]]` is an ordinary wikilink, and
|
|
51
|
+
* it resolves to `/<package>/`. The shortcode names the page in links; the
|
|
52
|
+
* address is the package root.
|
|
53
|
+
*
|
|
54
|
+
* `id` is refused, on its own ground: a homepage compiles into no document, so
|
|
55
|
+
* it carries no compendium UUID and appears in no pack and in no link-manifest
|
|
56
|
+
* entry.
|
|
64
57
|
*
|
|
65
58
|
* @module
|
|
66
59
|
*/
|
|
67
60
|
|
|
68
61
|
import fs from "node:fs";
|
|
69
62
|
|
|
70
|
-
import { addressSlug } from "./content-address.mjs";
|
|
71
63
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
72
64
|
import { formatLocator, positionInFrontmatter } from "./diagnostics.mjs";
|
|
73
65
|
|
|
@@ -107,20 +99,15 @@ export const HOMEPAGE_SHORTCODE = "root";
|
|
|
107
99
|
/**
|
|
108
100
|
* The file a homepage is written to, relative to the package's site root.
|
|
109
101
|
*
|
|
110
|
-
*
|
|
111
|
-
* —
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* `/<package>/kb/homepage/` that nothing links to and nobody wrote.
|
|
102
|
+
* The mount's own `_index.md`: Hugo renders it as the `home` kind, at
|
|
103
|
+
* `baseURL` — which is `/<package>/`, the package's own address. One fixed
|
|
104
|
+
* destination rather than one derived from the note's address, because the
|
|
105
|
+
* homepage's address *is* the package root; the note's `shortcode` names the
|
|
106
|
+
* page in links and decides no file.
|
|
116
107
|
*
|
|
117
|
-
* @
|
|
118
|
-
* @returns {string} The destination filename, e.g. `homepage-root.md`.
|
|
119
|
-
* @throws {Error} When the note declares no shortcode, and so has no address.
|
|
108
|
+
* @type {string}
|
|
120
109
|
*/
|
|
121
|
-
export
|
|
122
|
-
return `${addressSlug(fm)}.md`;
|
|
123
|
-
}
|
|
110
|
+
export const HOMEPAGE_DESTINATION = "_index.md";
|
|
124
111
|
|
|
125
112
|
/**
|
|
126
113
|
* Whether a note's frontmatter declares the homepage type.
|
|
@@ -133,17 +120,16 @@ export function isHomepage(fm) {
|
|
|
133
120
|
}
|
|
134
121
|
|
|
135
122
|
/**
|
|
136
|
-
* The top-level
|
|
123
|
+
* The top-level fields a homepage refuses, and what each would decide.
|
|
137
124
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* everywhere else and are permitted.
|
|
125
|
+
* `id` is the Foundry document id a compendium UUID is built from, and a
|
|
126
|
+
* homepage compiles into no document. `landing` is a card block; the homepage
|
|
127
|
+
* is a page with a body, rendered as one, so nothing reads it — and an index
|
|
128
|
+
* of what the package publishes is a `doc` note carrying a content table,
|
|
129
|
+
* authored where every other page is.
|
|
144
130
|
*
|
|
145
|
-
* `
|
|
146
|
-
*
|
|
131
|
+
* `name` and `shortcode` are permitted: the shortcode is what a link is
|
|
132
|
+
* written with, and `name` titles the page like every other note's.
|
|
147
133
|
*
|
|
148
134
|
* **A named class, not an allow-list, and that boundary is the decision.** A
|
|
149
135
|
* homepage's frontmatter is *emitted into the published page*
|
|
@@ -167,6 +153,14 @@ export const HOMEPAGE_REFUSED_FIELDS = Object.freeze(
|
|
|
167
153
|
"homepage compiles into no document — it appears in no pack " +
|
|
168
154
|
"and in no link manifest. Delete it",
|
|
169
155
|
],
|
|
156
|
+
[
|
|
157
|
+
"landing",
|
|
158
|
+
"`landing` decides nothing on a `type: homepage` note: the homepage " +
|
|
159
|
+
"is a page with a body, rendered as one, and no card block is " +
|
|
160
|
+
"read off it. Write the page's links in its body, and author an " +
|
|
161
|
+
"index of what the package publishes as a `doc` note carrying a " +
|
|
162
|
+
"content table. Delete it",
|
|
163
|
+
],
|
|
170
164
|
]),
|
|
171
165
|
);
|
|
172
166
|
|
|
@@ -174,7 +168,7 @@ export const HOMEPAGE_REFUSED_FIELDS = Object.freeze(
|
|
|
174
168
|
* What the address rule says about one note's top-level fields.
|
|
175
169
|
*
|
|
176
170
|
* Two statements about the same thing, so they are made together: the field a
|
|
177
|
-
* homepage **owes** and the
|
|
171
|
+
* homepage **owes** and the fields it may **not** write.
|
|
178
172
|
*
|
|
179
173
|
* The missing `shortcode` comes first, and is located at `type:` rather than at
|
|
180
174
|
* a key that is not there — the `homepage` value is what makes the field
|
|
@@ -184,8 +178,9 @@ export const HOMEPAGE_REFUSED_FIELDS = Object.freeze(
|
|
|
184
178
|
* diagnostic per finding walks down the file.
|
|
185
179
|
*
|
|
186
180
|
* Presence is the whole test for a refused field, and absence-or-blank for the
|
|
187
|
-
* required one: `shortcode:` authored empty is no address, and
|
|
188
|
-
* make `id` mean something on a page that compiles to no document
|
|
181
|
+
* required one: `shortcode:` authored empty is no address, and no value can
|
|
182
|
+
* make `id` or `landing` mean something on a page that compiles to no document
|
|
183
|
+
* and renders as its body.
|
|
189
184
|
*
|
|
190
185
|
* Each finding carries the `locator` key to position it at, because the two
|
|
191
186
|
* things that would resolve one — the raw note text and the position helper —
|
|
@@ -220,10 +215,10 @@ export function checkHomepageAddressFields(fm, { isAuthored } = {}) {
|
|
|
220
215
|
locator: { key: "type", literal: HOMEPAGE_TYPE },
|
|
221
216
|
message:
|
|
222
217
|
"a `type: homepage` note declares a `shortcode`, like every " +
|
|
223
|
-
"other note: it is addressed as `homepage-<shortcode
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
`\`shortcode: ${HOMEPAGE_SHORTCODE}\` — the
|
|
218
|
+
"other note: it is addressed as `homepage-<shortcode>`, which " +
|
|
219
|
+
"is what `[[homepage-<shortcode>|Text]]` is written with to " +
|
|
220
|
+
"reach the package's front page at `/<package>/`. Write " +
|
|
221
|
+
`\`shortcode: ${HOMEPAGE_SHORTCODE}\` — the front page is ` +
|
|
227
222
|
`\`homepage-${HOMEPAGE_SHORTCODE}\` in every package`,
|
|
228
223
|
});
|
|
229
224
|
}
|
|
@@ -247,13 +242,11 @@ export function checkHomepageAddressFields(fm, { isAuthored } = {}) {
|
|
|
247
242
|
* this exists to prevent, and it is silent — the site build reports `wrote 0
|
|
248
243
|
* homepage(s)` and exits 0.
|
|
249
244
|
* - _Two_ and it serves a page nobody chose. **This is a cardinality rule, and
|
|
250
|
-
* only that.**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* name is a question nothing here can answer, and both being reachable is
|
|
256
|
-
* not an answer to it.
|
|
245
|
+
* only that.** Both are written to the mount's `_index.md`, so the second
|
|
246
|
+
* silently overwrites the first; the duplicate-address check catches only
|
|
247
|
+
* the pair that happen to share a shortcode, and says nothing at all about
|
|
248
|
+
* a `homepage-root` beside a `homepage-front`. Which of the two should be
|
|
249
|
+
* the front page is a question nothing here can answer.
|
|
257
250
|
*
|
|
258
251
|
* Neither has a safe default, so neither is a warning. A warning is the right
|
|
259
252
|
* severity for something a build can proceed past correctly, and a build that
|
|
@@ -315,10 +308,9 @@ export function checkHomepageCount(found, { contentBase, contentPackage }) {
|
|
|
315
308
|
message:
|
|
316
309
|
`duplicate \`type: homepage\` note, also declared by ` +
|
|
317
310
|
`${others.join(", ")}; a package has one front page` +
|
|
318
|
-
`${contentPackage ? `, at${address}` : ""}, and
|
|
319
|
-
`
|
|
320
|
-
`
|
|
321
|
-
`the rest ordinary notes`,
|
|
311
|
+
`${contentPackage ? `, at${address}` : ""}, and nothing here ` +
|
|
312
|
+
`can say which of these it should be. Keep one, and make the ` +
|
|
313
|
+
`rest ordinary notes`,
|
|
322
314
|
};
|
|
323
315
|
});
|
|
324
316
|
}
|
|
@@ -371,24 +363,15 @@ export function homepageTitle(fm, config) {
|
|
|
371
363
|
/**
|
|
372
364
|
* The frontmatter a homepage publishes with.
|
|
373
365
|
*
|
|
374
|
-
* The note's own, plus the derived values every emitted page carries: the
|
|
375
|
-
* resolved `title`, the package the build derived — no note declares one
|
|
366
|
+
* The note's own, plus the two derived values every emitted page carries: the
|
|
367
|
+
* resolved `title`, and the package the build derived — no note declares one
|
|
376
368
|
* (`package:` is retired) and the theme's breadcrumb partial reads
|
|
377
|
-
* `.Params.package
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* for one; it decides nothing while `url` is present, but a page carrying only
|
|
384
|
-
* `url` would report a slug Hugo had inferred from the filename.
|
|
385
|
-
*
|
|
386
|
-
* **Site-root relative, and so carrying no package base**, exactly as
|
|
387
|
-
* `pageFrontmatter` states a content page's: Hugo resolves a `url`
|
|
388
|
-
* against `baseURL`, whose path is already where the package is served, so a
|
|
389
|
-
* stated base was written twice and published the landing at
|
|
390
|
-
* `/<package>/<package>/homepage-root/`. Where the package is served is what
|
|
391
|
-
* every *href* is composed from and it reaches this page's address not at all.
|
|
369
|
+
* `.Params.package`.
|
|
370
|
+
*
|
|
371
|
+
* **No `url` and no `slug`.** Hugo publishes the `home` kind at `baseURL`,
|
|
372
|
+
* whose path is already where the package is served, so the page has no
|
|
373
|
+
* address to state; a content page states one because its file sits under the
|
|
374
|
+
* mount and its address does not.
|
|
392
375
|
*
|
|
393
376
|
* An authored `aliases` is dropped for the same reason it is on every other
|
|
394
377
|
* page: Hugo reads it as URL redirects, so passing it through would publish a
|
|
@@ -400,11 +383,9 @@ export function homepageTitle(fm, config) {
|
|
|
400
383
|
* @param {string} options.contentPackage - The package this build publishes.
|
|
401
384
|
* @param {string} options.title - The resolved title.
|
|
402
385
|
* @returns {object} The frontmatter to write.
|
|
403
|
-
* @throws {Error} When the note declares no shortcode, and so has no address.
|
|
404
386
|
*/
|
|
405
387
|
export function homepageFrontmatter(fm, { contentPackage, title }) {
|
|
406
|
-
const
|
|
407
|
-
const data = { ...fm, package: contentPackage, title, slug, url: `/${slug}/` };
|
|
388
|
+
const data = { ...fm, package: contentPackage, title };
|
|
408
389
|
delete data.aliases;
|
|
409
390
|
return data;
|
|
410
391
|
}
|
|
@@ -412,103 +393,41 @@ export function homepageFrontmatter(fm, { contentPackage, title }) {
|
|
|
412
393
|
/**
|
|
413
394
|
* An inline markdown link — `[text](target)`, but not an image.
|
|
414
395
|
*
|
|
415
|
-
* Reference-style links are deliberately not matched: a
|
|
416
|
-
*
|
|
417
|
-
*
|
|
396
|
+
* Reference-style links are deliberately not matched: a homepage is published
|
|
397
|
+
* verbatim, so a `[x][y]` whose definition sits in the body reaches the reader
|
|
398
|
+
* as Hugo renders it, and one whose definition is missing is a defect the
|
|
399
|
+
* rendered page shows for itself.
|
|
418
400
|
*
|
|
419
401
|
* @type {RegExp}
|
|
420
402
|
*/
|
|
421
403
|
const MARKDOWN_LINK = /(?<!!)\[[^\]]*\]\(\s*([^)\s]+)(?:\s+"[^"]*")?\s*\)/g;
|
|
422
404
|
|
|
423
405
|
/**
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
* They are **not** interchangeable, and a check that treated them as one would
|
|
427
|
-
* be wrong about both. The theme resolves a `url` against the site with
|
|
428
|
-
* `relURL`, so a package writes `kb/rules/` and is served `/sohl/kb/rules/`
|
|
429
|
-
* without ever naming its own prefix. An `href` is an address that is *already*
|
|
430
|
-
* resolved and is used verbatim — which is what `cards.source: sections` fills
|
|
431
|
-
* in, since a section's permalink already carries the prefix.
|
|
432
|
-
*
|
|
433
|
-
* So a leading `/` is a defect in a `url` (it is prefixed a second time) and
|
|
434
|
-
* correct in an `href`.
|
|
435
|
-
*
|
|
436
|
-
* @type {ReadonlySet<string>}
|
|
437
|
-
*/
|
|
438
|
-
export const HOMEPAGE_ADDRESS_KEYS = Object.freeze(new Set(["url", "href"]));
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Collect the markdown links in one prose value.
|
|
406
|
+
* Every address a homepage carries: the markdown links in its body.
|
|
442
407
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
|
|
449
|
-
function collectProse(text, field, kind, out, skipCode = false) {
|
|
450
|
-
const pattern = new RegExp(MARKDOWN_LINK.source, "g");
|
|
451
|
-
const matches = skipCode ? matchAllOutsideCode(text, pattern) : [...text.matchAll(pattern)];
|
|
452
|
-
for (const m of matches) out.push({ field, url: m[1], kind });
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Every address a homepage carries, wherever it is written.
|
|
457
|
-
*
|
|
458
|
-
* **Both halves of the page are in scope, and that is the finding rather than
|
|
459
|
-
* the assumption.** Of the six homepages authored today, four carry every link
|
|
460
|
-
* in the body as ordinary markdown and two carry them in `landing:` — and the
|
|
461
|
-
* one whose dead links prompted the check has an *empty body*, so a body-only
|
|
462
|
-
* reading would have found nothing at all on it. A dead link in a card is
|
|
463
|
-
* exactly as broken as one in a paragraph.
|
|
408
|
+
* Nowhere else: the frontmatter holds no address, because a homepage is a
|
|
409
|
+
* page with a body and the card block that once carried links is refused.
|
|
410
|
+
* Top-level `title` and `description` are not walked — they are set as text,
|
|
411
|
+
* never rendered as markdown — and `banner:` is not an address: it is an image
|
|
412
|
+
* path resolved through the CDN base, and `banner: none` is a sentinel rather
|
|
413
|
+
* than a target.
|
|
464
414
|
*
|
|
465
|
-
*
|
|
466
|
-
* the
|
|
415
|
+
* A body link is emitted as written and resolved by the browser against the
|
|
416
|
+
* homepage's own address, which *is* the package root, so a package-relative
|
|
417
|
+
* one (`kb/rules/`) lands where a reader expects.
|
|
467
418
|
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* - **prose and body markdown links** — emitted as written and resolved by the
|
|
471
|
-
* browser against the landing's own address, which *is* the package root, so
|
|
472
|
-
* a relative one means the same thing a `url` does.
|
|
419
|
+
* Links inside code are ignored, so an example in a fenced block is not
|
|
420
|
+
* reported as a dead address.
|
|
473
421
|
*
|
|
474
|
-
* `banner:` is not an address: it is an image path resolved through the CDN
|
|
475
|
-
* base, and `banner: none` is a sentinel rather than a target. Top-level
|
|
476
|
-
* `title` and `description` are not walked either — they are set as text, never
|
|
477
|
-
* rendered as markdown.
|
|
478
|
-
*
|
|
479
|
-
* @param {object|null|undefined} fm - The note's frontmatter.
|
|
480
422
|
* @param {string} [body] - The note's markdown body.
|
|
481
423
|
* @returns {Array<{field: string, url: string, kind: string}>} Every address,
|
|
482
|
-
*
|
|
483
|
-
* written at.
|
|
424
|
+
* in body order, each recorded at `field: "body"` with `kind: "body"`.
|
|
484
425
|
*/
|
|
485
|
-
export function homepageAddresses(
|
|
426
|
+
export function homepageAddresses(body = "") {
|
|
486
427
|
const out = [];
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
if (Array.isArray(value)) {
|
|
494
|
-
value.forEach((v, i) => walk(v, `${field}[${i}]`));
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
if (!value || typeof value !== "object") return;
|
|
498
|
-
for (const [key, v] of Object.entries(value)) {
|
|
499
|
-
const child = `${field}.${key}`;
|
|
500
|
-
// An address field holds an address, not prose: reading it for
|
|
501
|
-
// markdown links as well would report the same target twice
|
|
502
|
-
// whenever one happened to look like a link.
|
|
503
|
-
if (HOMEPAGE_ADDRESS_KEYS.has(key) && typeof v === "string") {
|
|
504
|
-
out.push({ field: child, url: v, kind: key });
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
walk(v, child);
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
walk(fm?.landing, "landing");
|
|
512
|
-
collectProse(String(body ?? ""), "body", "body", out, true);
|
|
428
|
+
const pattern = new RegExp(MARKDOWN_LINK.source, "g");
|
|
429
|
+
for (const m of matchAllOutsideCode(String(body ?? ""), pattern)) {
|
|
430
|
+
out.push({ field: "body", url: m[1], kind: "body" });
|
|
431
|
+
}
|
|
513
432
|
return out;
|
|
514
433
|
}
|
|
@@ -50,6 +50,7 @@ import { packageBuildRecords } from "./packagebuild-index.mjs";
|
|
|
50
50
|
|
|
51
51
|
export { metadataFileName };
|
|
52
52
|
import { PACKAGE_BASE, readCanonicalKey, resolvePackageUrl } from "./content-address.mjs";
|
|
53
|
+
import { HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Written once a fetch completes, so a half-finished cache is never used.
|
|
@@ -365,9 +366,12 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
|
|
|
365
366
|
// must tolerate that rather than invent an href, exactly as it
|
|
366
367
|
// already tolerates an entry with no `uuid`.
|
|
367
368
|
url:
|
|
368
|
-
web
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
!web || !record.address.slug ? undefined
|
|
370
|
+
// A package's homepage is its root: the note is the
|
|
371
|
+
// mount's `_index.md`, so `[[thalorna-homepage-root]]`
|
|
372
|
+
// lands on `/thalorna/`, not on a page below it.
|
|
373
|
+
: parts.type === HOMEPAGE_TYPE ? base
|
|
374
|
+
: resolvePackageUrl(`${record.address.slug}/`, base),
|
|
371
375
|
uuid: foundry?.uuid,
|
|
372
376
|
doc: record.documentation ?? undefined,
|
|
373
377
|
anchors: foundry?.anchors,
|
|
@@ -496,26 +496,6 @@ export function hasTag(fm, tag) {
|
|
|
496
496
|
return false;
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
/**
|
|
500
|
-
* Whether a note carries any `tags:` at all, however authored.
|
|
501
|
-
*
|
|
502
|
-
* The one question the site build asks of tags in aggregate — whether the
|
|
503
|
-
* tree publishes taxonomy pages — rather than about a particular tag. Reads
|
|
504
|
-
* `tags` and `tag` exactly as {@link hasTag} does, and treats an empty list
|
|
505
|
-
* or a blank string as carrying none.
|
|
506
|
-
*
|
|
507
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
508
|
-
* @returns {boolean} Whether the note carries at least one tag.
|
|
509
|
-
*/
|
|
510
|
-
export function hasAnyTag(fm) {
|
|
511
|
-
const raw = fm?.tags ?? fm?.tag;
|
|
512
|
-
if (raw == null) return false;
|
|
513
|
-
for (const entry of Array.isArray(raw) ? raw : [raw]) {
|
|
514
|
-
if (typeof entry === "string" && entry.trim() !== "") return true;
|
|
515
|
-
}
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
499
|
/**
|
|
520
500
|
* Whether a note is tagged as an unfinished **draft**.
|
|
521
501
|
*
|
package/engine/pdf-build.mjs
CHANGED
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
*
|
|
35
35
|
* ## `publish.site` is the switch, and it is the only switch
|
|
36
36
|
*
|
|
37
|
-
* `homepage` mode fences the content surfaces off: the tree is not walked
|
|
38
|
-
*
|
|
37
|
+
* `homepage` mode fences the content surfaces off: the tree is not walked for
|
|
38
|
+
* pages, whatever else the configuration declares. A
|
|
39
39
|
* PDF of the content tree is a content surface by any reading — arguably the
|
|
40
40
|
* most portable one there is — so it is fenced on exactly the same terms, by
|
|
41
41
|
* asking the same {@link module:content-config.publishesContentPages} the
|