@heroiclands/package-build 10.0.1 → 11.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 +279 -0
- package/CONTENT.md +218 -71
- package/MIGRATING.md +64 -0
- package/bin/content-build.mjs +59 -75
- package/docs/content-format.md +90 -67
- package/engine/base-compiler.mjs +7 -1
- package/engine/content-address.mjs +71 -18
- package/engine/content-format-check.mjs +1 -1
- package/engine/content-links.mjs +93 -112
- package/engine/content-lint.mjs +14 -10
- package/engine/content-slug.mjs +39 -105
- package/engine/diagnostics.mjs +16 -2
- package/engine/frontmatter-lint.mjs +26 -13
- package/engine/helpers.mjs +31 -68
- package/engine/homepage.mjs +131 -86
- package/engine/index.mjs +2 -5
- package/engine/manifest-emit.mjs +23 -4
- package/engine/note-vocabulary.mjs +58 -1
- package/engine/retired-fields.mjs +117 -6
- package/engine/site-build.mjs +182 -59
- package/engine/site-index.mjs +57 -102
- package/engine/web-wikilinks.mjs +183 -127
- package/engine/wikilink-syntax.mjs +174 -34
- package/engine/wikilinks.mjs +159 -117
- package/package.json +1 -1
- package/types/engine/base-compiler.d.mts +1 -1
- package/types/engine/content-address.d.mts +46 -14
- package/types/engine/content-links.d.mts +13 -17
- package/types/engine/content-slug.d.mts +11 -48
- package/types/engine/diagnostics.d.mts +14 -1
- package/types/engine/helpers.d.mts +4 -3
- package/types/engine/homepage.d.mts +96 -60
- package/types/engine/index.d.mts +0 -1
- package/types/engine/note-vocabulary.d.mts +43 -0
- package/types/engine/retired-fields.d.mts +78 -1
- package/types/engine/site-build.d.mts +70 -17
- package/types/engine/site-index.d.mts +19 -21
- package/types/engine/web-wikilinks.d.mts +29 -28
- package/types/engine/wikilink-syntax.d.mts +126 -40
- package/types/engine/wikilinks.d.mts +29 -24
- package/engine/abbreviations.mjs +0 -0
- package/engine/alias-index.mjs +0 -153
- package/types/engine/abbreviations.d.mts +0 -44
- package/types/engine/alias-index.d.mts +0 -122
|
@@ -1,59 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The URL
|
|
2
|
+
* The URL-safe token a piece of prose reduces to.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
* is carried across rather than dropped
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `fi`→`fi`, and eth (`ð`) follows the Icelandic convention of a bare `d`.
|
|
4
|
+
* The text is **transliterated** before it is reduced, so an accented character
|
|
5
|
+
* is carried across rather than dropped. Ligatures expand the way a reader would
|
|
6
|
+
* spell them out: `þ`→`th`, `æ`→`ae`, `œ`→`oe`, `ß`→`ss`, `ij`→`ij`, `fi`→`fi`,
|
|
7
|
+
* and eth (`ð`) follows the Icelandic convention of a bare `d`.
|
|
9
8
|
*
|
|
10
9
|
* Two reductions are ours rather than the transliterator's:
|
|
11
10
|
*
|
|
12
11
|
* - **apostrophes are removed**, not treated as separators (`Armorer's Kit` →
|
|
13
|
-
* `armorers-kit`)
|
|
12
|
+
* `armorers-kit`);
|
|
14
13
|
* - **a fraction keeps its digits together** — a vulgar fraction expands to
|
|
15
14
|
* `3/4`, and the solidus would otherwise split it into `3-4`, so a slash
|
|
16
15
|
* *between digits* is closed up (`Kûrbúl ¾-Helm` → `kurbul-34-helm`).
|
|
17
16
|
*
|
|
18
|
-
* @param {string | undefined}
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* characters — either way the note cannot be addressed, which is a content
|
|
23
|
-
* error rather than something to paper over with a fallback.
|
|
17
|
+
* @param {string | undefined} text - The prose to reduce.
|
|
18
|
+
* @returns {string} The token, or `""` when the text carries nothing URL-safe.
|
|
19
|
+
* Empty is an ordinary answer here: nothing is addressed by a slug any more,
|
|
20
|
+
* so an anchor that reduces to nothing is the caller's to judge.
|
|
24
21
|
*/
|
|
25
|
-
export function slugify(text:
|
|
26
|
-
/**
|
|
27
|
-
* The URL segment a content note publishes at.
|
|
28
|
-
*
|
|
29
|
-
* {@link slugify} with the rule that a document *must* be addressable: a note
|
|
30
|
-
* that yields no slug is a content error, not something to paper over with a
|
|
31
|
-
* fallback, because the alternative is a page nobody can reach.
|
|
32
|
-
*
|
|
33
|
-
* @param {string | undefined} name - The note's display name (`name.full`),
|
|
34
|
-
* which a malformed note may not have at all.
|
|
35
|
-
* @returns {string} The URL segment (never empty).
|
|
36
|
-
* @throws {Error} When there is no name, or the name carries no URL-safe
|
|
37
|
-
* characters.
|
|
38
|
-
*/
|
|
39
|
-
export function contentSlug(name: string | undefined): string;
|
|
40
|
-
/**
|
|
41
|
-
* Find pages that would publish to the same URL.
|
|
42
|
-
*
|
|
43
|
-
* Nothing constrains two notes in one section from sharing a name, and a
|
|
44
|
-
* collision silently overwrites one page with the other. This turns it into a
|
|
45
|
-
* build failure that names every claimant, so the fix is a more specific title.
|
|
46
|
-
* (The content tree has no collisions today.)
|
|
47
|
-
*
|
|
48
|
-
* @param {Array<{sec: string, slug: string, src: string}>} pages
|
|
49
|
-
* @returns {Array<{url: string, sources: string[]}>} One entry per collision, in
|
|
50
|
-
* first-claim order; empty when every URL is unique.
|
|
51
|
-
*/
|
|
52
|
-
export function findSlugCollisions(pages: Array<{
|
|
53
|
-
sec: string;
|
|
54
|
-
slug: string;
|
|
55
|
-
src: string;
|
|
56
|
-
}>): Array<{
|
|
57
|
-
url: string;
|
|
58
|
-
sources: string[];
|
|
59
|
-
}>;
|
|
22
|
+
export function slugify(text: string | undefined): string;
|
|
@@ -111,16 +111,29 @@ export function positionInBody(body: string, offset: number, { bodyLine, bodyCol
|
|
|
111
111
|
* a line of prose — sending the reader to a position that is not the problem,
|
|
112
112
|
* which is the one thing the located form exists to prevent.
|
|
113
113
|
*
|
|
114
|
+
* The key match tolerates leading whitespace by default, so a nested key of the
|
|
115
|
+
* same name answers when no top-level one is present — which is usually what a
|
|
116
|
+
* reader wants, the key being unique in nearly every note. Pass `topLevel` where
|
|
117
|
+
* it is not: `aliases` is both a retired top-level field and a **permitted**
|
|
118
|
+
* `name.aliases` (#180), and a finding about the first must never open on the
|
|
119
|
+
* second, which would tell an author to delete a field they are allowed to
|
|
120
|
+
* write.
|
|
121
|
+
*
|
|
114
122
|
* @param {string} raw - The file's full contents, frontmatter included.
|
|
115
123
|
* @param {string} key - The top-level frontmatter key.
|
|
116
124
|
* @param {string} [value] - When given, prefer the occurrence whose line also
|
|
117
125
|
* carries this text. A list-valued key (`aliases`) is reported at the entry
|
|
118
126
|
* that is wrong, not at the key that introduces it.
|
|
127
|
+
* @param {object} [options] - Options.
|
|
128
|
+
* @param {boolean} [options.topLevel=false] - Require the key at column 1, so
|
|
129
|
+
* an identically named nested key cannot answer for it.
|
|
119
130
|
* @returns {{line?: number, column?: number}} Spreadable position fields, empty
|
|
120
131
|
* when the key cannot be located — dropped rather than guessed, as
|
|
121
132
|
* {@link formatDiagnostic} requires.
|
|
122
133
|
*/
|
|
123
|
-
export function positionInFrontmatter(raw: string, key: string, value?: string
|
|
134
|
+
export function positionInFrontmatter(raw: string, key: string, value?: string, { topLevel }?: {
|
|
135
|
+
topLevel?: boolean | undefined;
|
|
136
|
+
}): {
|
|
124
137
|
line?: number;
|
|
125
138
|
column?: number;
|
|
126
139
|
};
|
|
@@ -256,11 +256,11 @@ export function defaultStats(): object;
|
|
|
256
256
|
* @param {object} [router] - The pack router. Supplied by the calling pass so
|
|
257
257
|
* the index and the compile agree about where each note landed; defaults to
|
|
258
258
|
* this repository's own.
|
|
259
|
-
* @returns {{byShortcode: Map,
|
|
259
|
+
* @returns {{byShortcode: Map, types: Set}} From `buildWikilinkIndex`.
|
|
260
260
|
*/
|
|
261
261
|
export function buildContentLinkIndex(contentBase: string, router?: object): {
|
|
262
262
|
byShortcode: Map<any, any>;
|
|
263
|
-
|
|
263
|
+
types: Set<any>;
|
|
264
264
|
};
|
|
265
265
|
/**
|
|
266
266
|
* Converts the wikilinks in one note's markdown, reporting any that have no
|
|
@@ -280,7 +280,8 @@ export function buildContentLinkIndex(contentBase: string, router?: object): {
|
|
|
280
280
|
* entry. Position is carried by `{ file, bodyLine, bodyColumn, lineMap }`,
|
|
281
281
|
* the last from {@link expandNoteTables}.
|
|
282
282
|
* @returns {{markdown: string, unresolved: Array<object>}}
|
|
283
|
-
* @throws {Error} On
|
|
283
|
+
* @throws {Error} On any link that does not resolve — an unlabelled one, a
|
|
284
|
+
* target that is not an address, or an address nothing publishes. The error
|
|
284
285
|
* carries `file` and `position`, so a caller reports it in the same form
|
|
285
286
|
* rather than re-deriving one.
|
|
286
287
|
*/
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file a homepage is written to, relative to the package's site root.
|
|
3
|
+
*
|
|
4
|
+
* Its **address**, flat at the package root, and stated in the page's own `url`
|
|
5
|
+
* — the same separation every other page has since #181, where the directory
|
|
6
|
+
* decides the Hugo section and the front matter decides the URL. Flat rather
|
|
7
|
+
* than inside a `homepage/` section directory, because a homepage is not one of
|
|
8
|
+
* a kind: a section holding exactly one page would publish a landing at
|
|
9
|
+
* `/<package>/kb/homepage/` that nothing links to and nobody wrote.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} fm - Parsed frontmatter.
|
|
12
|
+
* @returns {string} The destination filename, e.g. `homepage-root.md`.
|
|
13
|
+
* @throws {Error} When the note declares no shortcode, and so has no address.
|
|
14
|
+
*/
|
|
15
|
+
export function homepageDestination(fm: object): string;
|
|
1
16
|
/**
|
|
2
17
|
* Whether a note's frontmatter declares the homepage type.
|
|
3
18
|
*
|
|
@@ -6,26 +21,38 @@
|
|
|
6
21
|
*/
|
|
7
22
|
export function isHomepage(fm: object | null | undefined): boolean;
|
|
8
23
|
/**
|
|
9
|
-
*
|
|
24
|
+
* What the address rule says about one note's top-level fields.
|
|
10
25
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* reader and a compiler-output parser both expect.
|
|
26
|
+
* Two statements about the same thing, so they are made together: the field a
|
|
27
|
+
* homepage **owes** and the field it may **not** write.
|
|
14
28
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
29
|
+
* The missing `shortcode` comes first, and is located at `type:` rather than at
|
|
30
|
+
* a key that is not there — the `homepage` value is what makes the field
|
|
31
|
+
* required, it is a real position in the file, and inventing a `1:1` for an
|
|
32
|
+
* absent key would put the author on the opening fence. The refused fields
|
|
33
|
+
* follow in the order the note authored them, so a caller emitting one
|
|
34
|
+
* diagnostic per finding walks down the file.
|
|
17
35
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
36
|
+
* Presence is the whole test for a refused field, and absence-or-blank for the
|
|
37
|
+
* required one: `shortcode:` authored empty is no address, and a value cannot
|
|
38
|
+
* make `id` mean something on a page that compiles to no document.
|
|
39
|
+
*
|
|
40
|
+
* Each finding carries the `locator` key to position it at, because the two
|
|
41
|
+
* things that would resolve one — the raw note text and the position helper —
|
|
42
|
+
* belong to the caller. This mirrors {@link module:engine/retired-fields},
|
|
43
|
+
* whose retired-field messages are likewise positioned by whoever reports them.
|
|
22
44
|
*
|
|
23
45
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
24
|
-
* @returns {Array<{
|
|
25
|
-
*
|
|
46
|
+
* @returns {Array<{field: string, locator: {key: string, literal?: string},
|
|
47
|
+
* message: string}>} One entry per finding, empty for any note that is not a
|
|
48
|
+
* homepage and declares nothing wrong.
|
|
26
49
|
*/
|
|
27
50
|
export function checkHomepageAddressFields(fm: object | null | undefined): Array<{
|
|
28
|
-
|
|
51
|
+
field: string;
|
|
52
|
+
locator: {
|
|
53
|
+
key: string;
|
|
54
|
+
literal?: string;
|
|
55
|
+
};
|
|
29
56
|
message: string;
|
|
30
57
|
}>;
|
|
31
58
|
/**
|
|
@@ -37,12 +64,16 @@ export function checkHomepageAddressFields(fm: object | null | undefined): Array
|
|
|
37
64
|
* - _None_ and the package serves nothing at `/<package>/`. That is the failure
|
|
38
65
|
* #50 exists to prevent, and it is silent — the site build reports `wrote 0
|
|
39
66
|
* homepage(s)` and exits 0.
|
|
40
|
-
* - _Two_ and it serves a page nobody chose.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
67
|
+
* - _Two_ and it serves a page nobody chose. **This is a cardinality rule, and
|
|
68
|
+
* since #182 it is only that.** It used to rest on the fixed destination
|
|
69
|
+
* every homepage shared — the second overwrote the first — so the address
|
|
70
|
+
* rule enforced it as a side effect. A homepage is written at its own address
|
|
71
|
+
* now, so two of them publish two pages and collide over nothing; the
|
|
72
|
+
* duplicate-address check catches only the pair that happen to share a
|
|
73
|
+
* shortcode, and says nothing at all about a `homepage-root` beside a
|
|
74
|
+
* `homepage-front`. Which of the two the redirect at `/<package>/` should
|
|
75
|
+
* name is a question nothing here can answer, and both being reachable is
|
|
76
|
+
* not an answer to it.
|
|
46
77
|
*
|
|
47
78
|
* Neither has a safe default, so neither is a warning. A warning is the right
|
|
48
79
|
* severity for something a build can proceed past correctly, and a build that
|
|
@@ -106,25 +137,36 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
|
|
|
106
137
|
/**
|
|
107
138
|
* The frontmatter a homepage publishes with.
|
|
108
139
|
*
|
|
109
|
-
* The note's own, plus the
|
|
110
|
-
* resolved `title`,
|
|
140
|
+
* The note's own, plus the derived values every emitted page carries: the
|
|
141
|
+
* resolved `title`, the package the build derived — no note declares one
|
|
111
142
|
* (`package:` is retired, #56) and the theme's breadcrumb partial reads
|
|
112
|
-
* `.Params.package
|
|
143
|
+
* `.Params.package` — and its **address**.
|
|
144
|
+
*
|
|
145
|
+
* The address is stated as `url` for the same reason every other page states
|
|
146
|
+
* one (#181): Hugo publishes a page where its file sits unless told otherwise,
|
|
147
|
+
* and a homepage's file sits at the package's site root. `slug` is written
|
|
148
|
+
* beside it because it is the last segment of that address and Hugo's own key
|
|
149
|
+
* for one; it decides nothing while `url` is present, but a page carrying only
|
|
150
|
+
* `url` would report a slug Hugo had inferred from the filename.
|
|
113
151
|
*
|
|
114
152
|
* An authored `aliases` is dropped for the same reason it is on every other
|
|
115
|
-
* page:
|
|
116
|
-
*
|
|
117
|
-
*
|
|
153
|
+
* page: Hugo reads it as URL redirects, so passing it through would publish a
|
|
154
|
+
* redirect stub at each one. The field is retired (#180) and refused before a
|
|
155
|
+
* build reaches here, which makes this a guard rather than a working path.
|
|
118
156
|
*
|
|
119
157
|
* @param {object} fm - The note's frontmatter.
|
|
120
158
|
* @param {object} options - Options.
|
|
121
159
|
* @param {string} options.contentPackage - The package this build publishes.
|
|
122
160
|
* @param {string} options.title - The resolved title.
|
|
161
|
+
* @param {string} options.base - Where the package is served, with both
|
|
162
|
+
* slashes — `/<package>/`.
|
|
123
163
|
* @returns {object} The frontmatter to write.
|
|
164
|
+
* @throws {Error} When the note declares no shortcode, and so has no address.
|
|
124
165
|
*/
|
|
125
|
-
export function homepageFrontmatter(fm: object, { contentPackage, title }: {
|
|
166
|
+
export function homepageFrontmatter(fm: object, { contentPackage, title, base }: {
|
|
126
167
|
contentPackage: string;
|
|
127
168
|
title: string;
|
|
169
|
+
base: string;
|
|
128
170
|
}): object;
|
|
129
171
|
/**
|
|
130
172
|
* Every address a homepage carries, wherever it is written.
|
|
@@ -172,54 +214,48 @@ export const HOMEPAGE_TYPE: string;
|
|
|
172
214
|
*
|
|
173
215
|
* Empty on purpose, and declared rather than omitted: a type with no vocabulary
|
|
174
216
|
* and a type that is unknown are different findings, and only the second is an
|
|
175
|
-
* authoring error. The whole envelope is the
|
|
176
|
-
* optional `title`; there is no game-system data
|
|
177
|
-
* document.
|
|
217
|
+
* authoring error. The whole envelope is the top-level keys `type` and
|
|
218
|
+
* `shortcode`, plus an optional `title` or `name`; there is no game-system data
|
|
219
|
+
* on a page that compiles to no document.
|
|
178
220
|
*
|
|
179
221
|
* @type {readonly import("./field-spec.mjs").FieldSpec[]}
|
|
180
222
|
*/
|
|
181
223
|
export const HOMEPAGE_FIELDS: readonly import("./field-spec.mjs").FieldSpec[];
|
|
182
224
|
/**
|
|
183
|
-
*
|
|
225
|
+
* The shortcode a package landing conventionally takes.
|
|
184
226
|
*
|
|
185
|
-
*
|
|
186
|
-
* package
|
|
227
|
+
* A **convention, not a rule.** The address only has to be unique within the
|
|
228
|
+
* package, which `(type, shortcode)` already guarantees, and nothing here knows
|
|
229
|
+
* better than an author what their landing is called. What the constant buys is
|
|
230
|
+
* one spelling shared by the diagnostic, the documentation and the six trees —
|
|
231
|
+
* so `[[homepage-root|…]]` is the same link in every package.
|
|
187
232
|
*
|
|
188
233
|
* @type {string}
|
|
189
234
|
*/
|
|
190
|
-
export const
|
|
235
|
+
export const HOMEPAGE_SHORTCODE: string;
|
|
191
236
|
/**
|
|
192
|
-
* The top-level
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* `
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* that says nothing. It also inflates `content-build lint`'s address tally, so
|
|
207
|
-
* the lint and the link manifest disagree about what the package publishes.
|
|
208
|
-
*
|
|
209
|
-
* **A named class, not an allow-list, and that boundary is the decision.** The
|
|
210
|
-
* documented envelope is `type` plus an optional `title`, and `landing`,
|
|
211
|
-
* `description` and `banner` are legitimate beside them — but a homepage's
|
|
212
|
-
* frontmatter is *emitted into the published page*
|
|
237
|
+
* The top-level field a homepage refuses, and what it would decide (#53).
|
|
238
|
+
*
|
|
239
|
+
* **One field, where there used to be three.** `name` and `shortcode` were
|
|
240
|
+
* refused because a page's URL derived from `name.full` while a homepage's
|
|
241
|
+
* destination was fixed, so the address a `shortcode` computed named a page the
|
|
242
|
+
* site build never wrote. A page's URL is its address now (#181) and a homepage
|
|
243
|
+
* publishes at its own, so both fields decide exactly what they decide
|
|
244
|
+
* everywhere else and are permitted (#182).
|
|
245
|
+
*
|
|
246
|
+
* `id` is untouched by that, and stays: it is the Foundry document id a
|
|
247
|
+
* compendium UUID is built from, and a homepage compiles into no document.
|
|
248
|
+
*
|
|
249
|
+
* **A named class, not an allow-list, and that boundary is the decision.** A
|
|
250
|
+
* homepage's frontmatter is *emitted into the published page*
|
|
213
251
|
* ({@link homepageFrontmatter}), so an unrecognised key is a Hugo or theme
|
|
214
252
|
* parameter this build has never heard of and has no standing to refuse.
|
|
215
253
|
* Rejecting unknown keys would make every new theme parameter wait on a
|
|
216
|
-
* package-build release.
|
|
217
|
-
* false claim about *where this page is*.
|
|
254
|
+
* package-build release.
|
|
218
255
|
*
|
|
219
|
-
* `aliases` is deliberately not in the class:
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* belief about this one's address.
|
|
256
|
+
* `aliases` is deliberately not in the class: it is a **retired** field, refused
|
|
257
|
+
* on every note whatever its type (#180), so it is answered there rather than
|
|
258
|
+
* here.
|
|
223
259
|
*
|
|
224
260
|
* @type {ReadonlyMap<string, string>}
|
|
225
261
|
*/
|
package/types/engine/index.d.mts
CHANGED
|
@@ -28,7 +28,6 @@ export * as documentSubtypes from "./document-subtypes.mjs";
|
|
|
28
28
|
export * as itemDocs from "./item-docs.mjs";
|
|
29
29
|
export * as wikilinks from "./wikilinks.mjs";
|
|
30
30
|
export * as wikilinkSyntax from "./wikilink-syntax.mjs";
|
|
31
|
-
export * as aliasIndex from "./alias-index.mjs";
|
|
32
31
|
export * as siteIndex from "./site-index.mjs";
|
|
33
32
|
export * as baseCompiler from "./base-compiler.mjs";
|
|
34
33
|
export * as journals from "./journals.mjs";
|
|
@@ -6,6 +6,35 @@
|
|
|
6
6
|
* @returns {readonly string[]} The tags, in declaration order.
|
|
7
7
|
*/
|
|
8
8
|
export function declaredTags(type: string, groups?: object): readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* Whether a note carries a given tag, however the author wrote it.
|
|
11
|
+
*
|
|
12
|
+
* `tags:` is authored by hand and Obsidian is permissive about it: a single tag
|
|
13
|
+
* may be a scalar rather than a list, a value may carry the leading `#` it is
|
|
14
|
+
* written with in prose, and case and surrounding space are not significant.
|
|
15
|
+
* The spelling of the tag *itself* still is — a near miss is a near miss, and
|
|
16
|
+
* the frontmatter lint is what reports it; nothing here guesses.
|
|
17
|
+
*
|
|
18
|
+
* Reads `tags` and, as Dataview does, `tag` — the singular spelling Obsidian
|
|
19
|
+
* also accepts.
|
|
20
|
+
*
|
|
21
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
22
|
+
* @param {string} tag - The tag to look for, in its declared spelling.
|
|
23
|
+
* @returns {boolean} Whether the note carries it.
|
|
24
|
+
*/
|
|
25
|
+
export function hasTag(fm: object | null | undefined, tag: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether a note is tagged as an unfinished **draft** (#183).
|
|
28
|
+
*
|
|
29
|
+
* The one reader of {@link DRAFT_TAG}, so both builds ask the same question of
|
|
30
|
+
* the same field. Presentation only: a draft note is in the packs, in the
|
|
31
|
+
* manifest and on the site exactly as any other, and this decides nothing but
|
|
32
|
+
* whether a link into it renders marked.
|
|
33
|
+
*
|
|
34
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
35
|
+
* @returns {boolean} Whether the note carries the `draft` tag.
|
|
36
|
+
*/
|
|
37
|
+
export function isDraftNote(fm: object | null | undefined): boolean;
|
|
9
38
|
/**
|
|
10
39
|
* The `data:` keys a note type may carry.
|
|
11
40
|
*
|
|
@@ -66,6 +95,20 @@ export function subTypes(type: string, vocabulary?: Readonly<Record<string, Type
|
|
|
66
95
|
* fishing village is a `village` that is `fishing`, and the single-valued field
|
|
67
96
|
* this replaced had to spell it `Fishing Village` as a value of its own.
|
|
68
97
|
*/
|
|
98
|
+
/**
|
|
99
|
+
* The declared tag that marks a note as **unfinished** (#183).
|
|
100
|
+
*
|
|
101
|
+
* Named once and referenced from the declaration below, because a second
|
|
102
|
+
* spelling is how the two come apart: rename the tag in `DECLARED_TAGS` and a
|
|
103
|
+
* private copy elsewhere keeps matching the old word, silently.
|
|
104
|
+
*
|
|
105
|
+
* It is a **presentation** fact and nothing more. A draft note compiles,
|
|
106
|
+
* validates, publishes and resolves like any other; only a link *into* it
|
|
107
|
+
* renders marked. What it emphatically is not is the retired `draft:` field,
|
|
108
|
+
* whose entire effect was to move a note from published to unresolvable — see
|
|
109
|
+
* {@link draftRetiredMessage}.
|
|
110
|
+
*/
|
|
111
|
+
export const DRAFT_TAG: "draft";
|
|
69
112
|
export const DECLARED_TAGS: Readonly<{
|
|
70
113
|
/** What a place *is*. */
|
|
71
114
|
placeKind: Readonly<{
|
|
@@ -35,6 +35,77 @@ export function assertNoDraftField(fm: object | null | undefined, { file, absPat
|
|
|
35
35
|
file?: string | undefined;
|
|
36
36
|
absPath?: string | undefined;
|
|
37
37
|
}): void;
|
|
38
|
+
/**
|
|
39
|
+
* What a note declaring a top-level `aliases:` is told, in one place.
|
|
40
|
+
*
|
|
41
|
+
* Shared by the compile-time refusal and the frontmatter lint, because an
|
|
42
|
+
* author meets whichever of the two runs first and they should read the same.
|
|
43
|
+
* It says what the field fed and what to write instead, rather than which value
|
|
44
|
+
* to correct: no value makes declaring it right.
|
|
45
|
+
*
|
|
46
|
+
* **What it did (#180).** It was the authored half of the alias index — the
|
|
47
|
+
* namespace a bare `[[Alias]]` was looked up in. Across the three content trees
|
|
48
|
+
* not one bare link resolved through it, while the collision rule that kept it
|
|
49
|
+
* unambiguous folded in every note's `name.full` and so dictated what a note
|
|
50
|
+
* could be named (#179). The form is retired, so the list has no reader.
|
|
51
|
+
*
|
|
52
|
+
* **`name.aliases` is a different field and is not retired.** It fed the same
|
|
53
|
+
* index, but unlike the top-level list it is being kept — reserved, unread,
|
|
54
|
+
* and deliberately unmentioned by this message, which would otherwise tell an
|
|
55
|
+
* author to delete a field they are allowed to write. See
|
|
56
|
+
* {@link assertNoAliasesField}.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} [file] - The note's path, named in the message. Omit it where
|
|
59
|
+
* the caller emits through a diagnostic, whose locator already starts the
|
|
60
|
+
* line — repeating it prints the path twice.
|
|
61
|
+
* @returns {string} The message, unpunctuated at the end as a finding is.
|
|
62
|
+
*/
|
|
63
|
+
export function aliasesRetiredMessage(file?: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Refuse a note that declares a top-level `aliases:`.
|
|
66
|
+
*
|
|
67
|
+
* Presence is the whole test. `aliases: []` is as retired as a populated one —
|
|
68
|
+
* it reads as "this note claims no other names", a statement about a namespace
|
|
69
|
+
* that no longer exists.
|
|
70
|
+
*
|
|
71
|
+
* **The nested `name.aliases` is deliberately not refused.** Both spellings fed
|
|
72
|
+
* the retired alias index, and both lost their reader with it, but only the
|
|
73
|
+
* top-level one is retired: `name.aliases` is **reserved**, held for a use that
|
|
74
|
+
* does not exist yet. So it is neither refused nor read — no index consults it,
|
|
75
|
+
* no rule validates its contents, nothing derives from it, and nothing emits
|
|
76
|
+
* it. It rides in the note as inert data, and a note carrying one compiles,
|
|
77
|
+
* resolves and emits exactly as if it were absent.
|
|
78
|
+
*
|
|
79
|
+
* That distinction is the reason this checks `Object.hasOwn(fm, "aliases")`
|
|
80
|
+
* rather than resolving a dotted key: the top-level field is the whole subject,
|
|
81
|
+
* and reaching into `name` at all is the thing being avoided.
|
|
82
|
+
*
|
|
83
|
+
* @param {object|null|undefined} fm - Parsed frontmatter, or nothing when it
|
|
84
|
+
* could not be parsed.
|
|
85
|
+
* @param {object} [options] - Options.
|
|
86
|
+
* @param {string} [options.file] - The note's path, named in the message. Omit
|
|
87
|
+
* it where the caller emits through a diagnostic, which puts the locator at
|
|
88
|
+
* the start of the line already — repeating it prints the path twice.
|
|
89
|
+
* @param {string} [options.absPath] - The note's file on disk, read only on the
|
|
90
|
+
* failing path to locate the offending line and column. The position rides on
|
|
91
|
+
* the thrown error as `position`, for a caller that emits a diagnostic.
|
|
92
|
+
* @returns {void}
|
|
93
|
+
* @throws {Error} When the note declares a top-level `aliases`.
|
|
94
|
+
*/
|
|
95
|
+
export function assertNoAliasesField(fm: object | null | undefined, { file, absPath }?: {
|
|
96
|
+
file?: string | undefined;
|
|
97
|
+
absPath?: string | undefined;
|
|
98
|
+
}): void;
|
|
99
|
+
/**
|
|
100
|
+
* Whether a note declares the retired top-level `aliases:`.
|
|
101
|
+
*
|
|
102
|
+
* A nested `name.aliases` is **not** this field and never answers true here —
|
|
103
|
+
* see {@link assertNoAliasesField} for why the two part company.
|
|
104
|
+
*
|
|
105
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
106
|
+
* @returns {boolean} Whether the retired field is declared.
|
|
107
|
+
*/
|
|
108
|
+
export function declaresRetiredAliasesField(fm: object | null | undefined): boolean;
|
|
38
109
|
/**
|
|
39
110
|
* A frontmatter key's position in a note's file, or nothing.
|
|
40
111
|
*
|
|
@@ -47,11 +118,17 @@ export function assertNoDraftField(fm: object | null | undefined, { file, absPat
|
|
|
47
118
|
* @param {string} [value] - When given, prefer the occurrence whose line also
|
|
48
119
|
* carries this text — so a finding about one entry of a block opens on that
|
|
49
120
|
* entry rather than on the key that introduces it.
|
|
121
|
+
* @param {object} [options] - Options, forwarded to
|
|
122
|
+
* {@link positionInFrontmatter}.
|
|
123
|
+
* @param {boolean} [options.topLevel=false] - Require the key at column 1, so
|
|
124
|
+
* an identically named nested key cannot answer for it.
|
|
50
125
|
* @returns {{line?: number, column?: number}|undefined} Spreadable position
|
|
51
126
|
* fields, dropped rather than guessed when the file cannot be read or the key
|
|
52
127
|
* cannot be found — as `formatDiagnostic` requires.
|
|
53
128
|
*/
|
|
54
|
-
export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string
|
|
129
|
+
export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string, { topLevel }?: {
|
|
130
|
+
topLevel?: boolean | undefined;
|
|
131
|
+
}): {
|
|
55
132
|
line?: number;
|
|
56
133
|
column?: number;
|
|
57
134
|
} | undefined;
|