@heroiclands/package-build 20.3.0 → 20.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 +97 -0
- package/CONTENT.md +111 -92
- package/README.md +2 -9
- package/bin/content-build.mjs +70 -61
- package/bin/package-build.mjs +2 -2
- package/config.mjs +6 -8
- package/content-config.mjs +187 -64
- package/docs/content-format.md +57 -68
- package/e2e.mjs +4 -4
- package/engine/actor-compiler.mjs +36 -36
- package/engine/address-charset.mjs +6 -6
- package/engine/address-diff.mjs +12 -11
- package/engine/anchored-sections.mjs +1 -1
- package/engine/anchors.mjs +1 -1
- package/engine/base-compiler.mjs +36 -38
- package/engine/bundle-notes.mjs +4 -4
- package/engine/bundles.mjs +8 -8
- package/engine/code-fences.mjs +3 -3
- package/engine/compendiums.mjs +8 -8
- package/engine/compile-corpus.mjs +4 -5
- package/engine/content-address.mjs +15 -15
- package/engine/content-charset.mjs +1 -1
- package/engine/content-format-check.mjs +24 -25
- package/engine/content-format.mjs +8 -8
- package/engine/content-html.mjs +202 -0
- package/engine/content-icons.mjs +225 -98
- package/engine/content-index.mjs +17 -17
- package/engine/content-links.mjs +30 -30
- package/engine/content-lint.mjs +20 -24
- package/engine/content-package.mjs +7 -9
- package/engine/content-slug.mjs +2 -2
- package/engine/content-tables.mjs +7 -8
- package/engine/content-tree.mjs +1 -1
- package/engine/derived-fields.mjs +174 -0
- package/engine/diagnostics.mjs +3 -4
- package/engine/document-subtypes.mjs +6 -7
- package/engine/field-reference.mjs +8 -8
- package/engine/field-spec.mjs +29 -30
- package/engine/folder-notes.mjs +14 -14
- package/engine/foreign-catalog.mjs +8 -9
- package/engine/foundry-entries.mjs +19 -20
- package/engine/frontmatter-lint.mjs +64 -69
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +39 -39
- package/engine/helpers.mjs +63 -54
- package/engine/homepage.mjs +20 -22
- package/engine/ids.mjs +15 -15
- package/engine/index-records.mjs +4 -4
- package/engine/index.mjs +14 -8
- package/engine/item-compiler.mjs +54 -29
- package/engine/item-docs.mjs +11 -13
- package/engine/item-registry.mjs +13 -13
- package/engine/journals.mjs +15 -15
- package/engine/macros.mjs +3 -3
- package/engine/map-notes.mjs +6 -6
- package/engine/metadata-index.mjs +5 -5
- package/engine/note-claims.mjs +36 -34
- package/engine/note-ids.mjs +5 -5
- package/engine/note-package.mjs +4 -4
- package/engine/note-renames.mjs +3 -3
- package/engine/note-schemas.mjs +3 -3
- package/engine/note-vocabulary.mjs +27 -26
- package/engine/pack-config.mjs +13 -13
- package/engine/pack-router.mjs +0 -0
- package/engine/prose-config.mjs +9 -9
- package/engine/prose-lint.mjs +12 -13
- package/engine/region-events.mjs +2 -2
- package/engine/retired-fields.mjs +24 -22
- package/engine/runtime-only-fields.mjs +1 -1
- package/engine/scene-levels.mjs +1 -1
- package/engine/scenes.mjs +19 -20
- package/engine/schema-check.mjs +20 -20
- package/engine/schema-extract.mjs +1 -1
- package/engine/site-build.mjs +45 -47
- package/engine/site-index.mjs +17 -18
- package/engine/sql-tables.mjs +21 -13
- package/engine/subtype-registry.mjs +5 -4
- package/engine/system-block.mjs +18 -19
- package/engine/systems.mjs +2 -2
- package/engine/web-wikilinks.mjs +35 -35
- package/engine/wikilink-syntax.mjs +16 -16
- package/engine/wikilinks.mjs +39 -39
- package/hm3/actors.mjs +22 -22
- package/hm3/document-subtypes.mjs +5 -5
- package/hm3/index.mjs +2 -2
- package/hm3/item-builders.mjs +1 -1
- package/hm3/item-fields.mjs +3 -3
- package/hm3/items.mjs +20 -8
- package/hm3/template-priority.mjs +2 -2
- package/lang.mjs +3 -3
- package/manifest.mjs +17 -20
- package/package.json +1 -2
- package/release.mjs +3 -3
- package/sohl/actors.mjs +28 -28
- package/sohl/affiliation-standings.mjs +2 -2
- package/sohl/being-info.mjs +5 -5
- package/sohl/default-item-art.mjs +5 -5
- package/sohl/document-subtypes.mjs +5 -5
- package/sohl/index.mjs +3 -3
- package/sohl/item-builders.mjs +7 -7
- package/sohl/item-fields.mjs +8 -9
- package/sohl/items.mjs +20 -6
- package/sohl/kb-passes.mjs +5 -5
- package/sohl/note-schemas.mjs +7 -7
- package/sohl/skill-base.mjs +1 -1
- package/types/content-config.d.mts +25 -24
- package/types/e2e.d.mts +3 -3
- package/types/engine/actor-compiler.d.mts +14 -13
- package/types/engine/address-charset.d.mts +6 -6
- package/types/engine/address-diff.d.mts +3 -3
- package/types/engine/base-compiler.d.mts +17 -19
- package/types/engine/bundle-notes.d.mts +3 -3
- package/types/engine/bundles.d.mts +1 -1
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/compendiums.d.mts +3 -3
- package/types/engine/compile-corpus.d.mts +1 -1
- package/types/engine/content-address.d.mts +11 -11
- package/types/engine/content-format-check.d.mts +6 -6
- package/types/engine/content-format.d.mts +2 -2
- package/types/engine/content-html.d.mts +78 -0
- package/types/engine/content-icons.d.mts +150 -62
- package/types/engine/content-index.d.mts +2 -2
- package/types/engine/content-links.d.mts +8 -8
- package/types/engine/content-lint.d.mts +2 -2
- package/types/engine/content-package.d.mts +6 -8
- package/types/engine/derived-fields.d.mts +101 -0
- package/types/engine/diagnostics.d.mts +2 -2
- package/types/engine/document-subtypes.d.mts +3 -3
- package/types/engine/field-spec.d.mts +37 -39
- package/types/engine/folder-notes.d.mts +4 -5
- package/types/engine/foreign-catalog.d.mts +3 -3
- package/types/engine/foundry-entries.d.mts +6 -7
- package/types/engine/frontmatter-lint.d.mts +7 -7
- package/types/engine/frontmatter.d.mts +7 -7
- package/types/engine/generate.d.mts +7 -7
- package/types/engine/helpers.d.mts +25 -27
- package/types/engine/homepage.d.mts +16 -18
- package/types/engine/ids.d.mts +13 -13
- package/types/engine/index-records.d.mts +3 -3
- package/types/engine/index.d.mts +2 -0
- package/types/engine/item-compiler.d.mts +21 -5
- package/types/engine/item-docs.d.mts +2 -2
- package/types/engine/item-registry.d.mts +6 -6
- package/types/engine/journals.d.mts +3 -3
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/metadata-index.d.mts +4 -4
- package/types/engine/note-claims.d.mts +16 -15
- package/types/engine/note-ids.d.mts +4 -4
- package/types/engine/note-package.d.mts +1 -1
- package/types/engine/note-renames.d.mts +3 -3
- package/types/engine/note-vocabulary.d.mts +9 -8
- package/types/engine/pack-config.d.mts +3 -3
- package/types/engine/pack-router.d.mts +1 -1
- package/types/engine/prose-config.d.mts +9 -9
- package/types/engine/prose-lint.d.mts +3 -4
- package/types/engine/region-events.d.mts +2 -2
- package/types/engine/retired-fields.d.mts +10 -9
- package/types/engine/schema-check.d.mts +11 -11
- package/types/engine/site-build.d.mts +18 -19
- package/types/engine/site-index.d.mts +3 -4
- package/types/engine/sql-tables.d.mts +11 -5
- package/types/engine/subtype-registry.d.mts +3 -3
- package/types/engine/system-block.d.mts +3 -3
- package/types/engine/web-wikilinks.d.mts +7 -7
- package/types/engine/wikilink-syntax.d.mts +17 -17
- package/types/engine/wikilinks.d.mts +13 -12
- package/types/hm3/actors.d.mts +1 -1
- package/types/hm3/document-subtypes.d.mts +1 -1
- package/types/hm3/items.d.mts +1 -2
- package/types/hm3/template-priority.d.mts +1 -1
- package/types/manifest.d.mts +8 -8
- package/types/sohl/actors.d.mts +2 -2
- package/types/sohl/affiliation-standings.d.mts +2 -2
- package/types/sohl/being-info.d.mts +2 -2
- package/types/sohl/document-subtypes.d.mts +1 -1
- package/types/sohl/items.d.mts +1 -2
- package/types/sohl/note-schemas.d.mts +1 -1
- package/MIGRATING.md +0 -608
package/engine/system-block.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* **The per-system frontmatter block** — how one note feeds more than one game
|
|
16
|
-
* system
|
|
16
|
+
* system.
|
|
17
17
|
*
|
|
18
18
|
* A note is system-agnostic. The only system-specific things it carries are the
|
|
19
19
|
* properties *named after a system*, and within one:
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* | property | maps to |
|
|
22
22
|
* | --- | --- |
|
|
23
23
|
* | `<system>.system` | `document.system` — the DataModel schema, verbatim paths |
|
|
24
|
-
* | `<system>.type` | `document.type` — the subtype
|
|
24
|
+
* | `<system>.type` | `document.type` — the subtype discriminator |
|
|
25
25
|
* | `<system>.img` | `document.img` |
|
|
26
26
|
* | `<system>.items` | `document.items` — actors only |
|
|
27
27
|
* | `<system>.effects` | `document.effects` |
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*
|
|
48
48
|
* 1. `S.system.<to>` — authored directly, wins outright;
|
|
49
49
|
* 2. `S.<legacyKey>` — the legacy in-block position the corpus still writes,
|
|
50
|
-
* kept until
|
|
50
|
+
* kept until the corpus moves it;
|
|
51
51
|
* 3. the shared top-level property the field **declares** as its source, which
|
|
52
52
|
* may be a dotted path (`data.portrait`) rather than a sibling key;
|
|
53
53
|
* 3b. for a `data.` source, the bare top-level key that container gathered it
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
*
|
|
57
57
|
* ## Steps 2 and 3 are two declarations, because they are two positions
|
|
58
58
|
*
|
|
59
|
-
* They
|
|
60
|
-
* shared source and the in-block key were the same word. `data:`
|
|
59
|
+
* They are not one: keying both on `name` holds only while the
|
|
60
|
+
* shared source and the in-block key were the same word. `data:` ended
|
|
61
61
|
* that — a shared source is now a path *into* a container, so `data.species`
|
|
62
62
|
* and `species` are two spellings of two different places, and no single value
|
|
63
63
|
* of `name` reached both. `name: "species"` read `hm3.species` and could not
|
|
64
64
|
* see `data.species`; `name: "data.species"` read the shared source and could
|
|
65
65
|
* not see `hm3.species`. Each yielded the field's **default** wherever only the
|
|
66
66
|
* other position was authored — silently, since the field compiles and the
|
|
67
|
-
* document is emitted with the value simply gone
|
|
67
|
+
* document is emitted with the value simply gone.
|
|
68
68
|
*
|
|
69
69
|
* That made every move into `data:` a flag day. Each of this package's other
|
|
70
70
|
* retirements — `package:`, `image`, `archetype`, `relation` — works because
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
* that declares one is mid-sweep by construction, which is what
|
|
78
78
|
* {@link module:engine/field-spec.readsLegacyKey} reports on.
|
|
79
79
|
*
|
|
80
|
-
* ## Step 3 has a retiring position too, and it is derived
|
|
80
|
+
* ## Step 3 has a retiring position too, and it is derived
|
|
81
81
|
*
|
|
82
82
|
* `legacyKey` retires the *in-block* position, and for a while that looked like
|
|
83
83
|
* the whole of what `data:` left behind. It is not. The facts `data:` holds
|
|
84
|
-
* were not invented by it —
|
|
84
|
+
* were not invented by it — the sweep **gathered** them out of the note's open top
|
|
85
85
|
* level, where `portrait:` sat beside `img:` and `shortcode:` — so a field that
|
|
86
86
|
* declares `data.portrait` has two shared spellings to read, not one, and
|
|
87
87
|
* reading only the current one is the same silent miss `legacyKey` exists to
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
* It was not a harmless coincidence either, because step 3 answers **without**
|
|
113
113
|
* applying `field.default` — only step 2 does — so an authored `title: null`
|
|
114
114
|
* reached the field's coercion unguarded and shipped as the literal string
|
|
115
|
-
* `"null"` in fifteen documents
|
|
115
|
+
* `"null"` in fifteen documents.
|
|
116
116
|
*
|
|
117
117
|
* So a field may declare `topLevelMeans`: what the top-level key of that name
|
|
118
118
|
* means *instead*. Declaring it removes the whole shared level — step 3 and the
|
|
@@ -132,9 +132,8 @@
|
|
|
132
132
|
*
|
|
133
133
|
* A block is addressed by name, and the name arrives from the caller — a
|
|
134
134
|
* system's own document-subtype map declares it (`DocumentSubtypeMap.block`).
|
|
135
|
-
* That is the `engine/` ÷ `sohl/` line this package draws everywhere else
|
|
136
|
-
*
|
|
137
|
-
* half.
|
|
135
|
+
* That is the `engine/` ÷ `sohl/` line this package draws everywhere else:
|
|
136
|
+
* note-format knowledge here, game-system knowledge in the system's own half.
|
|
138
137
|
*
|
|
139
138
|
* @module
|
|
140
139
|
*/
|
|
@@ -393,7 +392,7 @@ const DATA_PREFIX = "data.";
|
|
|
393
392
|
/**
|
|
394
393
|
* The bare top-level key a `data:`-sourced field is being swept off — step 3b.
|
|
395
394
|
*
|
|
396
|
-
* `data:`
|
|
395
|
+
* `data:` did not invent the facts it holds; it *gathered* them, out of
|
|
397
396
|
* the note's open top level where each was a sibling of `img` and `shortcode`.
|
|
398
397
|
* So the retiring spelling of `data.portrait` is not a second declaration
|
|
399
398
|
* anyone has to write — it is `portrait`, mechanically, and the same holds for
|
|
@@ -466,8 +465,8 @@ export function resolveFieldValue(field, fm, { block = "sohl" } = {}) {
|
|
|
466
465
|
|
|
467
466
|
// 2. The legacy in-block position, keyed on `legacyKey` — the shared
|
|
468
467
|
// source is a path into `data:` and the in-block key is a bare word, so
|
|
469
|
-
// the two are declared separately
|
|
470
|
-
// writes here today, and will until
|
|
468
|
+
// the two are declared separately. Every note in every tree
|
|
469
|
+
// writes here today, and will until the corpus moves them; dropping it would
|
|
471
470
|
// be a corpus migration disguised as a mechanism change.
|
|
472
471
|
const declared = systemBlock(fm, block);
|
|
473
472
|
const legacyKey = legacyKeyOf(field);
|
|
@@ -482,14 +481,14 @@ export function resolveFieldValue(field, fm, { block = "sohl" } = {}) {
|
|
|
482
481
|
|
|
483
482
|
// 3. The shared property this field declares as its source — unless the
|
|
484
483
|
// field declares that the top-level key of that name means something
|
|
485
|
-
// else, in which case there is no shared position to read
|
|
484
|
+
// else, in which case there is no shared position to read.
|
|
486
485
|
if (field.topLevelMeans === undefined) {
|
|
487
486
|
const shared = getFrontmatter(fm, field.name, undefined);
|
|
488
487
|
if (shared !== undefined) return { value: shared, from: "shared" };
|
|
489
488
|
|
|
490
489
|
// 3b. The bare top-level key the `data:` source was gathered off — the
|
|
491
490
|
// retiring *shared* position, exactly as `legacyKey` is the
|
|
492
|
-
// retiring *in-block* one
|
|
491
|
+
// retiring *in-block* one. Without it a field declaring
|
|
493
492
|
// `data.portrait` cannot see the `portrait:` every tree still
|
|
494
493
|
// writes, and the miss arrives at the caller's `?? default` as an
|
|
495
494
|
// ordinary absence: 646 `sohl-thalorna` beings compiled the generic
|
|
@@ -556,7 +555,7 @@ export function systemDataPaths(data, prefix = "") {
|
|
|
556
555
|
*
|
|
557
556
|
* It stayed invisible while those maps were authored *outside* `<system>.system`
|
|
558
557
|
* and so were never walked. The moment the corpus moves them to the destination
|
|
559
|
-
*
|
|
558
|
+
* every one of them lights up — 62 findings on `sohl-thalorna` alone,
|
|
560
559
|
* none of them a defect.
|
|
561
560
|
*
|
|
562
561
|
* So descent is conditional on the schema declaring something *beneath* the
|
|
@@ -621,7 +620,7 @@ function declaresChildren(declared, path) {
|
|
|
621
620
|
* @param {object} options - Options.
|
|
622
621
|
* @param {Iterable<string>} options.known - The keys this system declares on
|
|
623
622
|
* top of the shared vocabulary: its generators, its toolchain keys, and —
|
|
624
|
-
* until
|
|
623
|
+
* until the corpus moves them — the field names its notes still author in the
|
|
625
624
|
* block.
|
|
626
625
|
* @returns {string[]} The unrecognized keys, in authored order.
|
|
627
626
|
*/
|
package/engine/systems.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* The closed registry of system ids, and the `none` that stands for no system
|
|
16
|
-
* at all
|
|
16
|
+
* at all.
|
|
17
17
|
*
|
|
18
18
|
* A canonical address carries the system in a fixed position —
|
|
19
19
|
* `harnadventures-none-being-grod` — so "which systems exist" is a question the
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
// The one charset, read rather than restated — a system id is an address
|
|
49
49
|
// segment like any other, and a second spelling of the pattern is how the
|
|
50
|
-
// disagreements
|
|
50
|
+
// disagreements between them arise.
|
|
51
51
|
import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./address-charset.mjs";
|
|
52
52
|
|
|
53
53
|
/**
|
package/engine/web-wikilinks.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* `[[type-shortcode#slug|Text]]` → `[Text](/section/slug/#slug)`
|
|
23
23
|
* `[[#slug|Text]]` → `[Text](#slug)`
|
|
24
24
|
*
|
|
25
|
-
* **Every link is an address and carries a label
|
|
25
|
+
* **Every link is an address and carries a label**. One written without
|
|
26
26
|
* a label addresses nothing and is reported — see
|
|
27
27
|
* {@link unlabelledLinkMessage}, which states the rule for this build and the
|
|
28
28
|
* pack build together.
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
* Lives here rather than in a consumer so every package resolves a link the
|
|
35
35
|
* same way. `sohl-thalorna` carried a forked copy of this as
|
|
36
36
|
* `utils/site-wikilinks.mjs`, already 3 KB adrift from the original, which is
|
|
37
|
-
* exactly the drift one rule with two implementations produces
|
|
37
|
+
* exactly the drift one rule with two implementations produces.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
// How an address *parses* is the pack build's own rule, so the two builds
|
|
41
41
|
// cannot drift apart on it: they disagreed once over the unlabelled hyphen
|
|
42
42
|
// form, which the packs showed as a raw shortcode and the knowledgebase as a
|
|
43
|
-
// name
|
|
43
|
+
// name.
|
|
44
44
|
import { readQualifier } from "./wikilinks.mjs";
|
|
45
45
|
import { replaceOutsideCode } from "./code-fences.mjs";
|
|
46
46
|
// The canonical `package-system-type-shortcode` key, so a package-qualified
|
|
47
47
|
// address is matched the way a vendored manifest publishes it — by the
|
|
48
|
-
// segments the target supplies, with the system wildcarded unless stated
|
|
48
|
+
// segments the target supplies, with the system wildcarded unless stated.
|
|
49
49
|
import { canonicalKey, expandAddress, readCanonicalKey } from "./content-address.mjs";
|
|
50
50
|
import { NO_SYSTEM } from "./systems.mjs";
|
|
51
51
|
// The one rule about a link's shape both builds share: it carries a label, and
|
|
52
|
-
// {@link unlabelledLinkMessage} is the one place that says so
|
|
52
|
+
// {@link unlabelledLinkMessage} is the one place that says so.
|
|
53
53
|
import { unlabelledLinkMessage } from "./wikilink-syntax.mjs";
|
|
54
54
|
// One slug rule for the whole build — see `./content-slug.mjs`. This module
|
|
55
55
|
// carried a copy that dropped non-ASCII letters rather than transliterating
|
|
@@ -68,7 +68,7 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
|
|
|
68
68
|
* parse as an address at all.
|
|
69
69
|
*
|
|
70
70
|
* The KB index is keyed by the canonical `type/shortcode`, so a target written
|
|
71
|
-
* in the hyphen separator — which is what the content tree authors
|
|
71
|
+
* in the hyphen separator — which is what the content tree authors —
|
|
72
72
|
* has to be rewritten to it before lookup. The target is read by the pack
|
|
73
73
|
* build's own {@link readQualifier}, so recognising an address and resolving
|
|
74
74
|
* one can never disagree: the two separators and the optional leading package
|
|
@@ -77,7 +77,7 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
|
|
|
77
77
|
* It takes the **parsed** qualifier rather than the raw target because the
|
|
78
78
|
* caller needs the parse for a second purpose: `unknown-type` and
|
|
79
79
|
* `not-an-address` are different findings with different fixes, and only the
|
|
80
|
-
* `reason` tells them apart
|
|
80
|
+
* `reason` tells them apart. Reading the target twice would let the two
|
|
81
81
|
* readings drift.
|
|
82
82
|
*
|
|
83
83
|
* The build indexes an item note under both `skill/climb` and `docskill/climb`,
|
|
@@ -88,7 +88,7 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
|
|
|
88
88
|
*/
|
|
89
89
|
function lookupRead(index, read, contentPackage) {
|
|
90
90
|
if (!read || read.reason) return undefined;
|
|
91
|
-
// Every omitted segment defaults from where the link is written
|
|
91
|
+
// Every omitted segment defaults from where the link is written —
|
|
92
92
|
// package from the citing package, system from the block, which on a page
|
|
93
93
|
// body is `none` — so the target expands to exactly one canonical address
|
|
94
94
|
// and this is a plain lookup. No filter, no single-hit rule, and no
|
|
@@ -104,7 +104,7 @@ function lookupRead(index, read, contentPackage) {
|
|
|
104
104
|
// A local target may also be keyed by its short form. On the KB that is not
|
|
105
105
|
// a second answer: an item note renders as **one page** which is its own
|
|
106
106
|
// documentation, so `skill/climb`, `docskill/climb` and both canonical
|
|
107
|
-
// addresses are all the same value
|
|
107
|
+
// addresses are all the same value — the fallback cannot pick
|
|
108
108
|
// differently, only earlier. It stays because an index built before the
|
|
109
109
|
// canonical documentation key was added still carries the short one, and
|
|
110
110
|
// because a page collision here is caught by the site index's own
|
|
@@ -127,10 +127,10 @@ function lookupRead(index, read, contentPackage) {
|
|
|
127
127
|
* This is deliberately identical to the pack compiler's own `unresolvedLink`,
|
|
128
128
|
* down to the class name and the `title` wording. One authored link renders on
|
|
129
129
|
* two surfaces, and the two builds have drifted before over exactly this kind
|
|
130
|
-
* of detail
|
|
130
|
+
* of detail — matching markup is what keeps a reader's cue the same in
|
|
131
131
|
* a journal and on the page. Duplicated rather than imported only because the
|
|
132
132
|
* function is not exported from `@heroiclands/package-build`; hoisting it there
|
|
133
|
-
* is
|
|
133
|
+
* is.
|
|
134
134
|
*
|
|
135
135
|
* The knowledgebase renders with `unsafe = true` (`kb/hugo.toml`), so raw HTML
|
|
136
136
|
* in generated markdown reaches the page. That makes escaping obligatory: this
|
|
@@ -154,7 +154,7 @@ function unresolvedLink(text, target) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* How a link to a **draft** note renders
|
|
157
|
+
* How a link to a **draft** note renders.
|
|
158
158
|
*
|
|
159
159
|
* A note tagged `draft` exists so a link into it is not dead, and nothing more.
|
|
160
160
|
* Unmarked, a reader follows a promising link into an empty page and an author
|
|
@@ -173,8 +173,8 @@ function unresolvedLink(text, target) {
|
|
|
173
173
|
* **Byte-identical with the pack build's copy** in `wikilinks.mjs`, down to the
|
|
174
174
|
* class name and the `title` wording — one authored link renders on two
|
|
175
175
|
* surfaces, and the two builds have drifted before over exactly this kind of
|
|
176
|
-
* detail
|
|
177
|
-
* {@link unresolvedLink} is; hoisting both is
|
|
176
|
+
* detail. Duplicated rather than imported for the same reason
|
|
177
|
+
* {@link unresolvedLink} is; hoisting both is.
|
|
178
178
|
*
|
|
179
179
|
* The argument is already-built markup and is deliberately not escaped; the
|
|
180
180
|
* *authored* text inside it was escaped, or made into a link, by the caller.
|
|
@@ -198,7 +198,7 @@ function draftLink(inner) {
|
|
|
198
198
|
const FRONTMATTER_WIKILINK = new RegExp(WIKILINK.source, "g");
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
* Every wikilink authored inside a frontmatter value
|
|
201
|
+
* Every wikilink authored inside a frontmatter value.
|
|
202
202
|
*
|
|
203
203
|
* Wikilinks are resolved in a note's **body** — by {@link resolveWebWikilinks}
|
|
204
204
|
* here, and by the pack compilers' `convertWikilinks` for Foundry. Frontmatter
|
|
@@ -267,19 +267,19 @@ function isPlainMap(value) {
|
|
|
267
267
|
* `section/slug`), then in the vendored `ctx.foreign` manifests. A target is a
|
|
268
268
|
* *partial* address: an omitted package means this package, an omitted system
|
|
269
269
|
* is a wildcard, and exactly one match resolves — two are `ambiguous`. A link written
|
|
270
|
-
* without a label addresses nothing at all and is reported as such
|
|
270
|
+
* without a label addresses nothing at all and is reported as such —
|
|
271
271
|
* there is no second namespace left for it to name.
|
|
272
272
|
*
|
|
273
273
|
* Only a slash-qualified target reaches the raw key, which is what keeps
|
|
274
274
|
* `section/slug` addressable without a page's own slug answering for it.
|
|
275
275
|
*
|
|
276
|
-
* **Every target that resolves nowhere fails the build
|
|
276
|
+
* **Every target that resolves nowhere fails the build**, and is
|
|
277
277
|
* classified into the vocabulary all three resolvers share — `unlabelled`,
|
|
278
278
|
* `not-an-address`, `unknown-type`, `ambiguous`, `unresolved`. Failures are
|
|
279
279
|
* collected in `ctx.errors`, each carrying the authored `link` and its
|
|
280
280
|
* `occurrence` so a caller can report the line and column it sits on.
|
|
281
281
|
*
|
|
282
|
-
* There
|
|
282
|
+
* There is deliberately no exception letting a hyphen-form address through while
|
|
283
283
|
* any linkable package had no vendored manifest, since a real cross-package
|
|
284
284
|
* reference and a typo look identical from here. The pack compilers and the
|
|
285
285
|
* link checker never made that allowance, so its only surviving effect was to
|
|
@@ -287,13 +287,13 @@ function isPlainMap(value) {
|
|
|
287
287
|
* instead.
|
|
288
288
|
*
|
|
289
289
|
* A target that resolves nowhere still renders through {@link unresolvedLink}
|
|
290
|
-
* rather than as bare prose
|
|
290
|
+
* rather than as bare prose: the author's text is kept, marked so a
|
|
291
291
|
* reader can see a link was intended. The marking and the failure are separate
|
|
292
292
|
* jobs and always were — the mark is for whoever reads the page a *previous*
|
|
293
293
|
* build emitted, the failure is for the author of this one.
|
|
294
294
|
*
|
|
295
295
|
* A target that **resolved** to an entry with no page is not this case and is
|
|
296
|
-
* not marked: a pack-only package
|
|
296
|
+
* not marked: a pack-only package publishes Foundry addresses and no
|
|
297
297
|
* web pages, so the author wrote a real address and there is simply nothing to
|
|
298
298
|
* link to.
|
|
299
299
|
*
|
|
@@ -302,7 +302,7 @@ function isPlainMap(value) {
|
|
|
302
302
|
* foreign, type, errors, src, file }`.
|
|
303
303
|
* `packages` is every package an address may name, without which the leading
|
|
304
304
|
* package segment of a canonical address reads as an unknown type; `foreign`
|
|
305
|
-
* is the cross-package manifest index
|
|
305
|
+
* is the cross-package manifest index. `src` is the page's display
|
|
306
306
|
* path and `file` the source file a diagnostic should name — absent, `src`
|
|
307
307
|
* stands in.
|
|
308
308
|
* @returns {string} The body with wikilinks rewritten.
|
|
@@ -311,7 +311,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
311
311
|
// How many times each authored link has been seen, so two identical links
|
|
312
312
|
// on one page are located at their own positions in the source file — the
|
|
313
313
|
// same counting the checker does, and what turns a finding into a
|
|
314
|
-
// `file:line:column:` diagnostic rather than a note-wide one
|
|
314
|
+
// `file:line:column:` diagnostic rather than a note-wide one.
|
|
315
315
|
const seen = new Map();
|
|
316
316
|
/**
|
|
317
317
|
* Records a finding, and returns the marked-up link it renders as.
|
|
@@ -338,16 +338,16 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
338
338
|
};
|
|
339
339
|
|
|
340
340
|
// Code is verbatim: a `[[…]]` inside a code fence, an indented block or an
|
|
341
|
-
// inline span is source text, not a link
|
|
341
|
+
// inline span is source text, not a link.
|
|
342
342
|
return replaceOutsideCode(body, WIKILINK, (all, rawInner) => {
|
|
343
343
|
const parsed = parseWikilink(rawInner);
|
|
344
344
|
const { target, anchor, display } = parsed;
|
|
345
345
|
// An empty label is not a label: `[[x|]]` addresses the target and
|
|
346
|
-
// shows its name, so `""` falls through to the same place `null` does
|
|
347
|
-
//
|
|
346
|
+
// shows its name, so `""` falls through to the same place `null` does.
|
|
347
|
+
// One reading, from {@link authoredLabel}.
|
|
348
348
|
const label = authoredLabel({ display });
|
|
349
349
|
|
|
350
|
-
// **Every link carries a label
|
|
350
|
+
// **Every link carries a label**. Without one there is nothing
|
|
351
351
|
// to resolve against — the alias namespace a bare `[[Text]]` named is
|
|
352
352
|
// retired — and nothing to show either, a shortcode being an address
|
|
353
353
|
// rather than prose. Reported before the same-page form, because the
|
|
@@ -362,7 +362,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
362
362
|
return `[${label ?? anchor}](#${slugify(anchor)})`;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
// The canonical separator
|
|
365
|
+
// The canonical separator has to be resolved, not merely
|
|
366
366
|
// recognised. `null` here means the target is not an address at all,
|
|
367
367
|
// which is a defect: there is no other namespace to try.
|
|
368
368
|
const read = readQualifier(target, ctx.contentTypes ?? new Set(), ctx.packages);
|
|
@@ -374,10 +374,10 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
374
374
|
// a page's bare slug cannot answer for an address.
|
|
375
375
|
(rawKey.includes("/") ? ctx.index.get(rawKey) : undefined) ??
|
|
376
376
|
// A manifest entry carries the same `{ url, name }` shape as a
|
|
377
|
-
// local one
|
|
377
|
+
// local one, so a cross-package hit needs no special case
|
|
378
378
|
// below. Local wins: a live build is authoritative and a vendored
|
|
379
379
|
// manifest can only be staler.
|
|
380
|
-
// A short form names *this* package
|
|
380
|
+
// A short form names *this* package, so it never reaches a
|
|
381
381
|
// vendored manifest; only a fully qualified address does.
|
|
382
382
|
(ctx.foreign && read?.package ?
|
|
383
383
|
lookupRead(ctx.foreign, read, ctx.contentPackage)
|
|
@@ -387,8 +387,8 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
387
387
|
// shortcode is not display text), so the document's **current**
|
|
388
388
|
// name stands in and a rename shows at every citation.
|
|
389
389
|
const text = label ?? hit.name;
|
|
390
|
-
// A pack-only package publishes Foundry addresses and no pages
|
|
391
|
-
//
|
|
390
|
+
// A pack-only package publishes Foundry addresses and no pages,
|
|
391
|
+
// so its entries carry no `path` and resolve to no URL.
|
|
392
392
|
// The address is real — this is not a typo and must not fail the
|
|
393
393
|
// build — but there is nothing on the web to point at, so the
|
|
394
394
|
// reader gets the text and no href. Emitting the href anyway is
|
|
@@ -396,8 +396,8 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
396
396
|
// as a link and goes nowhere.
|
|
397
397
|
const link =
|
|
398
398
|
hit.url ? `[${text}](${anchor ? `${hit.url}#${slugify(anchor)}` : hit.url})` : text;
|
|
399
|
-
// A link into a note that exists but is not written renders marked
|
|
400
|
-
//
|
|
399
|
+
// A link into a note that exists but is not written renders marked.
|
|
400
|
+
// Presentation only — the href above is unchanged, and a
|
|
401
401
|
// `[[#anchor]]` self-link is not marked because the reader is
|
|
402
402
|
// already on the page it would be telling them about.
|
|
403
403
|
return hit.draft ? draftLink(link) : link;
|
|
@@ -413,7 +413,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
413
413
|
// it parses as an address.)
|
|
414
414
|
const siteAddress = prefix !== null && ctx.sections.has(prefix);
|
|
415
415
|
|
|
416
|
-
// **An address resolving nowhere is a failure, unconditionally
|
|
416
|
+
// **An address resolving nowhere is a failure, unconditionally**.
|
|
417
417
|
//
|
|
418
418
|
// It was gated on a manifest-completeness check — while any linkable package was
|
|
419
419
|
// invisible here, `Rules/Bestiary.md` addressing `being-grkrahk` in the
|
|
@@ -443,7 +443,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
443
443
|
: read?.reason === "unknown-type" ? "unknown-type"
|
|
444
444
|
// Every link is an address, and this is not one. Distinct from
|
|
445
445
|
// a dead address, because the fix is different: a name has to
|
|
446
|
-
// become an address, not be corrected
|
|
446
|
+
// become an address, not be corrected.
|
|
447
447
|
: "not-an-address";
|
|
448
448
|
|
|
449
449
|
// Whether or not it failed the build, the link renders marked: the
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* The web side's pattern omitted `\n` from the excluded set, so an unclosed
|
|
30
30
|
* bracket consumed everything up to the next `]]` anywhere in the document —
|
|
31
31
|
* the same shape of corruption a hand-rolled code-fence regex caused on the one
|
|
32
|
-
* page whose subject is link syntax
|
|
32
|
+
* page whose subject is link syntax. It was also internally
|
|
33
33
|
* inconsistent: its *frontmatter* scan excluded newlines while its body scan did
|
|
34
34
|
* not.
|
|
35
35
|
*
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* ({@link LINK_FINDING_REASONS}) and the message each one reports through
|
|
46
46
|
* ({@link linkFindingMessage}). Three builds read one authored link; an author
|
|
47
47
|
* meets whichever ran first, and a consumer switching on a `reason` should not
|
|
48
|
-
* be switching on which build produced it
|
|
48
|
+
* be switching on which build produced it.
|
|
49
49
|
*
|
|
50
50
|
* @module
|
|
51
51
|
*/
|
|
@@ -72,7 +72,7 @@ export const WIKILINK = /\[\[([^\]\n]+)\]\]/g;
|
|
|
72
72
|
* @property {string|null} display - The text after `|`, or `null` when the link
|
|
73
73
|
* is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
|
|
74
74
|
* @property {boolean} labelled - Whether a `|` was present at all. A link
|
|
75
|
-
* without one addresses nothing and is a finding
|
|
75
|
+
* without one addresses nothing and is a finding — see
|
|
76
76
|
* {@link unlabelledLinkMessage} — so every reader has to be able to ask.
|
|
77
77
|
*/
|
|
78
78
|
|
|
@@ -111,12 +111,12 @@ export function parseWikilink(rawInner) {
|
|
|
111
111
|
* `display: null` does. The two are still distinguishable through
|
|
112
112
|
* {@link ParsedWikilink.labelled}, which is the thing that genuinely differs:
|
|
113
113
|
* `[[x|]]` is labelled and `[[x]]` is not, and only the first addresses
|
|
114
|
-
* anything
|
|
114
|
+
* anything.
|
|
115
115
|
*
|
|
116
116
|
* Stated here because the two resolvers had already drawn the line in two
|
|
117
117
|
* places and drawn it differently: the packs tested falsiness and were right,
|
|
118
118
|
* the web tested `??` — which falls through on `null` only — and emitted
|
|
119
|
-
* `[](/url/)`, a link with no clickable text, through every build
|
|
119
|
+
* `[](/url/)`, a link with no clickable text, through every build. That
|
|
120
120
|
* is the same drift this module exists to prevent, in the case its own
|
|
121
121
|
* {@link ParsedWikilink} docstring calls out. One reading, one place.
|
|
122
122
|
*
|
|
@@ -136,12 +136,12 @@ export function authoredLabel({ display }) {
|
|
|
136
136
|
* the form to write rather than a value to correct: there is no value that
|
|
137
137
|
* makes an unlabelled link resolve.
|
|
138
138
|
*
|
|
139
|
-
* **Why there is nothing left for a bare link to mean
|
|
139
|
+
* **Why there is nothing left for a bare link to mean**. The pipe used
|
|
140
140
|
* to select between two namespaces — address and alias — and the alias one was
|
|
141
141
|
* empty in practice: across 8,305 wikilinks in three content trees, not one
|
|
142
142
|
* bare `[[Alias]]` resolved to a note. What the index it looked up in *did* do
|
|
143
143
|
* was fold every note's `name.full` into itself, which forbade two notes of a
|
|
144
|
-
* type from sharing a display name
|
|
144
|
+
* type from sharing a display name. So the namespace is gone, every
|
|
145
145
|
* link is an address, and an address needs the pipe that says so.
|
|
146
146
|
*
|
|
147
147
|
* The **link part may still be an anchor**: `[[#slug|Text]]` addresses a
|
|
@@ -160,16 +160,16 @@ export function unlabelledLinkMessage(target) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
* Every way a link can fail, named once for all three resolvers
|
|
163
|
+
* Every way a link can fail, named once for all three resolvers.
|
|
164
164
|
*
|
|
165
165
|
* A link is read in three places — the checker (`content-links.mjs`), the pack
|
|
166
166
|
* compilers (`wikilinks.mjs`) and the web resolver (`web-wikilinks.mjs`) — and
|
|
167
|
-
* each
|
|
167
|
+
* each would otherwise name the failures in its own words. `unknown` in one is
|
|
168
168
|
* `unresolved` in another and `broken type/shortcode` in the third, so a
|
|
169
169
|
* consumer switching on a `reason` was switching on which build had produced
|
|
170
170
|
* it. The set is closed and lives here, beside the syntax the three share.
|
|
171
171
|
*
|
|
172
|
-
* - `unlabelled` — no `|`, so the link addresses nothing
|
|
172
|
+
* - `unlabelled` — no `|`, so the link addresses nothing.
|
|
173
173
|
* - `not-an-address` — labelled, but the target does not parse as an address.
|
|
174
174
|
* - `unknown-type` — definitely qualified, but names no type this build knows.
|
|
175
175
|
* - `unresolved` — parses as an address, and nothing publishes it.
|
|
@@ -198,12 +198,12 @@ export const LINK_FINDING_REASONS = Object.freeze(
|
|
|
198
198
|
* package publishing it is not a declared dependency — or is one whose index
|
|
199
199
|
* has not been fetched — and the link itself looks identical in every case.
|
|
200
200
|
*
|
|
201
|
-
*
|
|
201
|
+
* As a **warning** in the checker and, in the site build, nothing
|
|
202
202
|
* at all until every linkable package's manifest was accounted for — on the
|
|
203
203
|
* reasoning that a bare `[[Name]]` might be a placeholder for a note nobody had
|
|
204
|
-
* written yet. That reasoning was a property of the bare form, which is retired
|
|
205
|
-
*
|
|
206
|
-
* that exists and resolves and renders marked
|
|
204
|
+
* written yet. That reasoning was a property of the bare form, which is retired;
|
|
205
|
+
* the intent behind it now has a real spelling, a `draft`-tagged note
|
|
206
|
+
* that exists and resolves and renders marked. So an address naming no
|
|
207
207
|
* note is a typo or an omission, both want fixing, and all three builds say so.
|
|
208
208
|
*
|
|
209
209
|
* @param {string} target - The address as authored, named in the message.
|
|
@@ -226,9 +226,9 @@ export function unresolvedAddressMessage(target) {
|
|
|
226
226
|
* message names the claimants so the author can choose between them without
|
|
227
227
|
* going looking.
|
|
228
228
|
*
|
|
229
|
-
* **The correction is the canonical form**, all four segments
|
|
229
|
+
* **The correction is the canonical form**, all four segments. Omission
|
|
230
230
|
* runs strictly left to right, so there is no `package-type-shortcode` to offer
|
|
231
|
-
* — naming a package means naming the system before the type. That
|
|
231
|
+
* — naming a package means naming the system before the type. That would be
|
|
232
232
|
* the correction here, back when a written target could state three segments at
|
|
233
233
|
* most; the grammar is positional now, and a three-segment target names a
|
|
234
234
|
* *system*, not a package.
|