@heroiclands/package-build 23.10.0 → 23.12.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 +28 -0
- package/README.md +1 -0
- package/assets/preview/check-sanitizer.go +68 -0
- package/assets/preview/gitea-theme.js +36 -0
- package/assets/preview/gitea.ini +127 -0
- package/assets/styles/content-base.css +119 -16
- package/bin/content-commands.mjs +2 -2
- package/bin/package-build-preview.mjs +75 -0
- package/content-config.mjs +11 -0
- package/docs/api.md +131 -45
- package/docs/authoring/first-note.md +10 -0
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +111 -6
- package/docs/commands.md +46 -2
- package/docs/configuration.md +21 -4
- package/docs/content-format.md +4 -2
- package/docs/design.md +19 -0
- package/docs/guides/book.md +11 -0
- package/docs/guides/site.md +7 -0
- package/docs/reference/format-details.md +82 -27
- package/docs/reference/note-types.md +0 -1
- package/docs/reference/theme-pages.md +1 -1
- package/docs/source-preview.md +172 -0
- package/engine/anchors.mjs +36 -4
- package/engine/base-compiler.mjs +7 -8
- package/engine/being-age.mjs +19 -105
- package/engine/calendar-notes.mjs +3 -25
- package/engine/compile-corpus.mjs +2 -1
- package/engine/content-format.yaml +0 -1
- package/engine/content-index.mjs +9 -9
- package/engine/content-links.mjs +41 -18
- package/engine/content-spans.mjs +54 -7
- package/engine/current-date.mjs +37 -0
- package/engine/definition-list.mjs +58 -0
- package/engine/frontmatter-lint.mjs +7 -2
- package/engine/helpers.mjs +6 -2
- package/engine/index.mjs +2 -0
- package/engine/infobox.mjs +1 -5
- package/engine/note-vocabulary.mjs +2 -2
- package/engine/npc-generate.mjs +7 -6
- package/engine/pdf-render.mjs +36 -3
- package/engine/preview-current-date.mjs +60 -0
- package/engine/site-build.mjs +5 -5
- package/engine/site-index.mjs +28 -6
- package/engine/site-preview.mjs +641 -325
- package/engine/web-wikilinks.mjs +33 -12
- package/engine/wikilink-syntax.mjs +65 -8
- package/engine/wikilinks.mjs +29 -11
- package/hugo-theme/layouts/_default/list.html +1 -1
- package/hugo-theme/layouts/blog/list.html +1 -1
- package/hugo-theme/layouts/blog-post/single.html +1 -1
- package/hugo-theme/layouts/partials/dictionary-lists.html +53 -0
- package/hugo-theme/layouts/partials/footer.html +2 -0
- package/hugo-theme/layouts/partials/page-body.html +1 -1
- package/hugo-theme/static/css/style.css +39 -9
- package/hugo-theme/static/js/inline-spoilers.js +23 -0
- package/package.json +8 -2
- package/types/content-config.d.mts +8 -0
- package/types/engine/anchors.d.mts +2 -1
- package/types/engine/base-compiler.d.mts +1 -1
- package/types/engine/being-age.d.mts +10 -44
- package/types/engine/calendar-notes.d.mts +2 -6
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-index.d.mts +3 -1
- package/types/engine/content-spans.d.mts +7 -1
- package/types/engine/current-date.d.mts +9 -0
- package/types/engine/definition-list.d.mts +8 -0
- package/types/engine/frontmatter-lint.d.mts +6 -2
- package/types/engine/index.d.mts +2 -0
- package/types/engine/npc-generate.d.mts +2 -2
- package/types/engine/preview-current-date.d.mts +8 -0
- package/types/engine/site-index.d.mts +5 -1
- package/types/engine/site-preview.d.mts +40 -33
- package/types/engine/wikilink-syntax.d.mts +40 -3
package/docs/design.md
CHANGED
|
@@ -21,6 +21,25 @@ An Address identifies a subject by package, system, type, and shortcode. It is i
|
|
|
21
21
|
|
|
22
22
|
The content index uses JSON Lines. A record can retain the note's full frontmatter, anchors, and outgoing links, and streams without loading one giant JSON array. It is derived, disposable, and stable for a fixed source tree and toolchain version. SQL queries and editor navigation read that index rather than guessing from filenames.
|
|
23
23
|
|
|
24
|
+
## Reference presentation and access
|
|
25
|
+
|
|
26
|
+
An indexed subject has one ordinary wikilink syntax regardless of its audience.
|
|
27
|
+
The renderer derives spoiler presentation from the actual destination, so an
|
|
28
|
+
author does not duplicate a privacy declaration on every mention. The `.show`
|
|
29
|
+
override reveals a particular reference without changing the destination's
|
|
30
|
+
audience. A separate inline `.secret` span marks authored text as a spoiler.
|
|
31
|
+
|
|
32
|
+
Concealment and access remain separate decisions. Public site and book builds
|
|
33
|
+
omit GM-only notes; a reference to an omitted destination carries no unusable
|
|
34
|
+
link. Foundry's private packs and page permissions govern destination access.
|
|
35
|
+
Web inline spoilers use a separate show/hide control beside their content, so
|
|
36
|
+
a revealed link is independently usable. Foundry inline spoilers use a CSS
|
|
37
|
+
redaction revealed on hover or keyboard focus; they have no native secret-section
|
|
38
|
+
permission behavior. Books ignore inline `.secret` and preserve normal text
|
|
39
|
+
formatting. A spoiler in published HTML remains readable in the source. See the
|
|
40
|
+
[authoring contract](authoring/links-and-markup.md#references-to-secret-subjects)
|
|
41
|
+
for syntax and surface presentation.
|
|
42
|
+
|
|
24
43
|
## Build boundaries
|
|
25
44
|
|
|
26
45
|
The content pass reads `assets/content/` and addressable assets. It emits the content index, page source, and Foundry documents. Site rendering and book typesetting consume those outputs; their layout decisions do not become fields of a Foundry DataModel. Packaging stages only the files a Foundry manifest declares. The build checks emitted fields against the target system schema because Foundry can otherwise discard an unknown field without an error.
|
package/docs/guides/book.md
CHANGED
|
@@ -522,6 +522,17 @@ Anchors on headings are scoped to their entries, so two entries can use the
|
|
|
522
522
|
same heading anchor. The build reports an unresolved destination while
|
|
523
523
|
keeping the source available for inspection.
|
|
524
524
|
|
|
525
|
+
A reference to a GM-only note or secret heading prints normally. GM-only notes
|
|
526
|
+
remain omitted, and references to them print as text without a link. `.show`
|
|
527
|
+
does not add an omitted destination to the book. Inline `[text]{.secret}` spans
|
|
528
|
+
print as ordinary text, preserving emphasis, links, anchors and `.name` bold
|
|
529
|
+
styling; the book ignores their `.secret` class.
|
|
530
|
+
|
|
531
|
+
Secret sections and `:::secret` fences keep their visible contents in pale
|
|
532
|
+
lavender boxes (`#f2eefb`) with a purple left border and title (`#5c4b8a`). The
|
|
533
|
+
default title is **! Secret**; a fence can supply its own title. See
|
|
534
|
+
[secret references](../authoring/links-and-markup.md#references-to-secret-subjects).
|
|
535
|
+
|
|
525
536
|
The [generated sections](../reference/format-details.md#generated-sections)
|
|
526
537
|
an entry carries — **Within**, **Governed by**, **In song and story**, the
|
|
527
538
|
event views and the rest — name notes the build chose rather than the author,
|
package/docs/guides/site.md
CHANGED
|
@@ -118,6 +118,13 @@ Wikilinks resolve through the address index. Each emitted page receives
|
|
|
118
118
|
derived `related` entries for the links its author wrote and the backlinks
|
|
119
119
|
other authors wrote to it.
|
|
120
120
|
|
|
121
|
+
A wikilink to a GM-only note or secret heading conceals its whole reference in
|
|
122
|
+
an inline show/hide spoiler. `.show` reveals the reference without changing the
|
|
123
|
+
destination's audience. GM-only pages remain omitted, so references to them
|
|
124
|
+
have no link; published secret sections retain theirs. Spoiler text remains in
|
|
125
|
+
the generated HTML and is readable in page source. See
|
|
126
|
+
[secret references](../authoring/links-and-markup.md#references-to-secret-subjects).
|
|
127
|
+
|
|
121
128
|
What lies within a place, who governs it, what an affiliation governs, the
|
|
122
129
|
works naming a note and the map from a place reach the page as
|
|
123
130
|
[generated sections](../reference/format-details.md#generated-sections): H1
|
|
@@ -515,7 +515,10 @@ is declared, so a near miss is a finding that names what you probably meant.
|
|
|
515
515
|
|
|
516
516
|
`gm` marks a note for GM access. It is excluded from public site pages, SQL
|
|
517
517
|
tables on the public site, and the book. Foundry compiles it only to private
|
|
518
|
-
compendiums.
|
|
518
|
+
compendiums. Any note may reference it with an ordinary wikilink; the
|
|
519
|
+
reference is automatically a spoiler. The `.show` override reveals that
|
|
520
|
+
reference without changing the destination's audience or access permissions;
|
|
521
|
+
see [references to secret subjects](#references-to-secret-subjects).
|
|
519
522
|
|
|
520
523
|
**`draft` marks unfinished content.** It does two things and nothing
|
|
521
524
|
else: a link _into_ a draft note renders marked, and the note itself states that
|
|
@@ -1808,6 +1811,38 @@ name at every citation with no link edited. `[[x|]]` is labelled; `[[x]]` is not
|
|
|
1808
1811
|
It is the label that is required, not a target: the anchor-only form above
|
|
1809
1812
|
carries its pipe like every other link.
|
|
1810
1813
|
|
|
1814
|
+
#### References to secret subjects
|
|
1815
|
+
|
|
1816
|
+
Use `[[doc-divineactn|The Truth of Divine Action]]` for an indexed subject,
|
|
1817
|
+
including one with a GM-only audience. The renderer derives spoiler presentation
|
|
1818
|
+
from the resolved destination and conceals the whole reference, including its
|
|
1819
|
+
label. A public destination has no automatic concealment. An anchor inside secret
|
|
1820
|
+
content is a secret destination, including a same-note anchor reference. A
|
|
1821
|
+
link to the whole public note does not inherit a section's secrecy.
|
|
1822
|
+
|
|
1823
|
+
The website renders an inline show/hide spoiler; an omitted GM-only target
|
|
1824
|
+
retains its reference as text without a dead link. A published secret section
|
|
1825
|
+
retains its link. Foundry renders a CSS-redacted inline spoiler, revealed on
|
|
1826
|
+
hover or keyboard focus, with destination access controlled by private packs
|
|
1827
|
+
and page permissions. Books ignore inline `.secret` presentation and print
|
|
1828
|
+
references normally, retaining text without a link when the GM-only destination
|
|
1829
|
+
is omitted. `.show` bypasses automatic spoiler presentation on the website and
|
|
1830
|
+
in Foundry; book references already print normally. An unavailable destination
|
|
1831
|
+
still has no link.
|
|
1832
|
+
|
|
1833
|
+
An immediately following `.show` attribute explicitly reveals the reference:
|
|
1834
|
+
|
|
1835
|
+
```markdown
|
|
1836
|
+
[[doc-divineactn|The Truth of Divine Action]]{.show}
|
|
1837
|
+
[[doc-magictruth|The Truth of Magic]]{.show}
|
|
1838
|
+
```
|
|
1839
|
+
|
|
1840
|
+
This override changes the reference's presentation only. It does not publish a
|
|
1841
|
+
GM-only note, alter Foundry permissions or reveal the destination's content.
|
|
1842
|
+
It is redundant for public destinations. `[text]{.secret}` separately marks
|
|
1843
|
+
text itself as a spoiler; it declares no destination audience. See
|
|
1844
|
+
[inline spans](#inline-spans) for inline spoiler syntax and surface behavior.
|
|
1845
|
+
|
|
1811
1846
|
#### Marking names in prose
|
|
1812
1847
|
|
|
1813
1848
|
`package-build lint` reports name-marking warnings in authored body prose.
|
|
@@ -1815,7 +1850,10 @@ The warnings leave a valid package passing. Headings, frontmatter, tables,
|
|
|
1815
1850
|
infoboxes, code, comments and embeds are outside this check.
|
|
1816
1851
|
|
|
1817
1852
|
A name is explicit: a `[Name]{.name}` bracket span or an authored link label.
|
|
1818
|
-
Use `.name` for
|
|
1853
|
+
Use typed `.name` spans for unwritten subjects and ordinary wikilinks for
|
|
1854
|
+
indexed subjects, including private ones. Introduce another subject with its
|
|
1855
|
+
full canonical name at its first prose use; use plain text for the note's own
|
|
1856
|
+
subject and for later mentions and aliases.
|
|
1819
1857
|
Plain prose is not searched for names. Wikilinks, ordinary Markdown links and
|
|
1820
1858
|
reference links all supply names; a resolved empty wikilink label uses the
|
|
1821
1859
|
note's full name. Images, code, footnote markers and bare autolinks without
|
|
@@ -1856,7 +1894,7 @@ An optional `data-type` on a `.name` span states what the named thing is:
|
|
|
1856
1894
|
|
|
1857
1895
|
```markdown
|
|
1858
1896
|
[Ravenmoor]{.name data-type="place/settlement"}
|
|
1859
|
-
[Aldric Venn]{.name data-type="being"}
|
|
1897
|
+
[Aldric Venn]{.name data-type="being/npc"}
|
|
1860
1898
|
```
|
|
1861
1899
|
|
|
1862
1900
|
Use a declared [note type](note-types.md), optionally followed by `/` and one
|
|
@@ -2630,7 +2668,13 @@ local to a note; rendered markers are sequential numbers. A Foundry page
|
|
|
2630
2668
|
collects its referenced footnotes in a Footnotes section at the bottom of that
|
|
2631
2669
|
JournalEntryPage, while a book places them at the bottom of the page of
|
|
2632
2670
|
reference. A term followed by `: definition` lines creates a definition list.
|
|
2633
|
-
|
|
2671
|
+
After a blank line, indent continuation paragraphs and blocks by four spaces
|
|
2672
|
+
to keep them in the same definition; indent a code block's opening fence,
|
|
2673
|
+
contents and closing fence. Another `:` starts a separate definition for the
|
|
2674
|
+
same term. See the [authoring examples](../authoring/links-and-markup.md#footnotes-and-definition-lists). Definition lists use dictionary hanging indents: bold, nonwrapping terms,
|
|
2675
|
+
normal definitions, a 150px shared column (100px at browser widths up to 600px),
|
|
2676
|
+
a `.75em` term gap and `.5em` entry spacing. A long term pushes only the first
|
|
2677
|
+
definition line beyond the shared column; continuation lines return to it.
|
|
2634
2678
|
|
|
2635
2679
|
```
|
|
2636
2680
|
# Heading {#id .class1 .class2 attr="value"}
|
|
@@ -3483,6 +3527,27 @@ The watch wears [a _blue_ sash]{#watch-sash .uniform lang=en}.
|
|
|
3483
3527
|
|
|
3484
3528
|
The exact `.name` class marks a name, with optional `data-type="type/subType"`;
|
|
3485
3529
|
see [marking names in prose](#marking-names-in-prose) for the vocabulary and index.
|
|
3530
|
+
The exact `.secret` class marks the enclosed text as an inline spoiler:
|
|
3531
|
+
|
|
3532
|
+
```markdown
|
|
3533
|
+
[The hidden **passage**]{.secret}
|
|
3534
|
+
[Aldric Venn]{.name .secret data-type="being/npc"}
|
|
3535
|
+
```
|
|
3536
|
+
|
|
3537
|
+
The website provides an inline show/hide button and preserves interactive links
|
|
3538
|
+
inside the revealed content. Foundry renders a focusable, CSS-redacted inline
|
|
3539
|
+
span that reveals its text and nested content on hover or keyboard focus.
|
|
3540
|
+
Native Foundry secret sections retain their own controls. Books ignore `.secret`
|
|
3541
|
+
on inline spans, preserving their text, emphasis, links, anchors and `.name`
|
|
3542
|
+
bold styling. Secret sections and `:::secret` fences keep their visible labelled
|
|
3543
|
+
boxes. Published HTML retains the text, so opening the spoiler or inspecting the
|
|
3544
|
+
source reveals it.
|
|
3545
|
+
|
|
3546
|
+
This span has no effect on a destination's audience. Ordinary wikilinks derive
|
|
3547
|
+
spoilers from their resolved destinations; `.show` bypasses that automatic
|
|
3548
|
+
concealment without cancelling an enclosing authored `.secret`. An inline span
|
|
3549
|
+
does not change the scope of `.secret` headings or `:::secret` blocks, and it
|
|
3550
|
+
does not start or withhold a JournalEntryPage.
|
|
3486
3551
|
|
|
3487
3552
|
Inline Markdown remains active inside the span. Bracket spans have lower priority than URL links, image links, reference links, footnotes and wikilinks. A bracketed form immediately preceded by `!`, followed by `(` or another `[`, or beginning with `[[` is not a span, even when the link is unresolved. Ordinary links retain their existing syntax. Escape a bracket when it should be literal, or put examples inside a code span or fence. A span identifier addresses that point in the note; it does not start a journal page.
|
|
3488
3553
|
|
|
@@ -4499,7 +4564,7 @@ three fields are built around.
|
|
|
4499
4564
|
unrecorded. Both spellings of `died` mean dead.
|
|
4500
4565
|
- **`age` is optional and is an override.** Written, it wins over anything the
|
|
4501
4566
|
dates say. Unwritten beside a dated `born`, it is the age that follows from
|
|
4502
|
-
the date and the
|
|
4567
|
+
the date and the configured current date — see below. Unwritten beside
|
|
4503
4568
|
`born: unknown` or an absent `born`, the age is unknown.
|
|
4504
4569
|
|
|
4505
4570
|
Most people in a setting do not know their own birth date, so an age beside
|
|
@@ -4512,22 +4577,15 @@ read it.
|
|
|
4512
4577
|
|
|
4513
4578
|
#### The age a dated `born` computes to
|
|
4514
4579
|
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
**An authored `age` beside a dated `born` is expected to agree with what the
|
|
4525
|
-
date computes.** Where it does not, `package-build lint` raises a warning
|
|
4526
|
-
naming both values, so a stale age left behind after a `born` was corrected
|
|
4527
|
-
does not go unnoticed. The warning is silent beside `born: unknown` or an
|
|
4528
|
-
absent `born` — there is nothing there to disagree with — and silent wherever
|
|
4529
|
-
the package declares no present, for the same reason no age is computed at
|
|
4530
|
-
all.
|
|
4580
|
+
The root configuration's `currentDate` states the setting's neutral current
|
|
4581
|
+
date, defaulting to `"720.1"`. A being with a dated `born` and no authored `age`
|
|
4582
|
+
compiles the age implied by those dates. A birthday later in the year counts one
|
|
4583
|
+
less than the difference between the years. A year-only birth date uses day one.
|
|
4584
|
+
Unknown and absent birth dates supply no computed age.
|
|
4585
|
+
|
|
4586
|
+
An authored `age` beside a dated `born` is expected to agree with the configured
|
|
4587
|
+
current date. `package-build lint` warns when the values disagree. Authored ages,
|
|
4588
|
+
including zero and estimates such as `~34`, remain unchanged in built output.
|
|
4531
4589
|
|
|
4532
4590
|
Use `born:` for a being's birth date.
|
|
4533
4591
|
|
|
@@ -6933,7 +6991,6 @@ level with no background prints nothing.
|
|
|
6933
6991
|
| `year.hoursPerDay` | `number` | How many hours the day divides into |
|
|
6934
6992
|
| `year.minutesPerHour` | `number` | How many minutes the hour divides into |
|
|
6935
6993
|
| `year.secondsPerMinute` | `number` | How many seconds the minute divides into |
|
|
6936
|
-
| `present` | `date` | The day the setting stops and play begins |
|
|
6937
6994
|
| `body.diameterKm` | `number` | The body's diameter, in kilometres |
|
|
6938
6995
|
| `body.orbitalRadiusKm` | `number` | How far it orbits from the body it circles, in kilometres |
|
|
6939
6996
|
| `body.orbit` | `string` | The orbit's shape — `circular` means the cycle never varies |
|
|
@@ -6965,7 +7022,6 @@ about the world's year.
|
|
|
6965
7022
|
data:
|
|
6966
7023
|
world: { equatorialCircumferenceKm: 40000, surfaceGravityG: 1.0, axialTiltDegrees: 23.5 }
|
|
6967
7024
|
year: { days: 365, hoursPerDay: 24, minutesPerHour: 60, secondsPerMinute: 60 }
|
|
6968
|
-
present: 720
|
|
6969
7025
|
|
|
6970
7026
|
# its moon — a note of its own, because a moon is a body and not a property
|
|
6971
7027
|
data:
|
|
@@ -6983,14 +7039,13 @@ else wrote down.
|
|
|
6983
7039
|
can say which one a calendar divides; a `moon` with no `year` anywhere is
|
|
6984
7040
|
refused, because a cycle counted in days has no year to be measured against.
|
|
6985
7041
|
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
says it that way.
|
|
7042
|
+
The root configuration's `currentDate` supplies the neutral date used for age
|
|
7043
|
+
calculations throughout the setting. Its default is `"720.1"`.
|
|
6989
7044
|
|
|
6990
7045
|
**`world.axialTiltDegrees`, the circumference, the gravity, the moon's diameter
|
|
6991
7046
|
and its orbital radius are read by nobody.** They are there so a reader knows
|
|
6992
7047
|
what kind of world this is. What the build consumes is the year, the day's
|
|
6993
|
-
divisions, the
|
|
7048
|
+
divisions, the moon's cycle and epoch, and whether its orbit is
|
|
6994
7049
|
circular and inclined.
|
|
6995
7050
|
|
|
6996
7051
|
#### What a settlement's market supports
|
|
@@ -885,7 +885,6 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
|
|
|
885
885
|
| `data.year.hoursPerDay` | number | How many hours the day divides into. |
|
|
886
886
|
| `data.year.minutesPerHour` | number | How many minutes the hour divides into. |
|
|
887
887
|
| `data.year.secondsPerMinute` | number | How many seconds the minute divides into. |
|
|
888
|
-
| `data.present` | a date | The day the setting stops and play begins — what an age is computed against. |
|
|
889
888
|
| `data.body.diameterKm` | number | The body's diameter, in kilometres. |
|
|
890
889
|
| `data.body.orbitalRadiusKm` | number | How far the body orbits from the one it circles, in kilometres. |
|
|
891
890
|
| `data.body.orbit` | string | The orbit's shape — `circular` means the cycle never varies. |
|
|
@@ -515,4 +515,4 @@ A row is a linked title and, when the page carries one, its description.
|
|
|
515
515
|
|
|
516
516
|
## Alerts
|
|
517
517
|
|
|
518
|
-
The content renderer emits an `aside` with `alert` and `alert-note`, `alert-tip`, `alert-important`, `alert-warning` classes. The `alert-title` header contains a Font Awesome icon and the type's label. Shared styles supply a colored left rule, colored header and
|
|
518
|
+
The content renderer emits an `aside` with `alert` and `alert-note`, `alert-tip`, `alert-important`, `alert-warning` classes. The `alert-title` header contains a Font Awesome icon and the type's label. Shared styles supply a colored left rule, colored header, tinted background and readable body text. The website uses the dark palette; Foundry selects a light or dark palette from its color scheme. Alerts do not use `details` and never start collapsed. Authored identifiers, additional classes and named attributes belong to the entire aside. Secret blocks retain their separate expandable presentation on the website.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
shortcode: source-preview
|
|
3
|
+
name: { full: "Source preview and Gitea renderer" }
|
|
4
|
+
type: doc
|
|
5
|
+
subType: reference
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Source preview and Gitea renderer
|
|
9
|
+
|
|
10
|
+
Preview renders only the supplied Markdown buffer. It works with ordinary
|
|
11
|
+
Markdown, optional YAML frontmatter, and local Heroic Lands extensions, without
|
|
12
|
+
configuration, a content tree, dependency caches, a content index or Pandoc.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
package-build-preview < note.md > preview.html
|
|
16
|
+
package-build-preview --dark < note.md > preview.html
|
|
17
|
+
package-build-preview --current-date 740.1 < note.md > preview.html
|
|
18
|
+
package-build-preview --css > preview.css
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The command writes an HTML fragment by default. Include `preview.css` in the
|
|
22
|
+
hosting page to apply the syntax colors, panels and tinted alerts. Light mode
|
|
23
|
+
is the default; `--dark` is explicit and independent of browser preferences.
|
|
24
|
+
`--css` emits both scoped palettes and does not read stdin. `--help` lists the
|
|
25
|
+
options. Unsupported arguments exit 1. Findings go to stderr; success exits 0,
|
|
26
|
+
and render errors exit 1. A partial preview may accompany error findings.
|
|
27
|
+
|
|
28
|
+
Wikilinks, image and portrait links retain their complete source: blue brackets,
|
|
29
|
+
purple addresses, bold foreground labels and grey attributes. Entire spans are bold, including their visible brackets and attributes.
|
|
30
|
+
Definition-list terms are bold and definitions use normal text. SQL and pagelist fences remain grey source;
|
|
31
|
+
they never execute. Calendar expressions, dependent expressions, references and
|
|
32
|
+
maps remain green source. Local expressions can evaluate against authored
|
|
33
|
+
frontmatter. Missing ages are calculated from neutral birth dates and the root
|
|
34
|
+
`currentDate` setting, defaulting to `"720.1"`, as for other package outputs.
|
|
35
|
+
Authored ages, including `0`, take precedence. Unknown or calendar-dependent
|
|
36
|
+
birth dates omit the age row; `{{data.age}}` is empty when unavailable. Generated sections,
|
|
37
|
+
related pages and See also are absent. Preview performs no publication or link
|
|
38
|
+
checks. Alerts render with their icons, titles, Markdown bodies and tinted
|
|
39
|
+
backgrounds, including nested alerts. Secret blocks are closed disclosure
|
|
40
|
+
spoilers. An authored `.secret` heading hides its section through the next
|
|
41
|
+
heading at the same or a higher level, while retaining its anchor. Secret spans
|
|
42
|
+
redact their label until hover or keyboard focus; explicit `.secret` wikilink
|
|
43
|
+
and image attributes similarly redact their inner source. The authored outer
|
|
44
|
+
brackets and attributes remain visible. This uses scoped CSS and a focusable
|
|
45
|
+
wrapper, with no preview JavaScript required.
|
|
46
|
+
|
|
47
|
+
Applications import `renderPreview` and `previewStyles` from
|
|
48
|
+
`@heroiclands/package-build/preview`. The HTML wrapper has `hl-preview` and
|
|
49
|
+
exactly one mode class: `hl-preview-light` or `hl-preview-dark`. See the
|
|
50
|
+
[API reference](api.md) for the function contract.
|
|
51
|
+
|
|
52
|
+
## Current date
|
|
53
|
+
|
|
54
|
+
Declare the setting-wide date as a quoted root configuration value:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
currentDate: "720.1"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The standalone command discovers the nearest `package-build.config.yaml`,
|
|
61
|
+
`.yml` or `.mjs` from its working directory. YAML reads only `currentDate`; it
|
|
62
|
+
requires no other package fields, content or index. MJS executes its trusted
|
|
63
|
+
configuration module. Missing configuration or an absent setting uses `"720.1"`.
|
|
64
|
+
Invalid explicit values fail with a configuration diagnostic. Use
|
|
65
|
+
`--current-date DATE` to override discovery for one invocation. Dates must use
|
|
66
|
+
neutral `<year>[.<day>[:HHMMSS]]` syntax; named-calendar dates are refused.
|
|
67
|
+
|
|
68
|
+
The pure `renderPreview` API accepts `currentDate` and never reads configuration.
|
|
69
|
+
Hosts can opt into `await readPreviewCurrentDate(projectRoot)` to read the root
|
|
70
|
+
setting before rendering. Repeated calls observe configuration edits. This is
|
|
71
|
+
the same root setting used by compilation, website output and other toolchain
|
|
72
|
+
consumers; there is no separate preview date setting.
|
|
73
|
+
|
|
74
|
+
## Gitea installation
|
|
75
|
+
|
|
76
|
+
Install Node 24 or newer and this package in the Gitea runtime, including the
|
|
77
|
+
container image when applicable. The examples assume installation at
|
|
78
|
+
`/opt/heroiclands/node_modules/@heroiclands/package-build` and a usable Node
|
|
79
|
+
executable on Gitea's PATH. Pin the package version in the installation's
|
|
80
|
+
`package.json` and lockfile.
|
|
81
|
+
|
|
82
|
+
Copy [the complete renderer configuration](../assets/preview/gitea.ini) into
|
|
83
|
+
`app.ini`. It replaces whole-file rendering for the listed Markdown extensions;
|
|
84
|
+
it does not replace Gitea's issue-comment or pull-request discussion renderer.
|
|
85
|
+
The renderer section must appear before its sanitizer sections. Its command
|
|
86
|
+
reads stdin and emits HTML; it needs no repository checkout. Change the absolute
|
|
87
|
+
command path to match the installation. Gitea normally uses the shared default
|
|
88
|
+
`"720.1"` because it receives no repository checkout. To select the deployment's
|
|
89
|
+
setting-wide date explicitly, use:
|
|
90
|
+
|
|
91
|
+
```ini
|
|
92
|
+
RENDER_COMMAND = /opt/heroiclands/node_modules/.bin/package-build-preview --current-date 740.1
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Keep this override aligned with the setting's root configuration. Without an
|
|
96
|
+
override, run Gitea from a directory with no package-build configuration so
|
|
97
|
+
unrelated local configuration cannot select the date.
|
|
98
|
+
|
|
99
|
+
The supplied configuration keeps sanitization enabled and disables Gitea's
|
|
100
|
+
post-processing so preserved source and literal links remain intact. It permits
|
|
101
|
+
only the preview's class families on the elements that need them, plus the SVG
|
|
102
|
+
attributes needed for alert icons. Gitea's default SVG rules retain `viewBox`,
|
|
103
|
+
`width`, `height`, `aria-hidden`, path `d` and `fill-rule`; the extra rules retain
|
|
104
|
+
`fill="currentColor"` and circle coordinates. The wrapper mode class, syntax
|
|
105
|
+
span classes, fence classes and infobox wrapper must survive sanitization for
|
|
106
|
+
styling to work. Source is escaped text, so no additional source-data attributes
|
|
107
|
+
are required. The `secretFocus` rule retains only `tabindex="0"` on span
|
|
108
|
+
wrappers so keyboard users can reveal redacted source labels. Do not enable
|
|
109
|
+
arbitrary styles, scripts or data-URI images.
|
|
110
|
+
|
|
111
|
+
Install the stylesheet and optional theme adapter beneath the Gitea custom
|
|
112
|
+
path. Here `custom` means the actual instance's `CustomPath`:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
mkdir -p custom/public/assets/css custom/public/assets/js
|
|
116
|
+
/opt/heroiclands/node_modules/.bin/package-build-preview --css > custom/public/assets/css/heroiclands-preview.css
|
|
117
|
+
cp /opt/heroiclands/node_modules/@heroiclands/package-build/assets/preview/gitea-theme.js custom/public/assets/js/heroiclands-preview.js
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Add to `custom/templates/custom/header.tmpl`, preserving existing content:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/heroiclands-preview.css" />
|
|
124
|
+
<script defer src="{{AppSubUrl}}/assets/js/heroiclands-preview.js"></script>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Restart Gitea after configuration/template changes. Regenerate the CSS asset
|
|
128
|
+
when updating the package. These are administrator installation instructions;
|
|
129
|
+
package-build does not deploy or change the server configuration.
|
|
130
|
+
|
|
131
|
+
## Gitea theme selection
|
|
132
|
+
|
|
133
|
+
The external renderer runs on the server and cannot see a reader's active
|
|
134
|
+
theme. The installed browser adapter reads Gitea's root CSS variable
|
|
135
|
+
`--is-dark-theme`, selecting dark for `true` and light for `false`. It follows
|
|
136
|
+
`data-theme` changes, stylesheet loads and newly inserted preview nodes. It
|
|
137
|
+
changes only `.markup.markdown .hl-preview` mode classes; the surrounding Gitea
|
|
138
|
+
interface keeps its own colors. A custom theme that omits the variable retains
|
|
139
|
+
the initial renderer mode.
|
|
140
|
+
|
|
141
|
+
Without the browser adapter, use the default command for fixed light rendering,
|
|
142
|
+
or append `--dark` to `RENDER_COMMAND` for fixed dark rendering. Explicit CLI
|
|
143
|
+
mode never changes itself. Theme adaptation happens only when the administrator
|
|
144
|
+
installs the separate browser script.
|
|
145
|
+
|
|
146
|
+
## Sanitizer validation
|
|
147
|
+
|
|
148
|
+
[The validation harness](../assets/preview/check-sanitizer.go) applies the
|
|
149
|
+
shipped INI rules using the actual `bluemonday` sanitizer and the relevant
|
|
150
|
+
Gitea SVG defaults. It requires a Go toolchain and a temporary module with
|
|
151
|
+
`github.com/microcosm-cc/bluemonday` at the version used by the target Gitea
|
|
152
|
+
release. Run it against generated preview HTML:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
go run check-sanitizer.go /path/to/gitea.ini < preview.html > sanitized.html
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Check both light and dark fixtures containing all four alert types, a custom
|
|
159
|
+
title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes, poetry
|
|
160
|
+
and footnotes. All wrapper/token classes and icon shapes should remain, while
|
|
161
|
+
injected scripts and event-handler attributes should disappear. This replay
|
|
162
|
+
checks the sanitizer primitives; also inspect the result in the actual Gitea
|
|
163
|
+
version before deployment, since defaults can vary between releases.
|
|
164
|
+
|
|
165
|
+
Configuration and installation follow Gitea's
|
|
166
|
+
[external renderer documentation](https://docs.gitea.com/administration/external-renderers/).
|
|
167
|
+
The relevant implementation contracts are its
|
|
168
|
+
[command adapter](https://github.com/go-gitea/gitea/blob/main/modules/markup/external/external.go),
|
|
169
|
+
[sanitizer defaults](https://github.com/go-gitea/gitea/blob/main/modules/markup/sanitizer_default.go),
|
|
170
|
+
[renderer settings](https://github.com/go-gitea/gitea/blob/main/modules/setting/markup.go),
|
|
171
|
+
[root theme attribute](https://github.com/go-gitea/gitea/blob/main/templates/base/head.tmpl),
|
|
172
|
+
and [theme scheme variable](https://github.com/go-gitea/gitea/blob/main/web_src/css/themes/theme-gitea-dark.css).
|
package/engine/anchors.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import { scanPoetry } from "./content-poetry.mjs";
|
|
|
41
41
|
import { scanSpans } from "./content-spans.mjs";
|
|
42
42
|
import { slugify } from "./content-slug.mjs";
|
|
43
43
|
import { scanFigures } from "./content-figures.mjs";
|
|
44
|
-
import { scanHeadingAttributes } from "./heading-attributes.mjs";
|
|
44
|
+
import { withheldSections, scanHeadingAttributes } from "./heading-attributes.mjs";
|
|
45
45
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -67,11 +67,12 @@ export const ANCHOR_KINDS = Object.freeze(["prose", "event"]);
|
|
|
67
67
|
* {@link module:engine/content-figures.scanFigures}.
|
|
68
68
|
* @param {import("./content-session.mjs").ContentSyntax} [syntax] - Exact-text scanner projections.
|
|
69
69
|
* @returns {Array<{slug: string, name: string, level: number, line: number,
|
|
70
|
-
* kind: string}>} In document order.
|
|
70
|
+
* kind: string, secret?: boolean}>} In document order.
|
|
71
71
|
*/
|
|
72
72
|
export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
|
|
73
73
|
const anchors = [];
|
|
74
|
-
|
|
74
|
+
const headings = scanHeadingAttributes(body, bodyLine).headings;
|
|
75
|
+
for (const heading of headings) {
|
|
75
76
|
if (!heading.id) continue;
|
|
76
77
|
anchors.push({
|
|
77
78
|
slug: heading.id,
|
|
@@ -91,9 +92,12 @@ export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
|
|
|
91
92
|
kind: "prose",
|
|
92
93
|
});
|
|
93
94
|
}
|
|
95
|
+
const secretBlocks = [];
|
|
94
96
|
const blockAnchors = (source, firstLine) => {
|
|
95
97
|
const sourceLines = source.split("\n");
|
|
96
98
|
for (const block of (syntax ? syntax(source).blocks : scanBlocks(source)).blocks) {
|
|
99
|
+
if (block.name === "secret")
|
|
100
|
+
secretBlocks.push({ start: firstLine + block.start, end: firstLine + block.end });
|
|
97
101
|
if (block.id)
|
|
98
102
|
anchors.push({
|
|
99
103
|
slug: slugify(block.id),
|
|
@@ -129,7 +133,8 @@ export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
|
|
|
129
133
|
kind: "prose",
|
|
130
134
|
});
|
|
131
135
|
}
|
|
132
|
-
|
|
136
|
+
const spans = (syntax ? syntax(body).spans : scanSpans(body)).spans;
|
|
137
|
+
for (const span of spans) {
|
|
133
138
|
if (span.id)
|
|
134
139
|
anchors.push({
|
|
135
140
|
slug: slugify(span.id),
|
|
@@ -139,6 +144,33 @@ export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
|
|
|
139
144
|
kind: "prose",
|
|
140
145
|
});
|
|
141
146
|
}
|
|
147
|
+
const sections =
|
|
148
|
+
headings.some((heading) => heading.classes.includes("secret")) ?
|
|
149
|
+
withheldSections(body).sections
|
|
150
|
+
: [];
|
|
151
|
+
const secretSpans = spans.filter((span) =>
|
|
152
|
+
span.attrs.some(([key, value]) => key === "class" && value.split(/\s+/).includes("secret")),
|
|
153
|
+
);
|
|
154
|
+
const secretSpanAnchors = new Set(
|
|
155
|
+
spans
|
|
156
|
+
.filter(
|
|
157
|
+
(target) =>
|
|
158
|
+
target.id &&
|
|
159
|
+
secretSpans.some(
|
|
160
|
+
(parent) => parent.start <= target.start && parent.end >= target.end,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
.map((span) => span.id),
|
|
164
|
+
);
|
|
165
|
+
for (const anchor of anchors) {
|
|
166
|
+
const line = anchor.line - bodyLine;
|
|
167
|
+
if (
|
|
168
|
+
sections.some((section) => line >= section.start && line < section.end) ||
|
|
169
|
+
secretBlocks.some((block) => anchor.line >= block.start && anchor.line <= block.end) ||
|
|
170
|
+
secretSpanAnchors.has(anchor.slug)
|
|
171
|
+
)
|
|
172
|
+
anchor.secret = true;
|
|
173
|
+
}
|
|
142
174
|
return anchors.sort((a, b) => a.line - b.line);
|
|
143
175
|
}
|
|
144
176
|
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -103,10 +103,11 @@ import { packForType } from "./ids.mjs";
|
|
|
103
103
|
import { resolveNoteId } from "./note-ids.mjs";
|
|
104
104
|
import { carriesSystemBlock } from "./system-block.mjs";
|
|
105
105
|
import { checkAuthoredSystemData, checkEmittedSystemData } from "./schema-check.mjs";
|
|
106
|
-
// A being's computed `age`, filled in against the
|
|
107
|
-
//
|
|
106
|
+
// A being's computed `age`, filled in against the configured setting
|
|
107
|
+
// current date before this pass reads `data:` for anything else — see
|
|
108
108
|
// `engine/being-age.mjs`.
|
|
109
|
-
import { applyComputedBeingAge
|
|
109
|
+
import { applyComputedBeingAge } from "./being-age.mjs";
|
|
110
|
+
import { normalizeCurrentDate } from "./current-date.mjs";
|
|
110
111
|
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
111
112
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
112
113
|
import { annotateAssetReferences, assetReferenceLookup } from "./asset-references.mjs";
|
|
@@ -632,10 +633,8 @@ export class BasePackCompiler {
|
|
|
632
633
|
if (this.constructor.convertsWikilinks) {
|
|
633
634
|
this.sqlTables = this.corpus.sqlTables;
|
|
634
635
|
}
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
// the content index.
|
|
638
|
-
this.worldPresent = presentAmongRecords(this.corpus.records);
|
|
636
|
+
// Use the compile corpus's setting date.
|
|
637
|
+
this.currentDate = normalizeCurrentDate(this.corpus.currentDate);
|
|
639
638
|
this.unresolvedLinks = 0;
|
|
640
639
|
}
|
|
641
640
|
|
|
@@ -1277,7 +1276,7 @@ export class BasePackCompiler {
|
|
|
1277
1276
|
stats.skippedOther++;
|
|
1278
1277
|
continue;
|
|
1279
1278
|
}
|
|
1280
|
-
applyComputedBeingAge(fm, this.
|
|
1279
|
+
applyComputedBeingAge(fm, this.currentDate, dates);
|
|
1281
1280
|
// `package:` is refused before `selects`, so a note is answered
|
|
1282
1281
|
// whichever pass would have claimed it: a note's package is the
|
|
1283
1282
|
// repository's configured one, so declaring it restates a
|