@heroiclands/package-build 22.12.3 → 22.13.1
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 +19 -0
- package/README.md +2 -2
- package/docs/api.md +42 -40
- package/docs/commands.md +0 -3
- package/docs/content-format.md +94 -84
- package/docs/getting-started.md +1 -3
- package/engine/address.mjs +39 -153
- package/engine/art-fields.mjs +1 -1
- package/engine/bundles.mjs +3 -2
- package/engine/content-address.mjs +6 -9
- package/engine/content-index.mjs +8 -10
- package/engine/content-links.mjs +9 -17
- package/engine/content-tables.mjs +1 -1
- package/engine/folder-notes.mjs +2 -1
- package/engine/foundry-entries.mjs +4 -8
- package/engine/frontmatter-lint.mjs +2 -2
- package/engine/frontmatter.mjs +2 -2
- package/engine/homepage.mjs +3 -3
- package/engine/item-compiler.mjs +7 -2
- package/engine/map-places.mjs +3 -1
- package/engine/metadata-index.mjs +23 -6
- package/engine/note-addresses.mjs +27 -9
- package/engine/note-ids.mjs +7 -2
- package/engine/note-vocabulary.mjs +24 -0
- package/engine/pack-router.mjs +7 -3
- package/engine/place-relations.mjs +4 -4
- package/engine/reference-targets.mjs +7 -7
- package/engine/shortcode-references.mjs +1 -1
- package/engine/site-index.mjs +8 -39
- package/engine/sql-tables.mjs +1 -1
- package/engine/system-block.mjs +19 -0
- package/engine/systems.mjs +4 -1
- package/engine/web-wikilinks.mjs +3 -3
- package/engine/wikilinks.mjs +31 -106
- package/hm3/actors.mjs +7 -2
- package/package.json +2 -3
- package/sohl/infobox.mjs +2 -2
- package/types/engine/address.d.mts +21 -88
- package/types/engine/content-address.d.mts +3 -6
- package/types/engine/content-links.d.mts +2 -6
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/homepage.d.mts +2 -2
- package/types/engine/note-addresses.d.mts +2 -0
- package/types/engine/system-block.d.mts +11 -0
- package/types/engine/systems.d.mts +2 -0
- package/bin/heroiclands-content-language-server.mjs +0 -8
- package/docs/content-language-server.md +0 -25
- package/engine/content-language-server.mjs +0 -439
- package/types/engine/content-language-server.d.mts +0 -64
package/engine/address.mjs
CHANGED
|
@@ -79,41 +79,25 @@
|
|
|
79
79
|
* to guess which sense was meant. The cost is that `<package>-<type>-<shortcode>`
|
|
80
80
|
* is **not** a written form, so a link into another package states its system.
|
|
81
81
|
*
|
|
82
|
-
* ##
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* Everything else takes the written system, then the position's default.
|
|
89
|
-
* - **`type` may not survive unchanged.** A system-bearing type reached at
|
|
90
|
-
* `none` is redirected to `doc<type>`, because a note's `none` address *is*
|
|
91
|
-
* its documentation journal — the Item is the one with a system.
|
|
92
|
-
* - **Acceptability accepts a type's documentation form.** A position that
|
|
93
|
-
* accepts `weapongear` accepts `docweapongear`, so a target addressed by its
|
|
94
|
-
* documentation journal is acceptable wherever the note itself would be.
|
|
82
|
+
* ## Defaults belong to the citing position
|
|
83
|
+
*
|
|
84
|
+
* Ordinary wikilinks default to `note`, embeds default to `none`, and typed
|
|
85
|
+
* frontmatter fields supply their declared system. The authored type stays
|
|
86
|
+
* the type in the complete tuple. A `doc<type>` qualifier is accepted as an
|
|
87
|
+
* input spelling for a readable note and resolves to `note-<type>`.
|
|
95
88
|
*
|
|
96
89
|
* ## The tuple keeps no memory of how it was written
|
|
97
90
|
*
|
|
98
91
|
* An abbreviation is a *rendering*, not a different value: `being-kaldor` in a
|
|
99
|
-
* `thalorna` note and `thalorna-
|
|
100
|
-
* they parse to the same four fields.
|
|
101
|
-
*
|
|
102
|
-
* journal. Nothing here records what the author typed — a tuple that did would
|
|
103
|
-
* make identity depend on a spelling, and two notes naming one document would
|
|
104
|
-
* compare unequal.
|
|
92
|
+
* `thalorna` note and `thalorna-note-being-kaldor` are the same Address, and
|
|
93
|
+
* they parse to the same four fields. Nothing here records what the author
|
|
94
|
+
* typed — a tuple that did would make identity depend on a spelling.
|
|
105
95
|
*
|
|
106
96
|
* The converse matters just as much: **one written string is two Addresses at
|
|
107
97
|
* two positions**, because the positions supply different defaults.
|
|
108
98
|
* `skill-wpnc` under `sohl.items` defaults its system from the block and names
|
|
109
|
-
* the
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* That is why {@link acceptsType} widens the accepted set rather than reading a
|
|
113
|
-
* remembered pre-redirect type off the tuple. The redirect is not reversible
|
|
114
|
-
* from the four segments alone: `docmacro-autoattack` addresses a `macro`'s
|
|
115
|
-
* journal while `macro-autoattack` addresses the Macro, so `doc`-stripping is a
|
|
116
|
-
* question about the type vocabulary, not about the tuple.
|
|
99
|
+
* the Item; the same string in body prose defaults to `note` and names the
|
|
100
|
+
* readable page. Only the resolved tuple can tell them apart.
|
|
117
101
|
*
|
|
118
102
|
* ## What this module does not do
|
|
119
103
|
*
|
|
@@ -132,17 +116,15 @@
|
|
|
132
116
|
// `engine/systems.mjs` imports nothing but `engine/address-charset.mjs`, so the
|
|
133
117
|
// direction is toward the leaf and cannot close a cycle.
|
|
134
118
|
import { isAddressSegment } from "./address-charset.mjs";
|
|
135
|
-
import { NO_SYSTEM, assertSystemSegment, isSystemSegment } from "./systems.mjs";
|
|
136
|
-
//
|
|
137
|
-
// whether a type's `<system>` segment is fixed. `engine/asset-types.mjs` is a
|
|
138
|
-
// leaf, so the direction cannot close a cycle.
|
|
139
|
-
import { isAssetType } from "./asset-types.mjs";
|
|
119
|
+
import { NO_SYSTEM, NOTE_SYSTEM, assertSystemSegment, isSystemSegment } from "./systems.mjs";
|
|
120
|
+
// Document subtype maps identify the system of a note's own Actor or Item.
|
|
140
121
|
import { systemOf } from "./document-subtypes.mjs";
|
|
141
122
|
import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
|
|
142
123
|
// `ids.mjs` is a leaf with no local imports, and `pack-config.mjs` reaches only
|
|
143
124
|
// the configuration loader and the diagnostics writer — neither reads an
|
|
144
125
|
// address, so the grammar sits below everything that consumes it.
|
|
145
|
-
import { currentType, ITEM_PACK, packForType } from "./ids.mjs";
|
|
126
|
+
import { currentType, ITEM_PACK, JOURNAL_TYPES, packForType } from "./ids.mjs";
|
|
127
|
+
import { HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
146
128
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
147
129
|
|
|
148
130
|
export { NO_SYSTEM };
|
|
@@ -166,32 +148,18 @@ export const ADDRESS_SEPARATOR = "-";
|
|
|
166
148
|
*/
|
|
167
149
|
export const CANONICAL_KEY_SEGMENTS = 4;
|
|
168
150
|
|
|
169
|
-
/**
|
|
170
|
-
* The qualifier prefix that addresses a note's **documentation** rather than
|
|
171
|
-
* the note's own document: `docskill-wpnc` is the JournalEntry that
|
|
172
|
-
* `skill-wpnc`'s prose compiled into.
|
|
173
|
-
*
|
|
174
|
-
* Every doc-carrying type has a `doc<type>` counterpart, formed by prefix and
|
|
175
|
-
* never enumerated, so the prefix itself is the whole of that vocabulary.
|
|
176
|
-
*
|
|
177
|
-
* @type {string}
|
|
178
|
-
*/
|
|
151
|
+
/** The accepted `doc<type>` input prefix for readable note links. */
|
|
179
152
|
export const ITEM_DOC_PREFIX = "doc";
|
|
180
153
|
|
|
181
154
|
/** Package, system and type are one spelling each, so they fold to lowercase. */
|
|
182
155
|
const norm = (s) => String(s).toLowerCase().trim();
|
|
183
156
|
|
|
184
157
|
/**
|
|
185
|
-
* A complete Address: four segments,
|
|
186
|
-
*
|
|
187
|
-
* `type` is the segment the canonical string carries, after the `doc<type>`
|
|
188
|
-
* redirect. There is no fifth field recording the written form, because the
|
|
189
|
-
* written form is a rendering rather than part of the value.
|
|
158
|
+
* A complete Address: four segments, with no memory of the authored form.
|
|
190
159
|
*
|
|
191
160
|
* @typedef {object} AddressTuple
|
|
192
161
|
* @property {string} package - The owning **content** package.
|
|
193
|
-
* @property {string} system -
|
|
194
|
-
* `none`.
|
|
162
|
+
* @property {string} system - `note`, `none`, or a game system id.
|
|
195
163
|
* @property {string} type - The type segment of the canonical Address.
|
|
196
164
|
* @property {string} shortcode - The note's `shortcode`.
|
|
197
165
|
*/
|
|
@@ -218,29 +186,8 @@ const norm = (s) => String(s).toLowerCase().trim();
|
|
|
218
186
|
*/
|
|
219
187
|
|
|
220
188
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* Every item type, every actor type, every map type, plus `macro` — a macro
|
|
225
|
-
* note's body documents the script the note also compiles into a Macro, which
|
|
226
|
-
* is the same shape as an item and its description: one note, two documents,
|
|
227
|
-
* the prose living in the journals pack.
|
|
228
|
-
*
|
|
229
|
-
* **One set, read by the compilers and by the grammar alike.** The journals
|
|
230
|
-
* pass decides what to compile from it, the link manifest decides what to
|
|
231
|
-
* publish a `doc<type>` entry for, and the parser decides whether `doc<type>`
|
|
232
|
-
* is a legal type segment. Held apart, the three drift into a manifest that
|
|
233
|
-
* asserts documentation nothing compiled, a compiled entry no consumer can
|
|
234
|
-
* address, or an authored `doc<type>` link that stops parsing. It is composed
|
|
235
|
-
* exactly once, in `defineConfig`, and read from there — never recomposed at a
|
|
236
|
-
* call site.
|
|
237
|
-
*
|
|
238
|
-
* `doc` notes are absent, for the reason that applies to them alone: a `doc`
|
|
239
|
-
* note's single document *is* its prose, so there is no second document for a
|
|
240
|
-
* `doc<type>` address to name.
|
|
241
|
-
*
|
|
242
|
-
* An accessor rather than a hoisted constant, so that importing this module
|
|
243
|
-
* needs no configuration.
|
|
189
|
+
* The configured types whose prose compiles into a separate JournalEntry.
|
|
190
|
+
* The journal has a `note` Address with the authored type segment.
|
|
244
191
|
*
|
|
245
192
|
* @returns {ReadonlySet<string>} The configured doc-carrying types.
|
|
246
193
|
*/
|
|
@@ -265,50 +212,20 @@ export function hasDocEntry(type) {
|
|
|
265
212
|
return docEntryTypes().has(String(currentType(type)));
|
|
266
213
|
}
|
|
267
214
|
|
|
268
|
-
/**
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
* the map types — whose documents Foundry itself defines and which therefore
|
|
274
|
-
* already live at `none`.
|
|
275
|
-
*
|
|
276
|
-
* It is what the `doc<type>` redirect tests rather than {@link hasDocEntry}: a
|
|
277
|
-
* `macro` has a documentation journal *and* a `none` address of its own, so
|
|
278
|
-
* redirecting on "has a doc entry" would collapse two live addresses into one
|
|
279
|
-
* and a `[[macro-autoattack|]]` would stop naming the Macro.
|
|
280
|
-
*
|
|
281
|
-
* @param {string} type - The note type.
|
|
282
|
-
* @returns {boolean} True when the type compiles into a system document.
|
|
283
|
-
*/
|
|
284
|
-
function isSystemBearing(type) {
|
|
285
|
-
return systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS) !== NO_SYSTEM;
|
|
215
|
+
/** The segment of a note's own document. */
|
|
216
|
+
export function ownDocumentSystem(type) {
|
|
217
|
+
return JOURNAL_TYPES.has(String(type)) || type === HOMEPAGE_TYPE ?
|
|
218
|
+
NOTE_SYSTEM
|
|
219
|
+
: systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS);
|
|
286
220
|
}
|
|
287
221
|
|
|
288
222
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* for a macro.
|
|
296
|
-
*
|
|
297
|
-
* The virtual form exists for a type that carries separate documentation
|
|
298
|
-
* ({@link docEntryTypes} — the set the journals compiler and the link manifest
|
|
299
|
-
* read too), **or** for one that routes to the items pack. The second clause
|
|
300
|
-
* covers the open, unenumerated set: types that compile into items are not
|
|
301
|
-
* enumerated anywhere, and a foreign package may publish an item type this
|
|
302
|
-
* build has never heard of. Dropping it would silently unlink every `doc<type>`
|
|
303
|
-
* address into such a package.
|
|
304
|
-
*
|
|
305
|
-
* A **real** type of the same name always wins: the virtual reading is only
|
|
306
|
-
* consulted for a qualifier no authored note claims.
|
|
307
|
-
*
|
|
308
|
-
* @param {string} qualifier - The already-normalised type segment.
|
|
309
|
-
* @param {Set<string>} types - Every type the content tree contains.
|
|
310
|
-
* @returns {string|null} The underlying document type, or `null` when the
|
|
311
|
-
* qualifier is not a virtual one.
|
|
223
|
+
* Read a `doc<type>` input qualifier as the underlying authored type.
|
|
224
|
+
* A real type with that spelling takes precedence.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} qualifier - The type segment from the input.
|
|
227
|
+
* @param {Set<string>} types - Types in the content tree.
|
|
228
|
+
* @returns {string|null} The underlying type, or `null`.
|
|
312
229
|
*/
|
|
313
230
|
export function resolveItemDocType(qualifier, types) {
|
|
314
231
|
if (types.has(qualifier)) return null; // a real type owns its own name
|
|
@@ -445,21 +362,11 @@ export function renderAddress(tuple) {
|
|
|
445
362
|
}
|
|
446
363
|
|
|
447
364
|
/**
|
|
448
|
-
* Whether the type an Address names is
|
|
449
|
-
*
|
|
450
|
-
* The second of the two questions, and separate from the first because the
|
|
451
|
-
* accepted **set** is a property of the position while the grammar is not.
|
|
452
|
-
*
|
|
453
|
-
* **An accepted type carries its documentation form with it.** A position
|
|
454
|
-
* accepting `weapongear` accepts `docweapongear`, because the journal that
|
|
455
|
-
* documents a weapon is the same thing to a position that wanted a weapon — and
|
|
456
|
-
* a prose address of one *is* the journal, so refusing it would refuse the form
|
|
457
|
-
* an author most often writes. It is the widening `defineConfig` applies to the
|
|
458
|
-
* types a package may claim, applied here to the types a position may take.
|
|
365
|
+
* Whether the canonical type an Address names is accepted at this position.
|
|
459
366
|
*
|
|
460
|
-
* @param {AddressTuple} tuple - A
|
|
461
|
-
* @param {Iterable<string>} allowed -
|
|
462
|
-
* @returns {boolean}
|
|
367
|
+
* @param {AddressTuple} tuple - A complete Address.
|
|
368
|
+
* @param {Iterable<string>} allowed - Accepted types.
|
|
369
|
+
* @returns {boolean} Whether the type is accepted.
|
|
463
370
|
*/
|
|
464
371
|
export function acceptsType(tuple, allowed) {
|
|
465
372
|
const type = norm(tuple?.type ?? "");
|
|
@@ -467,7 +374,7 @@ export function acceptsType(tuple, allowed) {
|
|
|
467
374
|
for (const one of allowed ?? []) {
|
|
468
375
|
const want = norm(one);
|
|
469
376
|
if (!want) continue;
|
|
470
|
-
if (type === want
|
|
377
|
+
if (type === want) return true;
|
|
471
378
|
}
|
|
472
379
|
return false;
|
|
473
380
|
}
|
|
@@ -475,9 +382,7 @@ export function acceptsType(tuple, allowed) {
|
|
|
475
382
|
/**
|
|
476
383
|
* Apply the defaults to a partial reading, giving a complete Address.
|
|
477
384
|
*
|
|
478
|
-
*
|
|
479
|
-
* other: the package, then the system the type may fix, then the redirect the
|
|
480
|
-
* system may force.
|
|
385
|
+
* Complete a parsed Address using the citing position's package and system.
|
|
481
386
|
*
|
|
482
387
|
* @param {{type: string, shortcode: string, package?: string, system?: string,
|
|
483
388
|
* itemDoc?: boolean}} read - A partial reading, as {@link readQualifier}
|
|
@@ -490,27 +395,8 @@ export function completeAddress(read, where = {}) {
|
|
|
490
395
|
if (isAddressTuple(read)) return read;
|
|
491
396
|
const written = read.type;
|
|
492
397
|
const pkg = read.package ?? where.package;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
// to no game system, so the segment is a property of the type rather than of
|
|
496
|
-
// where the reference was written. Without that, a `sohl:` block naming
|
|
497
|
-
// `icon-anvil` would expand to `sohl-sohl-icon-anvil` and resolve to
|
|
498
|
-
// nothing, which is exactly where an embedded item's art is written.
|
|
499
|
-
// Otherwise the block's system, which body prose reports as `none`.
|
|
500
|
-
const system =
|
|
501
|
-
read.itemDoc || isAssetType(written) ?
|
|
502
|
-
NO_SYSTEM
|
|
503
|
-
: (read.system ?? where.system ?? NO_SYSTEM);
|
|
504
|
-
// **Under `none`, a system-bearing type addresses its documentation
|
|
505
|
-
// journal.** A note's `none` address *is* its `doc<type>` entry — the Item
|
|
506
|
-
// is the one with a system — so a prose `[[affiliation-sirvadar|…]]` names
|
|
507
|
-
// the page, which is almost always what prose means. A value that means the
|
|
508
|
-
// Item states the system and gets it. A `doc<type>` written explicitly is
|
|
509
|
-
// `none` wherever it appears, even inside a system block: no game system
|
|
510
|
-
// defines a JournalEntry.
|
|
511
|
-
const redirected = system === NO_SYSTEM && isSystemBearing(written);
|
|
512
|
-
const type = read.itemDoc || redirected ? `${ITEM_DOC_PREFIX}${written}` : written;
|
|
513
|
-
return addressTuple({ package: pkg, system, type, shortcode: read.shortcode });
|
|
398
|
+
const system = read.itemDoc ? NOTE_SYSTEM : (read.system ?? where.system ?? NOTE_SYSTEM);
|
|
399
|
+
return addressTuple({ package: pkg, system, type: written, shortcode: read.shortcode });
|
|
514
400
|
}
|
|
515
401
|
|
|
516
402
|
/**
|
package/engine/art-fields.mjs
CHANGED
|
@@ -88,7 +88,7 @@ export function artSlot(key) {
|
|
|
88
88
|
* not parse; or `not-accepted` when it parses to a type outside `accepts`.
|
|
89
89
|
*/
|
|
90
90
|
export function artTarget(value, defaultType, accepts, vocabulary = {}) {
|
|
91
|
-
const tuple = parseAddress(value, { ...vocabulary, type: defaultType });
|
|
91
|
+
const tuple = parseAddress(value, { ...vocabulary, system: ASSET_SYSTEM, type: defaultType });
|
|
92
92
|
if (tuple.reason) return tuple;
|
|
93
93
|
if (accepts && !acceptsType(tuple, accepts)) {
|
|
94
94
|
return { reason: "not-accepted", type: tuple.type };
|
package/engine/bundles.mjs
CHANGED
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
import { contentPackage } from "./content-package.mjs";
|
|
72
72
|
import { FOLDER_TYPE } from "./folder-notes.mjs";
|
|
73
73
|
import { HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
74
|
-
import { itemDocEntryId } from "./item-docs.mjs";
|
|
74
|
+
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
75
75
|
import { folderField, renderFoundryMarkdown, resolveName } from "./helpers.mjs";
|
|
76
76
|
import { packForType } from "./ids.mjs";
|
|
77
77
|
import { readQualifier } from "./wikilinks.mjs";
|
|
@@ -251,8 +251,9 @@ export class Bundles extends BasePackCompiler {
|
|
|
251
251
|
|
|
252
252
|
const baseType =
|
|
253
253
|
isAddressTuple(read) ? resolveItemDocType(read.type, index.types) : undefined;
|
|
254
|
-
const itemDoc = read.itemDoc || Boolean(baseType);
|
|
255
254
|
const type = baseType ?? read.type;
|
|
255
|
+
const itemDoc =
|
|
256
|
+
read.itemDoc || Boolean(baseType) || (read.system === "note" && hasDocEntry(type));
|
|
256
257
|
const note = index.byShortcode.get(`${type}/${read.shortcode}`);
|
|
257
258
|
if (!note) {
|
|
258
259
|
throw new Error(
|
|
@@ -45,7 +45,7 @@ import { DEFAULT_ADDRESS_SCHEME } from "../content-config.mjs";
|
|
|
45
45
|
// The system vocabulary is the `<system>` segment's own registry, and
|
|
46
46
|
// `engine/systems.mjs` imports nothing but `engine/address-charset.mjs`, so
|
|
47
47
|
// the direction is toward the leaf and cannot close a cycle.
|
|
48
|
-
import { NO_SYSTEM,
|
|
48
|
+
import { NO_SYSTEM, NOTE_SYSTEM, isSystemId } from "./systems.mjs";
|
|
49
49
|
|
|
50
50
|
// The address grammar, which sits below this module: a page URL is derived from
|
|
51
51
|
// an address, so the rule for reading and writing one is stated there and
|
|
@@ -171,12 +171,9 @@ export function packageAddress(fm) {
|
|
|
171
171
|
*
|
|
172
172
|
* The **enclosing system block** decides, at any depth within it, and nothing
|
|
173
173
|
* else does: `sohl.items[3].model` and `sohl.system.body.structure` are both
|
|
174
|
-
* `sohl` because both sit under `sohl:`. Everywhere else
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
* It is the block rather than the field, so an `Address` field needs no opinion
|
|
179
|
-
* about systems and no per-field table has to be kept in step with the schema.
|
|
174
|
+
* `sohl` because both sit under `sohl:`. Everywhere else takes
|
|
175
|
+
* {@link NOTE_SYSTEM}; fields declared for assets or folders supply their own
|
|
176
|
+
* `none` default at the field parser.
|
|
180
177
|
*
|
|
181
178
|
* The first segment must **be** a declared system, not merely look like one:
|
|
182
179
|
* `sohlish.items` is a key called `sohlish`, and `notes.sohl.thing` names no
|
|
@@ -187,9 +184,9 @@ export function packageAddress(fm) {
|
|
|
187
184
|
* @returns {string} The system id, or `none`.
|
|
188
185
|
*/
|
|
189
186
|
export function blockSystem(keyPath) {
|
|
190
|
-
if (typeof keyPath !== "string" || !keyPath) return
|
|
187
|
+
if (typeof keyPath !== "string" || !keyPath) return NOTE_SYSTEM;
|
|
191
188
|
const first = keyPath.split(".")[0].trim().toLowerCase();
|
|
192
|
-
return
|
|
189
|
+
return isSystemId(first) ? first : NOTE_SYSTEM;
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
/**
|
package/engine/content-index.mjs
CHANGED
|
@@ -102,12 +102,13 @@ import unidecode from "unidecode";
|
|
|
102
102
|
import { metadataFileName } from "./metadata-index.mjs";
|
|
103
103
|
import { collectAssetRecords } from "./asset-index.mjs";
|
|
104
104
|
import { addressSlug, canonicalKey } from "./content-address.mjs";
|
|
105
|
+
import { ownDocumentSystem } from "./address.mjs";
|
|
106
|
+
import { NOTE_SYSTEM } from "./systems.mjs";
|
|
105
107
|
// One reader for a note's anchors, shared with the link checker and with the
|
|
106
108
|
// builds that emit a link. Re-exported because this is where callers
|
|
107
109
|
// have always addressed it.
|
|
108
110
|
import { collectAnchors } from "./anchors.mjs";
|
|
109
|
-
import {
|
|
110
|
-
import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./note-claims.mjs";
|
|
111
|
+
import { NO_SYSTEM } from "./document-subtypes.mjs";
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
114
|
* The `<system>` a note belongs to when it belongs to none.
|
|
@@ -204,7 +205,7 @@ export function noteAddress(frontmatter, contentPackage) {
|
|
|
204
205
|
slug,
|
|
205
206
|
canonical: canonicalKey(
|
|
206
207
|
contentPackage,
|
|
207
|
-
|
|
208
|
+
ownDocumentSystem(frontmatter.type),
|
|
208
209
|
frontmatter.type,
|
|
209
210
|
frontmatter.shortcode,
|
|
210
211
|
),
|
|
@@ -504,10 +505,7 @@ export function buildIndexRecord({
|
|
|
504
505
|
link: address ? `${address.slug}#${a.slug}` : null,
|
|
505
506
|
}))
|
|
506
507
|
),
|
|
507
|
-
foundry: foundryBlock(
|
|
508
|
-
entries?.own,
|
|
509
|
-
systemOf(frontmatter?.type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
|
|
510
|
-
),
|
|
508
|
+
foundry: foundryBlock(entries?.own, ownDocumentSystem(frontmatter?.type)),
|
|
511
509
|
// Forward link to the note's documentation journal, which is its
|
|
512
510
|
// own record. Named rather than nested, because the journal is a
|
|
513
511
|
// separate document with its own address — see `buildDocRecord`.
|
|
@@ -536,7 +534,7 @@ export function buildIndexRecord({
|
|
|
536
534
|
*
|
|
537
535
|
* An item note compiles into two documents — the item, and a JournalEntry
|
|
538
536
|
* holding its prose — and the second is a document in its own right: its own
|
|
539
|
-
* canonical address
|
|
537
|
+
* canonical `note` address with the authored type, its own UUID and pages.
|
|
540
538
|
* So it gets its own record, and resolving `docaffliction/blkdth` is the same
|
|
541
539
|
* lookup as resolving anything else. Nested inside the item's record it would
|
|
542
540
|
* be the one address in the index reachable only by knowing to look somewhere
|
|
@@ -572,7 +570,7 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
|
|
|
572
570
|
return /** @type {Record<string, any>} */ (
|
|
573
571
|
sortKeysDeep({
|
|
574
572
|
package: contentPackage,
|
|
575
|
-
type:
|
|
573
|
+
type: frontmatter.type,
|
|
576
574
|
shortcode: frontmatter.shortcode,
|
|
577
575
|
name: frontmatter.name,
|
|
578
576
|
// The journal's own `_id`, taken from the entry rather than
|
|
@@ -586,7 +584,7 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
|
|
|
586
584
|
// the forward link on that record, so either end reaches the other.
|
|
587
585
|
documents: address.canonical,
|
|
588
586
|
anchors: made ? anchors : null,
|
|
589
|
-
foundry: foundryBlock(entry),
|
|
587
|
+
foundry: foundryBlock(entry, NOTE_SYSTEM),
|
|
590
588
|
file,
|
|
591
589
|
})
|
|
592
590
|
);
|
package/engine/content-links.mjs
CHANGED
|
@@ -76,8 +76,8 @@ import { ASSET_TYPE_NAMES } from "./asset-types.mjs";
|
|
|
76
76
|
import { resolveEmbeds } from "./content-embeds.mjs";
|
|
77
77
|
import { foundryAddressProblem, servesFoundry } from "./pathnames.mjs";
|
|
78
78
|
import { hasDocEntry } from "./item-docs.mjs";
|
|
79
|
-
import {
|
|
80
|
-
import {
|
|
79
|
+
import { ownDocumentSystem } from "./address.mjs";
|
|
80
|
+
import { NOTE_SYSTEM } from "./systems.mjs";
|
|
81
81
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
82
82
|
import { searchableFrontmatter } from "./note-package.mjs";
|
|
83
83
|
import {
|
|
@@ -138,12 +138,8 @@ export function anchorsOf(body) {
|
|
|
138
138
|
* before, since the walk read the file and it then read it again for the raw
|
|
139
139
|
* text.
|
|
140
140
|
*
|
|
141
|
-
* The index
|
|
142
|
-
*
|
|
143
|
-
* `doc<type>/shortcode` for the JournalEntry its prose compiles into. Once a
|
|
144
|
-
* manifest publishes `doc<type>` entries that prefix is a *known type*, and the
|
|
145
|
-
* virtual reading that used to answer for it no longer fires — a real type owns
|
|
146
|
-
* its own name — so the note is indexed under both.
|
|
141
|
+
* The index holds each compiled document under its complete Address. Readable
|
|
142
|
+
* content uses `note`, and system documents use their declared system.
|
|
147
143
|
*
|
|
148
144
|
* @param {string} contentBase - Root of the content tree.
|
|
149
145
|
* @param {object} [opts]
|
|
@@ -250,24 +246,20 @@ export function buildLinkIndex(
|
|
|
250
246
|
// once for the whole build.
|
|
251
247
|
const canonical =
|
|
252
248
|
encodeAddresses(record.address?.canonical) ??
|
|
253
|
-
canonicalKey(pkg,
|
|
249
|
+
canonicalKey(pkg, ownDocumentSystem(type), type, fm.shortcode);
|
|
254
250
|
// A note with no address publishes no page, so its identity is
|
|
255
251
|
// filed where a link cannot reach it and a reference still can.
|
|
256
252
|
const into = record.address ? byKey : byStub;
|
|
257
253
|
into.set(canonical, note);
|
|
258
254
|
if (hasDocEntry(type)) {
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
// system blocks it carries.
|
|
262
|
-
into.set(canonicalKey(pkg, NO_SYSTEM, `doc${type}`, fm.shortcode), note);
|
|
255
|
+
// The prose has one `note` Address across all system blocks.
|
|
256
|
+
into.set(canonicalKey(pkg, NOTE_SYSTEM, type, fm.shortcode), note);
|
|
263
257
|
}
|
|
264
258
|
}
|
|
265
259
|
}
|
|
266
260
|
|
|
267
261
|
// The types a link may name, which are the ones notes declare. A
|
|
268
|
-
//
|
|
269
|
-
// virtual, and `readQualifier` resolves it from the base type rather than
|
|
270
|
-
// from a type any tree declares.
|
|
262
|
+
// The `doc<type>` input spelling is an alias, not a published type.
|
|
271
263
|
//
|
|
272
264
|
// The **asset** types join unconditionally, whether or not this tree holds
|
|
273
265
|
// a file of each. They are a closed vocabulary rather than a census of what
|
|
@@ -423,7 +415,7 @@ export function buildLinkIndex(
|
|
|
423
415
|
for (const [all, rawInner] of matchAllOutsideCode(body, new RegExp(WIKILINK.source, "g"))) {
|
|
424
416
|
const parsed = readWikilink(rawInner, {
|
|
425
417
|
package: pkg,
|
|
426
|
-
system: "
|
|
418
|
+
system: "note",
|
|
427
419
|
types,
|
|
428
420
|
packages,
|
|
429
421
|
noIndexPackages,
|
|
@@ -1041,7 +1041,7 @@ export function renderContentTable(spec, rows, linkable, self) {
|
|
|
1041
1041
|
const label = text.replace(/\\?\|/g, "/");
|
|
1042
1042
|
const target = completeAddress(
|
|
1043
1043
|
{ type: doc.fm.type, shortcode: doc.fm.shortcode },
|
|
1044
|
-
{ package: doc.fm.package ?? contentPackage(), system: "
|
|
1044
|
+
{ package: doc.fm.package ?? contentPackage(), system: "note" },
|
|
1045
1045
|
);
|
|
1046
1046
|
return `[[${renderAddress(target)}\\|${label}]]`;
|
|
1047
1047
|
}),
|
package/engine/folder-notes.mjs
CHANGED
|
@@ -274,7 +274,8 @@ export function collectFolderNotes(notes, pkg) {
|
|
|
274
274
|
|
|
275
275
|
const { parent, color } = folderFields(fm);
|
|
276
276
|
const address = folderAddress(pkg, shortcode);
|
|
277
|
-
const
|
|
277
|
+
const pinnedId = fm.data?.id ?? fm.id;
|
|
278
|
+
const authoredId = pinnedId == null ? "" : String(pinnedId).trim();
|
|
278
279
|
|
|
279
280
|
folders.push({
|
|
280
281
|
shortcode,
|
|
@@ -60,7 +60,8 @@ import path from "node:path";
|
|
|
60
60
|
|
|
61
61
|
import { noteAddressContext } from "./note-addresses.mjs";
|
|
62
62
|
import { canonicalKey, packageAddress } from "./content-address.mjs";
|
|
63
|
-
import {
|
|
63
|
+
import { ownDocumentSystem } from "./address.mjs";
|
|
64
|
+
import { NOTE_SYSTEM } from "./systems.mjs";
|
|
64
65
|
import {
|
|
65
66
|
KNOWN_DOCUMENT_SUBTYPE_MAPS,
|
|
66
67
|
NEVER_PACKED_TYPES,
|
|
@@ -150,12 +151,7 @@ export function anchorsOf(entryUuid, entryId, body, name) {
|
|
|
150
151
|
*/
|
|
151
152
|
export function entriesForNote(fm, name, address, body, ctx) {
|
|
152
153
|
const { contentPackage, foundryPackageId, packRouter } = ctx;
|
|
153
|
-
const key = canonicalKey(
|
|
154
|
-
contentPackage,
|
|
155
|
-
systemOf(fm.type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
|
|
156
|
-
fm.type,
|
|
157
|
-
fm.shortcode,
|
|
158
|
-
);
|
|
154
|
+
const key = canonicalKey(contentPackage, ownDocumentSystem(fm.type), fm.type, fm.shortcode);
|
|
159
155
|
// `buildManifest` records `packageRelative(url, base)`, so the pair it is
|
|
160
156
|
// given has to round-trip. The address is already package-relative, so the
|
|
161
157
|
// honest pair is the address under a base of `"/"` — which strips straight
|
|
@@ -244,7 +240,7 @@ export function entriesForNote(fm, name, address, body, ctx) {
|
|
|
244
240
|
// `NO_SYSTEM`, whatever the item is: a documentation journal is a
|
|
245
241
|
// JournalEntry, which no game system defines, and there is one of them
|
|
246
242
|
// however many system blocks the note carries.
|
|
247
|
-
const docKey = canonicalKey(contentPackage,
|
|
243
|
+
const docKey = canonicalKey(contentPackage, NOTE_SYSTEM, fm.type, fm.shortcode);
|
|
248
244
|
const docEntryId = fm.id ? itemDocEntryId(fm.id) : undefined;
|
|
249
245
|
const docUuid = uuidFor("doc", docEntryId);
|
|
250
246
|
return [
|
|
@@ -1109,7 +1109,7 @@ export function lintNote(
|
|
|
1109
1109
|
const fm = note.fm ?? {};
|
|
1110
1110
|
const referenceContext = {
|
|
1111
1111
|
package: index?.contentPackage,
|
|
1112
|
-
system: "
|
|
1112
|
+
system: "note",
|
|
1113
1113
|
...addressContext,
|
|
1114
1114
|
types: new Set([
|
|
1115
1115
|
...Object.keys(vocabulary ?? {}),
|
|
@@ -1773,7 +1773,7 @@ export function lintFrontmatter(
|
|
|
1773
1773
|
index: references ? index : undefined,
|
|
1774
1774
|
addressContext: {
|
|
1775
1775
|
package: index.contentPackage,
|
|
1776
|
-
system: "
|
|
1776
|
+
system: "note",
|
|
1777
1777
|
types: index.types,
|
|
1778
1778
|
},
|
|
1779
1779
|
...(systems ? { systems } : {}),
|
package/engine/frontmatter.mjs
CHANGED
|
@@ -348,7 +348,7 @@ export function parseValueDesc(raw) {
|
|
|
348
348
|
/**
|
|
349
349
|
* The compendium folder a note names.
|
|
350
350
|
*
|
|
351
|
-
*
|
|
351
|
+
* `data.packFolder` is a folder note's **address**
|
|
352
352
|
* (`folder-poisonsandtoxins`), resolved through the address index the whole
|
|
353
353
|
* build shares. The `folder:` Foundry-id spelling this function once read
|
|
354
354
|
* beside it, and the per-pack `*-folders.yaml` that id was resolved against,
|
|
@@ -372,7 +372,7 @@ export function parseValueDesc(raw) {
|
|
|
372
372
|
* two spellings, and there is only one left to be.
|
|
373
373
|
*/
|
|
374
374
|
export function folderField(fm) {
|
|
375
|
-
const asAddress =
|
|
375
|
+
const asAddress = fm?.sohl?.packFolder ?? fm?.data?.packFolder ?? fm?.packFolder ?? null;
|
|
376
376
|
if (asAddress != null && asAddress !== "") return { value: asAddress, isAddress: true };
|
|
377
377
|
return { value: null, isAddress: true };
|
|
378
378
|
}
|
package/engine/homepage.mjs
CHANGED
|
@@ -340,8 +340,8 @@ function positionOfType(file) {
|
|
|
340
340
|
* The one defaulted value on the page, and it defaults to the package's own
|
|
341
341
|
* `packageBuild.manifest.title` — the name Foundry already shows for the
|
|
342
342
|
* package — so a homepage that adds nothing to it need not restate it. An
|
|
343
|
-
* authored `
|
|
344
|
-
* differently from a package browser.
|
|
343
|
+
* authored `name.full` wins, because a front page may greet a reader
|
|
344
|
+
* differently from a package browser. A top-level `title` is also readable.
|
|
345
345
|
*
|
|
346
346
|
* Falls back to `contentPackage` last, so a package that has no manifest of its
|
|
347
347
|
* own still yields a titled page rather than a blank heading.
|
|
@@ -351,7 +351,7 @@ function positionOfType(file) {
|
|
|
351
351
|
* @returns {string} The title.
|
|
352
352
|
*/
|
|
353
353
|
export function homepageTitle(fm, config) {
|
|
354
|
-
const authored = fm?.title;
|
|
354
|
+
const authored = fm?.name?.full ?? fm?.title;
|
|
355
355
|
if (typeof authored === "string" && authored.trim()) return authored;
|
|
356
356
|
const manifest = /** @type {Record<string, unknown>|undefined} */ (
|
|
357
357
|
config?.packageBuild?.manifest
|
package/engine/item-compiler.mjs
CHANGED
|
@@ -64,7 +64,12 @@ import { documentSubtype, subtypeRow } from "./document-subtypes.mjs";
|
|
|
64
64
|
// The note-level `<system>:` block: `<system>.system` onto the document's
|
|
65
65
|
// `system` verbatim, and `<system>.img` / `.effects` / `.flags` overriding
|
|
66
66
|
// their shared top-level forms for this system alone.
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
blockDataProperty,
|
|
69
|
+
blockProperty,
|
|
70
|
+
claimedPaths,
|
|
71
|
+
mergeSystemData,
|
|
72
|
+
} from "./system-block.mjs";
|
|
68
73
|
// The other direction of the same declaration: a field the *document* writes in
|
|
69
74
|
// play, which a note may not author and the builder does not emit.
|
|
70
75
|
import { assertNoDerivedFields } from "./derived-fields.mjs";
|
|
@@ -379,7 +384,7 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
379
384
|
// one spelling: `packFolder` names a folder note by its address, the
|
|
380
385
|
// `folder:` id spelling having been retired with the per-pack YAML it
|
|
381
386
|
// resolved against.
|
|
382
|
-
const folder = this.folderResolver(
|
|
387
|
+
const folder = this.folderResolver(blockDataProperty(fm, system, "packFolder", null), {
|
|
383
388
|
isAddress: true,
|
|
384
389
|
});
|
|
385
390
|
|
package/engine/map-places.mjs
CHANGED
|
@@ -207,7 +207,9 @@ export function placesFromRecords(records, { contentBase, base }) {
|
|
|
207
207
|
folder: record.file?.folder ?? "",
|
|
208
208
|
package: String(record.package ?? ""),
|
|
209
209
|
local: true,
|
|
210
|
-
hasPackFolder:
|
|
210
|
+
hasPackFolder:
|
|
211
|
+
Object.hasOwn(record.data ?? {}, "packFolder") ||
|
|
212
|
+
Object.hasOwn(record, "packFolder"),
|
|
211
213
|
});
|
|
212
214
|
} else if (record.type === "affiliation" && record.subType === "polity") {
|
|
213
215
|
polities.push({
|