@heroiclands/package-build 8.1.0 → 10.0.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 +748 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +479 -123
- package/bin/package-build.mjs +27 -69
- package/bin/report.mjs +1 -2
- package/bundle.mjs +2 -10
- package/config.mjs +31 -106
- package/container.mjs +13 -57
- package/content-config.mjs +300 -188
- package/coverage.mjs +14 -55
- package/deploy.mjs +4 -13
- package/docs/content-format.md +1418 -0
- package/e2e.mjs +16 -55
- package/engine/address-charset.mjs +62 -0
- package/engine/address-diff.mjs +1 -4
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +203 -31
- package/engine/code-fences.mjs +4 -13
- package/engine/compendiums.mjs +13 -37
- package/engine/content-address.mjs +6 -10
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +144 -99
- package/engine/content-lint.mjs +12 -16
- package/engine/content-slug.mjs +2 -6
- package/engine/content-tables.mjs +26 -79
- package/engine/diagnostics.mjs +37 -15
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-reference.mjs +6 -20
- package/engine/field-spec.mjs +49 -45
- package/engine/foreign-catalog.mjs +7 -22
- package/engine/foreign-manifests.mjs +1 -4
- package/engine/frontmatter-lint.mjs +347 -43
- package/engine/frontmatter.mjs +3 -8
- package/engine/generate.mjs +36 -20
- package/engine/helpers.mjs +54 -81
- package/engine/homepage.mjs +4 -15
- package/engine/ids.mjs +21 -12
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -9
- package/engine/journals.mjs +4 -14
- package/engine/kb-manifest.mjs +41 -24
- package/engine/macros.mjs +2 -10
- package/engine/manifest-emit.mjs +6 -17
- package/engine/map-notes.mjs +53 -87
- package/engine/note-claims.mjs +383 -0
- package/engine/note-package.mjs +1 -4
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +56 -60
- package/engine/pack-router.mjs +18 -8
- package/engine/prose-config.mjs +20 -4
- package/engine/prose-lint.mjs +61 -17
- package/engine/region-events.mjs +1 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scene-levels.mjs +8 -22
- package/engine/scenes.mjs +31 -47
- package/engine/schema-check.mjs +348 -7
- package/engine/schema-extract.mjs +11 -39
- package/engine/site-build.mjs +13 -38
- package/engine/site-index.mjs +40 -35
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +115 -92
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +147 -183
- package/index.mjs +1 -5
- package/lang.mjs +1 -3
- package/manifest.mjs +10 -37
- package/markdownlint-config.mjs +1 -5
- package/package.json +6 -2
- package/sohl/actors.mjs +251 -68
- package/sohl/being-info.mjs +3 -6
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +4 -6
- package/sohl/item-builders.mjs +1 -3
- package/sohl/item-fields.mjs +16 -34
- package/sohl/items.mjs +111 -17
- package/sohl/kb-passes.mjs +29 -39
- package/sohl/note-schemas.mjs +11 -7
- package/sohl/skill-base.mjs +7 -23
- package/stage.mjs +3 -13
- package/templates.mjs +4 -15
- package/types/bundle.d.mts +1 -1
- package/types/container.d.mts +2 -2
- package/types/content-config.d.mts +48 -4
- package/types/coverage.d.mts +1 -1
- package/types/e2e.d.mts +4 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +15 -2
- package/types/engine/helpers.d.mts +22 -14
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/schema-extract.d.mts +1 -1
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/manifest.d.mts +1 -1
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
package/engine/web-wikilinks.mjs
CHANGED
|
@@ -17,10 +17,15 @@
|
|
|
17
17
|
* The same authored links the pack compilers turn into Foundry `@UUID` enrichers
|
|
18
18
|
* (see `./wikilinks.mjs`) become site-local hrefs here:
|
|
19
19
|
*
|
|
20
|
-
* `[[type
|
|
20
|
+
* `[[type-shortcode|Text]]` → `[Text](/section/slug/)`
|
|
21
|
+
* `[[type-shortcode|]]` → the same, showing the target's own name
|
|
21
22
|
* `[[Text]]` → the same, via a type-scoped alias
|
|
22
|
-
* `[[type
|
|
23
|
-
* `[[#slug|Text]]`
|
|
23
|
+
* `[[type-shortcode#slug|Text]]` → `[Text](/section/slug/#slug)`
|
|
24
|
+
* `[[#slug|Text]]` → `[Text](#slug)`
|
|
25
|
+
*
|
|
26
|
+
* **The pipe decides which namespace a target belongs to** (#131), with no
|
|
27
|
+
* fallback either way — see {@link resolvesAsAddress}, which states the rule
|
|
28
|
+
* for this build and the pack build together.
|
|
24
29
|
*
|
|
25
30
|
* The KB *section* is not always the type: prose pages (`type: doc`) route by
|
|
26
31
|
* their `category`, so `doc/quickstart` lands on `/user-guide/sohl-quickstart/`.
|
|
@@ -32,12 +37,21 @@
|
|
|
32
37
|
* exactly the drift one rule with two implementations produces (#20).
|
|
33
38
|
*/
|
|
34
39
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
40
|
+
// How an address *parses* is the pack build's own rule, so the two builds
|
|
41
|
+
// cannot drift apart on it: they disagreed once over the unlabelled hyphen
|
|
42
|
+
// form, which the packs showed as a raw shortcode and the knowledgebase as a
|
|
43
|
+
// name (#1409).
|
|
39
44
|
import { readQualifier } from "./wikilinks.mjs";
|
|
40
45
|
import { replaceOutsideCode } from "./code-fences.mjs";
|
|
46
|
+
// The canonical `package-type-shortcode` key, so a package-qualified address
|
|
47
|
+
// is looked up the way a vendored manifest publishes it.
|
|
48
|
+
import { canonicalKey } from "./kb-manifest.mjs";
|
|
49
|
+
// The alias half of the two namespaces — the key rule, shared with the pack
|
|
50
|
+
// build and the link checker (#131).
|
|
51
|
+
import { aliasKey } from "./alias-index.mjs";
|
|
52
|
+
// Which namespace a target belongs to. The pipe decides, and this is the one
|
|
53
|
+
// place that says so.
|
|
54
|
+
import { resolvesAsAddress } from "./wikilink-syntax.mjs";
|
|
41
55
|
// One slug rule for the whole build — see `./content-slug.mjs`. This module
|
|
42
56
|
// carried a copy that dropped non-ASCII letters rather than transliterating
|
|
43
57
|
// them, so a link to a heading named `Kûrbúl Helm` pointed at `#k-rb-l-helm`.
|
|
@@ -46,53 +60,38 @@ import { slugify } from "./content-slug.mjs";
|
|
|
46
60
|
// Re-exported so a site build keeps one import path for the whole of link
|
|
47
61
|
// resolution: the same rule that names a page also names an anchor within it.
|
|
48
62
|
export { slugify };
|
|
49
|
-
import {
|
|
50
|
-
authoredLabel,
|
|
51
|
-
WIKILINK,
|
|
52
|
-
isSamePage,
|
|
53
|
-
parseWikilink,
|
|
54
|
-
} from "./wikilink-syntax.mjs";
|
|
63
|
+
import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-syntax.mjs";
|
|
55
64
|
|
|
56
65
|
/** KB heading/anchor slug: lowercase, non-alphanumerics to single hyphens. */
|
|
57
66
|
|
|
58
67
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* Delegates to the pack build's {@link readQualifier} so one rule serves both
|
|
63
|
-
* builds. A `reason` is as much an address as a resolved qualifier is — the
|
|
64
|
-
* target is qualified either way, it just names no known type — and the caller
|
|
65
|
-
* only ever asks this of a target that already resolved.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} target - The link target, anchor already removed.
|
|
68
|
-
* @param {Set<string>} [contentTypes] - Every content type the KB build saw.
|
|
69
|
-
* @returns {boolean} `true` when the target is an address.
|
|
70
|
-
*/
|
|
71
|
-
function isAddress(target, contentTypes) {
|
|
72
|
-
return readQualifier(target, contentTypes ?? new Set()) !== null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* The `type/shortcode` index key a qualified target resolves to, or `null`.
|
|
68
|
+
* The index key a **piped** target resolves to, or `null` when it does not
|
|
69
|
+
* parse as an address at all.
|
|
77
70
|
*
|
|
78
71
|
* The KB index is keyed by the canonical `type/shortcode`, so a target written
|
|
79
|
-
* in the hyphen separator — which is what the
|
|
80
|
-
* rewritten to it before lookup. Uses the
|
|
81
|
-
* {@link
|
|
82
|
-
* disagree: the
|
|
83
|
-
*
|
|
72
|
+
* in the hyphen separator — which is what the content tree authors (#1398) —
|
|
73
|
+
* has to be rewritten to it before lookup. Uses the pack build's own
|
|
74
|
+
* {@link readQualifier}, so recognising an address and resolving one can never
|
|
75
|
+
* disagree: the two separators and the optional leading package segment are
|
|
76
|
+
* stated once, there.
|
|
84
77
|
*
|
|
85
78
|
* The build indexes an item note under both `skill/climb` and `docskill/climb`,
|
|
86
79
|
* and `contentTypes` carries both qualifiers, so either form finds the page.
|
|
87
80
|
*
|
|
88
81
|
* @param {string} target - The link target, anchor already removed.
|
|
89
82
|
* @param {Set<string>} [contentTypes] - Every content type the KB build saw.
|
|
90
|
-
* @
|
|
83
|
+
* @param {Set<string>} [packages] - Every package an address may name.
|
|
84
|
+
* @returns {string | null} The index key, or `null` when not an address.
|
|
91
85
|
*/
|
|
92
|
-
function qualifiedKey(target, contentTypes) {
|
|
93
|
-
const read = readQualifier(target, contentTypes ?? new Set());
|
|
86
|
+
function qualifiedKey(target, contentTypes, packages) {
|
|
87
|
+
const read = readQualifier(target, contentTypes ?? new Set(), packages);
|
|
94
88
|
if (!read || read.reason) return null;
|
|
95
|
-
|
|
89
|
+
// A package-qualified address keeps its package: the canonical key is what
|
|
90
|
+
// a vendored manifest publishes, and dropping the segment would resolve
|
|
91
|
+
// another package's address against this one's short key.
|
|
92
|
+
return read.package ?
|
|
93
|
+
canonicalKey(read.package, read.type, read.shortcode)
|
|
94
|
+
: `${read.type}/${read.shortcode}`.toLowerCase();
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
/**
|
|
@@ -208,18 +207,27 @@ function isPlainMap(value) {
|
|
|
208
207
|
/**
|
|
209
208
|
* Rewrites the wikilinks in a markdown body as KB-local markdown links.
|
|
210
209
|
*
|
|
211
|
-
* A target is looked up case-insensitively
|
|
212
|
-
*
|
|
213
|
-
* directory and `category` play no part — then in the KB-wide `ctx.index` (keyed
|
|
214
|
-
* by the unambiguous `section/slug` and `type/shortcode`, plus name/filename/slug
|
|
215
|
-
* fallbacks).
|
|
210
|
+
* A target is looked up case-insensitively in **one** of two namespaces, and
|
|
211
|
+
* the pipe chooses which (#131):
|
|
216
212
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* in
|
|
213
|
+
* - **Unpiped** — an alias scoped to the source's own **type**
|
|
214
|
+
* (`ctx.typeAlias`, keyed `type|alias`). A note's directory and `category`
|
|
215
|
+
* play no part.
|
|
216
|
+
* - **Piped** — an address, parsed by {@link readQualifier} and looked up in
|
|
217
|
+
* the KB-wide `ctx.index` (the canonical `package-type-shortcode`,
|
|
218
|
+
* `type/shortcode`, and the site's own `section/slug`), then in the vendored
|
|
219
|
+
* `ctx.foreign` manifests.
|
|
220
|
+
*
|
|
221
|
+
* Neither falls back to the other, so the name/basename/slug fallbacks that
|
|
222
|
+
* share `ctx.index` no longer answer for an address: only a slash-qualified
|
|
223
|
+
* target reaches the raw key, which is what keeps `section/slug` addressable.
|
|
224
|
+
*
|
|
225
|
+
* An unresolved target fails the build when it is a genuine intra-KB problem —
|
|
226
|
+
* an ambiguous alias, a qualified `prefix/key` whose prefix is a real KB
|
|
227
|
+
* section or content directory, or a **piped** target that is not an address
|
|
228
|
+
* at all. Anything else is treated as an external reference — until every
|
|
229
|
+
* package's manifest is present, after which any address resolving nowhere
|
|
230
|
+
* fails too. Failures are collected in `ctx.errors`.
|
|
223
231
|
*
|
|
224
232
|
* Whether or not it fails the build, a target that resolves nowhere renders
|
|
225
233
|
* through {@link unresolvedLink} rather than as bare prose (#1665): the author's
|
|
@@ -234,7 +242,9 @@ function isPlainMap(value) {
|
|
|
234
242
|
*
|
|
235
243
|
* @param {string} body - The markdown body.
|
|
236
244
|
* @param {object} ctx - `{ index, typeAlias, collide, typeCollide, sections,
|
|
237
|
-
* contentTypes, foreign, manifestsComplete, type, errors, src }`.
|
|
245
|
+
* contentTypes, packages, foreign, manifestsComplete, type, errors, src }`.
|
|
246
|
+
* `packages` is every package an address may name, without which the leading
|
|
247
|
+
* package segment of a canonical address reads as an unknown type; `foreign`
|
|
238
248
|
* is the cross-package manifest index (#1446); `manifestsComplete` says
|
|
239
249
|
* whether every linkable package is accounted for. Together they decide
|
|
240
250
|
* whether an unresolved address is a typo or a package merely absent.
|
|
@@ -244,7 +254,8 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
244
254
|
// Code is verbatim: a `[[…]]` inside a code fence, an indented block or an
|
|
245
255
|
// inline span is source text, not a link (#1505).
|
|
246
256
|
return replaceOutsideCode(body, WIKILINK, (_m, rawInner) => {
|
|
247
|
-
const
|
|
257
|
+
const parsed = parseWikilink(rawInner);
|
|
258
|
+
const { target, anchor, display } = parsed;
|
|
248
259
|
// An empty label is not a label: `[[x|]]` addresses the target and
|
|
249
260
|
// shows its name, so `""` falls through to the same place `null` does
|
|
250
261
|
// (#113). One reading, from {@link authoredLabel}.
|
|
@@ -255,34 +266,41 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
255
266
|
return `[${label ?? anchor}](#${slugify(anchor)})`;
|
|
256
267
|
}
|
|
257
268
|
|
|
258
|
-
|
|
259
|
-
|
|
269
|
+
// **The pipe chooses the namespace, with no fallback either way**
|
|
270
|
+
// (#131). The two used to be tried in turn, so a note *name* that
|
|
271
|
+
// looked like an address resolved as one and a genuine address that
|
|
272
|
+
// resolved nowhere silently became a name lookup.
|
|
273
|
+
const addressed = resolvesAsAddress(parsed);
|
|
274
|
+
const typeKey = ctx.type ? aliasKey(ctx.type, target) : null;
|
|
260
275
|
// The canonical separator (#1398) has to be resolved, not merely
|
|
261
|
-
// recognised.
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
const
|
|
276
|
+
// recognised. `null` here means the piped target is not an address at
|
|
277
|
+
// all, which is now a defect rather than a reason to try the aliases.
|
|
278
|
+
const hyphenKey = addressed ? qualifiedKey(target, ctx.contentTypes, ctx.packages) : null;
|
|
279
|
+
const rawKey = target.toLowerCase();
|
|
265
280
|
const hit =
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
addressed ?
|
|
282
|
+
((hyphenKey ? ctx.index.get(hyphenKey) : undefined) ??
|
|
283
|
+
// `section/slug` is the site's own address for a page, and it
|
|
284
|
+
// is in the same map. Admitted only when the target carries a
|
|
285
|
+
// slash, which is what keeps the *alias* fallbacks sharing
|
|
286
|
+
// that map — a page's name, basename and slug — out of the
|
|
287
|
+
// address namespace.
|
|
288
|
+
(rawKey.includes("/") ? ctx.index.get(rawKey) : undefined) ??
|
|
289
|
+
// A manifest entry carries the same `{ url, name }` shape as a
|
|
290
|
+
// local one (#1446), so a cross-package hit needs no special
|
|
291
|
+
// case below. Local wins: a live build is authoritative and a
|
|
292
|
+
// vendored manifest can only be staler.
|
|
293
|
+
(hyphenKey ? ctx.foreign?.get(hyphenKey) : undefined))
|
|
294
|
+
: typeKey ? ctx.typeAlias.get(typeKey)
|
|
295
|
+
: undefined;
|
|
274
296
|
if (hit) {
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
// ("worsens the [[Shock
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
// one, which is why the rule is the packs' own (#1409).
|
|
283
|
-
const text =
|
|
284
|
-
label ??
|
|
285
|
-
(isAddress(target, ctx.contentTypes) ? hit.name : target);
|
|
297
|
+
// An address with no label has no prose to show (a shortcode is
|
|
298
|
+
// not display text), so the document's **current** name stands in
|
|
299
|
+
// and a rename shows at every citation. A bare `[[Text]]` is
|
|
300
|
+
// already the prose the author wrote — substituting the canonical
|
|
301
|
+
// name there would rewrite the sentence ("worsens the [[Shock
|
|
302
|
+
// State]]" must not render as "Shock").
|
|
303
|
+
const text = label ?? (addressed ? hit.name : target);
|
|
286
304
|
// A pack-only package publishes Foundry addresses and no pages
|
|
287
305
|
// (#1516), so its entries carry no `path` and resolve to no URL.
|
|
288
306
|
// The address is real — this is not a typo and must not fail the
|
|
@@ -295,20 +313,12 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
295
313
|
}
|
|
296
314
|
|
|
297
315
|
const slash = target.indexOf("/");
|
|
298
|
-
const prefix =
|
|
299
|
-
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
// (`Rules/Bestiary.md` → `being-grkrahk`, a real note in the `thalorna`
|
|
303
|
-
// package). Nothing in the syntax separates that from a typo,
|
|
304
|
-
// so failing here would break the build on correct content.
|
|
305
|
-
//
|
|
306
|
-
// A dead address is caught instead by `lint:content-links` (#1414),
|
|
307
|
-
// which holds the reviewed list of cross-package exceptions — and which,
|
|
308
|
-
// unlike this build, runs as part of `npm run lint` on every change.
|
|
316
|
+
const prefix = slash === -1 ? null : target.slice(0, slash).toLowerCase();
|
|
317
|
+
// A slash-qualified target whose prefix is a real section or content
|
|
318
|
+
// type is definitely local, so it is a typo whatever the manifest
|
|
319
|
+
// situation.
|
|
309
320
|
const badQualified =
|
|
310
|
-
prefix !== null &&
|
|
311
|
-
(ctx.sections.has(prefix) || ctx.contentTypes.has(prefix));
|
|
321
|
+
prefix !== null && (ctx.sections.has(prefix) || ctx.contentTypes.has(prefix));
|
|
312
322
|
// The hyphen form is the canonical address (#1398) and is what the
|
|
313
323
|
// authored content writes. It could not be guarded while some packages
|
|
314
324
|
// were invisible here: `Rules/Bestiary.md` addresses `being-grkrahk`,
|
|
@@ -325,7 +335,17 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
325
335
|
// someone has to remember.
|
|
326
336
|
const badAddress = ctx.manifestsComplete === true && hyphenKey !== null;
|
|
327
337
|
|
|
328
|
-
if (
|
|
338
|
+
if (addressed && hyphenKey === null && !badQualified) {
|
|
339
|
+
// The author wrote a pipe, so they meant an address — and this is
|
|
340
|
+
// not one. Distinct from a dead address, because the fix is
|
|
341
|
+
// different: a name has to become an address, not be corrected
|
|
342
|
+
// (#131).
|
|
343
|
+
ctx.errors.push({ file: ctx.src, target, reason: "not-an-address" });
|
|
344
|
+
} else if (
|
|
345
|
+
addressed ?
|
|
346
|
+
rawKey.includes("/") && ctx.collide.has(rawKey)
|
|
347
|
+
: Boolean(typeKey && ctx.typeCollide.has(typeKey))
|
|
348
|
+
) {
|
|
329
349
|
ctx.errors.push({ file: ctx.src, target, reason: "ambiguous" });
|
|
330
350
|
} else if (badQualified || badAddress) {
|
|
331
351
|
ctx.errors.push({
|
|
@@ -334,6 +354,9 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
334
354
|
reason: "broken type/shortcode",
|
|
335
355
|
});
|
|
336
356
|
}
|
|
357
|
+
// An unresolved *alias* stays soft: it may be ordinary prose, or a
|
|
358
|
+
// worldbuilding placeholder for a note not yet written. It still
|
|
359
|
+
// renders marked, so a reader can see a link was intended.
|
|
337
360
|
return unresolvedLink(label ?? target, target);
|
|
338
361
|
});
|
|
339
362
|
}
|
|
@@ -116,6 +116,36 @@ export function authoredLabel({ display }) {
|
|
|
116
116
|
return display ? display : null;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Which namespace a link resolves in: the **address** space, or the **alias**
|
|
121
|
+
* space.
|
|
122
|
+
*
|
|
123
|
+
* **The pipe decides, and nothing else does** (#131). `[[x|…]]` is an address,
|
|
124
|
+
* parsed by the address grammar; `[[x]]` is an alias, looked up within the
|
|
125
|
+
* source note's own type. Neither falls back to the other.
|
|
126
|
+
*
|
|
127
|
+
* Both resolvers used to decide by *shape* instead — try the address, fall
|
|
128
|
+
* back to the alias — which had three costs. An author could not say which
|
|
129
|
+
* they meant, so a note whose **name** looked like an address (`Grukar-ahk`)
|
|
130
|
+
* was read as one, and a genuine address that resolved nowhere silently became
|
|
131
|
+
* a name lookup and reported nothing. And a positional address grammar could
|
|
132
|
+
* not split a target confidently until it had first ruled out every note name
|
|
133
|
+
* in the corpus.
|
|
134
|
+
*
|
|
135
|
+
* An **empty** label is still a pipe: `[[x|]]` is an address that renders the
|
|
136
|
+
* target's *current* name, so a rename shows at every citation with no link
|
|
137
|
+
* edited. That is why this reads {@link ParsedWikilink.labelled} and not
|
|
138
|
+
* {@link authoredLabel} — the two answer different questions, and only one of
|
|
139
|
+
* them is about namespaces.
|
|
140
|
+
*
|
|
141
|
+
* @param {{labelled: boolean}} parsed - A parsed wikilink, or anything
|
|
142
|
+
* carrying its `labelled`.
|
|
143
|
+
* @returns {boolean} True when the target is an address.
|
|
144
|
+
*/
|
|
145
|
+
export function resolvesAsAddress({ labelled }) {
|
|
146
|
+
return Boolean(labelled);
|
|
147
|
+
}
|
|
148
|
+
|
|
119
149
|
/**
|
|
120
150
|
* Whether a parsed link addresses a section of the page it is written on.
|
|
121
151
|
*
|