@heroiclands/package-build 22.6.0 → 22.8.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 +42 -0
- package/CONTENT.md +5 -0
- package/content-config.mjs +78 -0
- package/docs/api.md +39 -0
- package/docs/configuration.md +57 -3
- package/docs/content-format.md +76 -15
- package/engine/calendars.mjs +132 -0
- package/engine/content-format-check.mjs +10 -1
- package/engine/draft-notice.mjs +207 -0
- package/engine/frontmatter-lint.mjs +26 -0
- package/engine/index.mjs +9 -0
- package/engine/infobox.mjs +7 -2
- package/engine/item-docs.mjs +9 -8
- package/engine/journals.mjs +23 -4
- package/engine/note-dates.mjs +344 -0
- package/engine/note-vocabulary.mjs +26 -2
- package/engine/pdf-build.mjs +8 -1
- package/engine/pdf-render.mjs +16 -0
- package/engine/retired-fields.mjs +58 -8
- package/hm3/actors.mjs +5 -1
- package/package.json +1 -1
- package/sohl/actors.mjs +6 -1
- package/types/content-config.d.mts +10 -0
- package/types/engine/calendars.d.mts +115 -0
- package/types/engine/draft-notice.d.mts +99 -0
- package/types/engine/index.d.mts +3 -0
- package/types/engine/item-docs.d.mts +9 -8
- package/types/engine/journals.d.mts +7 -1
- package/types/engine/note-dates.d.mts +70 -0
- package/types/engine/retired-fields.d.mts +23 -2
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **What a draft note says about itself**, in one sentence, on every surface it
|
|
16
|
+
* reaches.
|
|
17
|
+
*
|
|
18
|
+
* A note tagged `draft` compiles, validates, publishes and resolves like any
|
|
19
|
+
* other. What separates it from a finished note is a judgement only its author
|
|
20
|
+
* holds — the content is not settled — and a reader who is not told assumes the
|
|
21
|
+
* opposite. So each surface states it where the reader cannot miss it: at the
|
|
22
|
+
* head of the entry, above the infobox and above the prose.
|
|
23
|
+
*
|
|
24
|
+
* **The words are the same everywhere and are stated once here.** A reader who
|
|
25
|
+
* meets the same note on a page, in a compendium and in the book should meet
|
|
26
|
+
* one statement, not three that agree approximately. {@link DRAFT_NOTICE} is
|
|
27
|
+
* that statement; every renderer below composes its own markup around it and
|
|
28
|
+
* changes none of it.
|
|
29
|
+
*
|
|
30
|
+
* **A label alone will not do.** "Draft" names the writing, so it reads as
|
|
31
|
+
* rough prose; what has to land is that the facts may change. The label and the
|
|
32
|
+
* sentence are one unit, and the sentence carries the meaning by itself — which
|
|
33
|
+
* is the floor each surface is built to, because each of them can lose
|
|
34
|
+
* everything around it.
|
|
35
|
+
*
|
|
36
|
+
* **The label is bold roman and the sentence is italic**, on every surface. The
|
|
37
|
+
* words around it are the world describing itself; this one sentence is the
|
|
38
|
+
* text speaking about its own state, and italic is how a reader is told the
|
|
39
|
+
* voice has changed. It is the one typographic mark the sentence carries:
|
|
40
|
+
* smaller or lighter type would say the reader may skip it, which is the
|
|
41
|
+
* opposite of what it is for, and a bold italic beside the mark would be one
|
|
42
|
+
* emphasis too many.
|
|
43
|
+
*
|
|
44
|
+
* ## What each surface can rely on
|
|
45
|
+
*
|
|
46
|
+
* - **Foundry** renders a JournalEntry's own HTML inside a sheet, so the floor
|
|
47
|
+
* is what a journal page carries with no module and no system stylesheet
|
|
48
|
+
* loaded. Foundry draws a left rule on a `blockquote` in any application
|
|
49
|
+
* window and bundles Font Awesome, so the rule and the mark are the host's
|
|
50
|
+
* and nothing here ships CSS. The class is a hook for a package that wants
|
|
51
|
+
* one, never a requirement.
|
|
52
|
+
* - **The book** sets Typst, where a glyph missing from the face is silent and
|
|
53
|
+
* a consumer declaring no icon family is the normal case — see {@link
|
|
54
|
+
* module:engine/content-icons}. So the mark is **drawn**, from two
|
|
55
|
+
* primitives, and cannot become a box.
|
|
56
|
+
* - **An Actor's prose** lands in a system's own sheet, in a field the system
|
|
57
|
+
* styles. It gets the same HTML the journal does; what it can rely on is the
|
|
58
|
+
* sentence.
|
|
59
|
+
*
|
|
60
|
+
* @module
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import { isDraftNote } from "./note-vocabulary.mjs";
|
|
64
|
+
|
|
65
|
+
/** The word that opens the notice, and the only part a reader reads as a label. */
|
|
66
|
+
export const DRAFT_NOTICE_LABEL = "Draft.";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The sentence, which states the fact and stops.
|
|
70
|
+
*
|
|
71
|
+
* It says the content may change rather than that the writing is rough,
|
|
72
|
+
* because that is the difference between a reader who treats the entry as
|
|
73
|
+
* provisional and one who builds on it. It gives no instruction: a notice that
|
|
74
|
+
* reaches one page in seven wears better as a statement than as a direction,
|
|
75
|
+
* and the fact carries the consequence for anyone who can act on it.
|
|
76
|
+
*
|
|
77
|
+
* @type {string}
|
|
78
|
+
*/
|
|
79
|
+
export const DRAFT_NOTICE =
|
|
80
|
+
"This entry is unfinished. What it states may change, and nothing in it is settled.";
|
|
81
|
+
|
|
82
|
+
/** The Font Awesome mark: a notice, not a hazard. */
|
|
83
|
+
export const DRAFT_NOTICE_ICON = "fa-circle-exclamation";
|
|
84
|
+
|
|
85
|
+
/** The class a package may style the HTML notice against. */
|
|
86
|
+
export const DRAFT_NOTICE_CLASS = "sohl-draft-notice";
|
|
87
|
+
|
|
88
|
+
/** The Typst function the book's notice calls. */
|
|
89
|
+
export const BOOK_DRAFT_NOTICE = "book-draft-notice";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The notice as HTML, for a JournalEntry page and for an Actor's prose field.
|
|
93
|
+
*
|
|
94
|
+
* A `blockquote` because that is the element Foundry already sets apart —
|
|
95
|
+
* `body.game .app blockquote` carries a left rule and an indent — so the
|
|
96
|
+
* treatment survives with no stylesheet of this package's own. The mark is a
|
|
97
|
+
* Font Awesome element, which Foundry bundles; `aria-hidden` keeps it out of a
|
|
98
|
+
* screen reader, where the label reads it. The sentence is an `em`, which every
|
|
99
|
+
* renderer of HTML sets in italic with no stylesheet to ask.
|
|
100
|
+
*
|
|
101
|
+
* @returns {string} The notice's HTML.
|
|
102
|
+
*/
|
|
103
|
+
export function draftNoticeHtml() {
|
|
104
|
+
return (
|
|
105
|
+
`<blockquote class="${DRAFT_NOTICE_CLASS}">` +
|
|
106
|
+
`<p><i class="fa-solid ${DRAFT_NOTICE_ICON}" aria-hidden="true"></i> ` +
|
|
107
|
+
`<strong>${DRAFT_NOTICE_LABEL}</strong> <em>${DRAFT_NOTICE}</em></p>` +
|
|
108
|
+
`</blockquote>`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The notice a note has earned, or nothing.
|
|
114
|
+
*
|
|
115
|
+
* The one place the tag is asked about on a compiled document, so the packs and
|
|
116
|
+
* the book cannot answer it differently. It reads {@link
|
|
117
|
+
* module:engine/note-vocabulary.isDraftNote}, which is the single reader of the
|
|
118
|
+
* tag itself.
|
|
119
|
+
*
|
|
120
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
121
|
+
* @returns {string} The notice's HTML, or `""` for a note that is settled.
|
|
122
|
+
*/
|
|
123
|
+
export function draftNoticeFor(fm) {
|
|
124
|
+
return isDraftNote(fm) ? draftNoticeHtml() : "";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Prose led by the notice, where the note is a draft.
|
|
129
|
+
*
|
|
130
|
+
* **Which field a system leads is the system's decision, and it makes it
|
|
131
|
+
* once.** A referee at the table opens an actor sheet rather than the
|
|
132
|
+
* documentation beside it, so the prose a sheet draws says it too — on one
|
|
133
|
+
* field, since a document showing the same statement twice teaches a reader to
|
|
134
|
+
* skip it. Both systems put it on the dossier: SoHL's `dossier` and HM3's
|
|
135
|
+
* `biography` read the same authored section, so the notice lands on the same
|
|
136
|
+
* prose whichever system compiles it.
|
|
137
|
+
*
|
|
138
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
139
|
+
* @param {string} html - The rendered prose.
|
|
140
|
+
* @returns {string} The prose, led by the notice where one is due.
|
|
141
|
+
*/
|
|
142
|
+
export function withDraftNotice(fm, html) {
|
|
143
|
+
const notice = draftNoticeFor(fm);
|
|
144
|
+
if (!notice) return html;
|
|
145
|
+
return html ? `${notice}\n${html}` : notice;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The `#let` the book's notice is drawn by.
|
|
150
|
+
*
|
|
151
|
+
* Emitted with the rest of the book's preamble. The mark is a disc with an
|
|
152
|
+
* exclamation knocked out of it: two primitives and the face already setting
|
|
153
|
+
* the page, so there is no font to resolve and nothing that can come out as a
|
|
154
|
+
* box. It is filled in the accent the book rules its headings with and the
|
|
155
|
+
* glyph is the colour of the paper, so the mark carries against the running
|
|
156
|
+
* face rather than reading as a character of it.
|
|
157
|
+
*
|
|
158
|
+
* **The mark is a column, not a word.** A two-column grid puts it beside the
|
|
159
|
+
* sentence rather than inside it, so a notice that takes two lines sets its
|
|
160
|
+
* second line against the first and never under the disc; the grid centres the
|
|
161
|
+
* mark's cell on the block, so it holds the middle of one line or two. The rule
|
|
162
|
+
* is the block's own left stroke and spans whatever height the sentence takes.
|
|
163
|
+
*
|
|
164
|
+
* **The sentence sets in the book's body face, size and ink**, because it is
|
|
165
|
+
* the one thing in the entry a reader has to read before trusting the rest. The
|
|
166
|
+
* rule and the mark set the block apart; nothing else has to, and type set
|
|
167
|
+
* smaller or lighter than the prose around it reads as apparatus a reader may
|
|
168
|
+
* skip. The mark scales with that text, so a consumer setting the book larger
|
|
169
|
+
* or smaller carries the disc with it.
|
|
170
|
+
*
|
|
171
|
+
* **One number sets the mark.** The glyph is a multiple of the disc's radius
|
|
172
|
+
* rather than a size of its own, so the exclamation keeps its share of the disc
|
|
173
|
+
* however large the disc is drawn and resizing the mark is one edit. Both are
|
|
174
|
+
* `em`, so the mark still follows the body text.
|
|
175
|
+
*
|
|
176
|
+
* @returns {string} The Typst definition.
|
|
177
|
+
*/
|
|
178
|
+
export function bookDraftNoticePreamble() {
|
|
179
|
+
return (
|
|
180
|
+
`#let ${BOOK_DRAFT_NOTICE}(label, body) = block(\n` +
|
|
181
|
+
" width: 100%, above: 0.55em, below: 0.75em, breakable: false,\n" +
|
|
182
|
+
" inset: (left: 0.7em, top: 0.35em, bottom: 0.35em),\n" +
|
|
183
|
+
" stroke: (left: 1.6pt + book-faint))[\n" +
|
|
184
|
+
" #set par(justify: false, first-line-indent: 0em)\n" +
|
|
185
|
+
" #let radius = 0.69em\n" +
|
|
186
|
+
" #grid(columns: (auto, 1fr), column-gutter: 0.5em,\n" +
|
|
187
|
+
" align: (center + horizon, horizon),\n" +
|
|
188
|
+
" circle(radius: radius, fill: book-accent)[\n" +
|
|
189
|
+
" #align(center + horizon)[\n" +
|
|
190
|
+
' #text(size: 1.6 * radius, weight: "bold", fill: book-paper)[!]\n' +
|
|
191
|
+
" ]\n" +
|
|
192
|
+
" ],\n" +
|
|
193
|
+
' [#text(weight: "bold")[#label]#h(0.25em)#emph[#body]],\n' +
|
|
194
|
+
" )\n" +
|
|
195
|
+
"]"
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The notice as Typst, for a leaf of the book.
|
|
201
|
+
*
|
|
202
|
+
* @returns {string} The call, ready to sit between the entry's plate and its
|
|
203
|
+
* infobox panel.
|
|
204
|
+
*/
|
|
205
|
+
export function draftNoticeTypst() {
|
|
206
|
+
return `#${BOOK_DRAFT_NOTICE}[${DRAFT_NOTICE_LABEL}][${DRAFT_NOTICE}]`;
|
|
207
|
+
}
|
|
@@ -455,8 +455,34 @@ function checkDataContainer(note, { type, fields, packs }) {
|
|
|
455
455
|
/** First segment of each declared name — `charges.value` is authored as `charges`. */
|
|
456
456
|
const declared = new Set(fields.map((f) => f.name.split(".")[0]));
|
|
457
457
|
|
|
458
|
+
// The retired spelling of a key this type declares → what to write now.
|
|
459
|
+
// Built from the type's own declaration, so a renamed `data:` key is
|
|
460
|
+
// retired exactly where its replacement exists and stays an unrecognised
|
|
461
|
+
// key on every other type.
|
|
462
|
+
const renamed = new Map();
|
|
463
|
+
for (const name of declared) {
|
|
464
|
+
const retired = RETIRED_FIELD_ALIASES[name];
|
|
465
|
+
if (retired) renamed.set(retired, name);
|
|
466
|
+
}
|
|
467
|
+
|
|
458
468
|
for (const key of Object.keys(entries)) {
|
|
459
469
|
if (declared.has(key)) continue;
|
|
470
|
+
const current = renamed.get(key);
|
|
471
|
+
if (current) {
|
|
472
|
+
findings.push({
|
|
473
|
+
file: note.file,
|
|
474
|
+
...positionOfFrontmatterPath(raw, ["data", key], { key: true }),
|
|
475
|
+
// A warning, as every retired spelling is: the note compiles to
|
|
476
|
+
// the correct document, so failing a build over it would red a
|
|
477
|
+
// tree that has done nothing wrong yet. The refusal comes once
|
|
478
|
+
// no tree writes the key, and needs no code — with the alias
|
|
479
|
+
// gone the spelling is an unrecognised key again, which the
|
|
480
|
+
// loop below already refuses.
|
|
481
|
+
severity: "warning",
|
|
482
|
+
message: retiredAliasMessage(key, current),
|
|
483
|
+
});
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
460
486
|
const guess = nearest(key, declared);
|
|
461
487
|
findings.push({
|
|
462
488
|
file: note.file,
|
package/engine/index.mjs
CHANGED
|
@@ -87,6 +87,9 @@ export * as noteVocabulary from "./note-vocabulary.mjs";
|
|
|
87
87
|
/** The three states of a note — stub, draft, full — and the empty body they rest on. */
|
|
88
88
|
export * as noteState from "./note-state.mjs";
|
|
89
89
|
|
|
90
|
+
/** What a draft note says about itself, in one sentence, on every surface. */
|
|
91
|
+
export * as draftNotice from "./draft-notice.mjs";
|
|
92
|
+
|
|
90
93
|
/** Whether every empty body is a stub on purpose, and how finished a tree is. */
|
|
91
94
|
export * as stubLint from "./stub-lint.mjs";
|
|
92
95
|
|
|
@@ -121,6 +124,12 @@ export * as siteMaps from "./site-maps.mjs";
|
|
|
121
124
|
/** The closed registry of system ids, and the `none` that stands for no system. */
|
|
122
125
|
export * as systems from "./systems.mjs";
|
|
123
126
|
|
|
127
|
+
/** The reckonings a package dates by, and the arithmetic that puts them on one line. */
|
|
128
|
+
export * as calendars from "./calendars.mjs";
|
|
129
|
+
|
|
130
|
+
/** A note's dates: one authored string, one grammar, one record shape. */
|
|
131
|
+
export * as noteDates from "./note-dates.mjs";
|
|
132
|
+
|
|
124
133
|
/** The one normalisation this build makes: prose to a URL-safe token. */
|
|
125
134
|
export * as contentSlug from "./content-slug.mjs";
|
|
126
135
|
|
package/engine/infobox.mjs
CHANGED
|
@@ -115,6 +115,7 @@
|
|
|
115
115
|
import { getFrontmatter } from "./frontmatter.mjs";
|
|
116
116
|
import { currentType } from "./ids.mjs";
|
|
117
117
|
import { NOTE_VOCABULARY, dataFields } from "./note-vocabulary.mjs";
|
|
118
|
+
import { readAliasedField } from "./retired-fields.mjs";
|
|
118
119
|
import { subtypeRow } from "./document-subtypes.mjs";
|
|
119
120
|
|
|
120
121
|
/**
|
|
@@ -272,7 +273,6 @@ export const NOTE_FIELD_PRESENTATION = Object.freeze({
|
|
|
272
273
|
banner: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
273
274
|
overlay: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
274
275
|
|
|
275
|
-
birthday: Object.freeze({ label: "Born" }),
|
|
276
276
|
assocSkill: Object.freeze({ label: "Skill" }),
|
|
277
277
|
assocAffiliation: Object.freeze({ label: "Affiliation" }),
|
|
278
278
|
parentSkill: Object.freeze({ label: "Specialises" }),
|
|
@@ -637,7 +637,12 @@ function noteBox(
|
|
|
637
637
|
for (const field of dataFields(fm?.type, vocabulary) ?? []) {
|
|
638
638
|
const overlay = overlayFor(presentation, fm?.type, field.name);
|
|
639
639
|
if (overlay.withheld) continue;
|
|
640
|
-
|
|
640
|
+
// The retired spelling underneath, so a note part-way through a rename
|
|
641
|
+
// still shows its value rather than losing the row with nothing said.
|
|
642
|
+
// The current name wins, which is the whole of the retirement window's
|
|
643
|
+
// behaviour and is `readAliasedField`'s answer, not a second one.
|
|
644
|
+
const own = getFrontmatter(data, field.name, undefined);
|
|
645
|
+
const raw = hasValue(own) ? own : readAliasedField(fm, field.name, { inData: true });
|
|
641
646
|
if (!hasValue(raw)) continue;
|
|
642
647
|
|
|
643
648
|
if (overlay.group) {
|
package/engine/item-docs.mjs
CHANGED
|
@@ -64,10 +64,10 @@ export { itemTypes };
|
|
|
64
64
|
* Every content type whose **prose compiles into a JournalEntry of its own**,
|
|
65
65
|
* addressed by the virtual `doc<type>` qualifier.
|
|
66
66
|
*
|
|
67
|
-
* Every item type, plus `macro` — a macro
|
|
68
|
-
* note also compiles into a Macro, which
|
|
69
|
-
* and its description: one note, two documents,
|
|
70
|
-
* journals pack.
|
|
67
|
+
* Every item type, every actor type, every map type, plus `macro` — a macro
|
|
68
|
+
* note's body documents the script the note also compiles into a Macro, which
|
|
69
|
+
* is the same shape as an item and its description: one note, two documents,
|
|
70
|
+
* the prose living in the journals pack.
|
|
71
71
|
*
|
|
72
72
|
* **One set, read by the compiler and the emitter alike.** The journals pass
|
|
73
73
|
* decides what to compile from it, and the link manifest decides what to
|
|
@@ -76,8 +76,9 @@ export { itemTypes };
|
|
|
76
76
|
* consumer can address. It is composed exactly once, in `defineConfig`, and
|
|
77
77
|
* read from there — never recomposed at a call site.
|
|
78
78
|
*
|
|
79
|
-
* `doc` notes
|
|
80
|
-
*
|
|
79
|
+
* `doc` notes are absent, for the reason that applies to them alone: a `doc`
|
|
80
|
+
* note's single document *is* its prose, so there is no second document for a
|
|
81
|
+
* `doc<type>` address to name.
|
|
81
82
|
*
|
|
82
83
|
* An accessor rather than a hoisted constant, so that importing this module
|
|
83
84
|
* needs no configuration (#2).
|
|
@@ -93,8 +94,8 @@ export function docEntryTypes() {
|
|
|
93
94
|
* its own.
|
|
94
95
|
*
|
|
95
96
|
* @param {string} type - The note's `type` frontmatter.
|
|
96
|
-
* @returns {boolean} True for an item type,
|
|
97
|
-
* false for `doc
|
|
97
|
+
* @returns {boolean} True for an item type, an actor type, a map type and
|
|
98
|
+
* `macro`; false for `doc`.
|
|
98
99
|
*/
|
|
99
100
|
export function hasDocEntry(type) {
|
|
100
101
|
// Through {@link currentType}, because the set is derived from the item
|
package/engine/journals.mjs
CHANGED
|
@@ -18,14 +18,17 @@
|
|
|
18
18
|
* The content root (`contentBase`) is walked recursively; any `.md` file
|
|
19
19
|
* whose frontmatter declares either `type: doc` or a
|
|
20
20
|
* **doc-carrying type** ({@link sohl.utils.packs.docEntryTypes} — every item
|
|
21
|
-
* type, plus `macro`) is compiled into one
|
|
21
|
+
* type, every actor type, every map type, plus `macro`) is compiled into one
|
|
22
|
+
* JournalEntry document. Each note's
|
|
22
23
|
* body is split on top-level H1 headings; the optional content before the
|
|
23
24
|
* first H1 becomes a lead page, and each subsequent H1 starts a new page named
|
|
24
25
|
* after its heading text. All page bodies are rendered to HTML.
|
|
25
26
|
*
|
|
26
27
|
* A doc-carrying note compiles into that document's **documentation** — the
|
|
27
|
-
* same prose and pages, filed in the same folder as the document itself
|
|
28
|
-
* keeps only a pointer to it
|
|
28
|
+
* same prose and pages, filed in the same folder as the document itself. An
|
|
29
|
+
* item keeps only a pointer to it; an actor keeps its own inline prose as
|
|
30
|
+
* well, because the indirection an item pays for is bought by embedding one
|
|
31
|
+
* item across hundreds of beings and an actor is singular. See `item-docs.mjs` for why, and for the ids the
|
|
29
32
|
* two passes agree on. A macro note's `{#script}` page is compiled here like
|
|
30
33
|
* any other: the macro pass reads the same page independently, and withholds
|
|
31
34
|
* nothing from the journal.
|
|
@@ -56,6 +59,7 @@ import { noteInfoboxes } from "./infobox-registry.mjs";
|
|
|
56
59
|
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
57
60
|
import { JOURNAL_TYPES } from "./ids.mjs";
|
|
58
61
|
import { journalHasContent } from "./note-state.mjs";
|
|
62
|
+
import { draftNoticeFor } from "./draft-notice.mjs";
|
|
59
63
|
|
|
60
64
|
/**
|
|
61
65
|
* Splits a markdown body into pages by top-level H1 headings. Fenced
|
|
@@ -287,6 +291,11 @@ export function buildPages(rawPages, entryId, noteName) {
|
|
|
287
291
|
* that reads. It is **not** a page of its own — a page is what a UUID
|
|
288
292
|
* addresses, and a summary a reader has to navigate to is a summary they do
|
|
289
293
|
* not see.
|
|
294
|
+
* @param {string} [params.notice] - A statement about the entry rather than
|
|
295
|
+
* about its subject, already rendered to HTML — see {@link
|
|
296
|
+
* module:engine/draft-notice}. It leads the first page, ahead of the infobox,
|
|
297
|
+
* because a reader deciding whether to rely on the entry has to be told
|
|
298
|
+
* before they read it rather than after.
|
|
290
299
|
* @returns {object} The JournalEntry document, keyed for the pack.
|
|
291
300
|
*/
|
|
292
301
|
export function buildJournalEntry({
|
|
@@ -298,12 +307,16 @@ export function buildJournalEntry({
|
|
|
298
307
|
flags,
|
|
299
308
|
stats = defaultStats(),
|
|
300
309
|
infobox = "",
|
|
310
|
+
notice = "",
|
|
301
311
|
}) {
|
|
302
312
|
const rawPages = splitPages(markdown, leadName);
|
|
303
313
|
const pages = buildPages(rawPages, id, name);
|
|
304
314
|
if (infobox.trim() && pages.length) {
|
|
305
315
|
pages[0].text.content = `${infobox}\n${pages[0].text.content}`;
|
|
306
316
|
}
|
|
317
|
+
if (notice.trim() && pages.length) {
|
|
318
|
+
pages[0].text.content = `${notice}\n${pages[0].text.content}`;
|
|
319
|
+
}
|
|
307
320
|
return {
|
|
308
321
|
name,
|
|
309
322
|
pages,
|
|
@@ -407,7 +420,7 @@ export class Journals extends BasePackCompiler {
|
|
|
407
420
|
* Compile one note into a JournalEntry.
|
|
408
421
|
*
|
|
409
422
|
* A `doc` note becomes the entry its frontmatter describes. A
|
|
410
|
-
* **doc-carrying note** — every item
|
|
423
|
+
* **doc-carrying note** — every item, actor, map and macro note — becomes
|
|
411
424
|
* that document's documentation instead: the same prose, the same pages,
|
|
412
425
|
* in the same folder, under an id derived from the note's, so the pointer
|
|
413
426
|
* the items pass wrote resolves to it (see
|
|
@@ -458,6 +471,12 @@ export class Journals extends BasePackCompiler {
|
|
|
458
471
|
name,
|
|
459
472
|
markdown,
|
|
460
473
|
infobox: infoboxesToHtml(boxes, { link: linkToUuid }),
|
|
474
|
+
// Whether the entry is settled, asked of the tag through the one
|
|
475
|
+
// reader of it. This sits here rather than in `buildJournalEntry`
|
|
476
|
+
// for the reason `skipNote` runs first: a note with no prose
|
|
477
|
+
// compiles into nothing, and a notice composed before that test
|
|
478
|
+
// would be content enough to give it an entry.
|
|
479
|
+
notice: draftNoticeFor(fm),
|
|
461
480
|
// A doc-carrying note's lead page is the document itself, not an
|
|
462
481
|
// "Introduction" — see {@link splitPages}.
|
|
463
482
|
leadName: ownsDoc ? name : undefined,
|