@heroiclands/package-build 23.11.0 → 23.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -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 +105 -46
- package/docs/authoring/links-and-markup.md +52 -14
- package/docs/commands.md +46 -2
- package/docs/configuration.md +21 -4
- package/docs/design.md +4 -3
- package/docs/guides/book.md +10 -6
- package/docs/reference/format-details.md +44 -40
- 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/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-spans.mjs +28 -6
- package/engine/current-date.mjs +37 -0
- package/engine/definition-list.mjs +58 -0
- package/engine/first-use.mjs +4 -2
- package/engine/frontmatter-lint.mjs +7 -2
- package/engine/helpers.mjs +2 -0
- 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 +37 -4
- package/engine/preview-current-date.mjs +60 -0
- package/engine/site-build.mjs +5 -5
- package/engine/site-index.mjs +5 -2
- package/engine/site-preview.mjs +641 -325
- 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/page-body.html +1 -1
- package/hugo-theme/static/css/style.css +22 -8
- package/package.json +8 -2
- package/types/content-config.d.mts +8 -0
- 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 +4 -2
- 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
|
@@ -1822,11 +1822,12 @@ link to the whole public note does not inherit a section's secrecy.
|
|
|
1822
1822
|
|
|
1823
1823
|
The website renders an inline show/hide spoiler; an omitted GM-only target
|
|
1824
1824
|
retains its reference as text without a dead link. A published secret section
|
|
1825
|
-
retains its link. Foundry renders a
|
|
1826
|
-
|
|
1827
|
-
permissions. Books
|
|
1828
|
-
without a link when the GM-only destination
|
|
1829
|
-
automatic spoiler presentation on
|
|
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
|
|
1830
1831
|
still has no link.
|
|
1831
1832
|
|
|
1832
1833
|
An immediately following `.show` attribute explicitly reveals the reference:
|
|
@@ -1850,9 +1851,13 @@ infoboxes, code, comments and embeds are outside this check.
|
|
|
1850
1851
|
|
|
1851
1852
|
A name is explicit: a `[Name]{.name}` bracket span or an authored link label.
|
|
1852
1853
|
Use typed `.name` spans for unwritten subjects and ordinary wikilinks for
|
|
1853
|
-
indexed subjects, including private ones.
|
|
1854
|
-
|
|
1855
|
-
|
|
1854
|
+
indexed subjects, including private ones. Mark or link another subject at its
|
|
1855
|
+
first prose use, and use plain text for the note's own subject and for later
|
|
1856
|
+
mentions and aliases. A wikilink identifies its note, so its label is whatever
|
|
1857
|
+
reads naturally in the sentence: `[[place-vedyara|Vedyara]]` for a note whose
|
|
1858
|
+
full name is _Vedyara Region_, and a lowercase article mid-sentence
|
|
1859
|
+
(`[[place-southwall|the Southern Wall]]`). A `.name` span has no note behind
|
|
1860
|
+
it, so it carries the full name.
|
|
1856
1861
|
Plain prose is not searched for names. Wikilinks, ordinary Markdown links and
|
|
1857
1862
|
reference links all supply names; a resolved empty wikilink label uses the
|
|
1858
1863
|
note's full name. Images, code, footnote markers and bare autolinks without
|
|
@@ -1866,9 +1871,10 @@ authored labels supply none.
|
|
|
1866
1871
|
anchors. Other labels use their whole text as their identity: different
|
|
1867
1872
|
labels linking to one page are separate names, and a label is not
|
|
1868
1873
|
automatically an alias of its destination.
|
|
1869
|
-
3.
|
|
1870
|
-
|
|
1871
|
-
|
|
1874
|
+
3. A `.name` span or an ordinary Markdown link introduces the full name
|
|
1875
|
+
before a short form or declared alias. A resolved wikilink introduces its
|
|
1876
|
+
note under any label. Possessives and regular plurals count as uses; other
|
|
1877
|
+
indexed forms can be declared aliases.
|
|
1872
1878
|
|
|
1873
1879
|
Bold is emphasis. A whole bold span exactly matching an indexed full name or
|
|
1874
1880
|
alias receives one advisory to mark or link the name. It supplies no name
|
|
@@ -2667,7 +2673,13 @@ local to a note; rendered markers are sequential numbers. A Foundry page
|
|
|
2667
2673
|
collects its referenced footnotes in a Footnotes section at the bottom of that
|
|
2668
2674
|
JournalEntryPage, while a book places them at the bottom of the page of
|
|
2669
2675
|
reference. A term followed by `: definition` lines creates a definition list.
|
|
2670
|
-
|
|
2676
|
+
After a blank line, indent continuation paragraphs and blocks by four spaces
|
|
2677
|
+
to keep them in the same definition; indent a code block's opening fence,
|
|
2678
|
+
contents and closing fence. Another `:` starts a separate definition for the
|
|
2679
|
+
same term. See the [authoring examples](../authoring/links-and-markup.md#footnotes-and-definition-lists). Definition lists use dictionary hanging indents: bold, nonwrapping terms,
|
|
2680
|
+
normal definitions, a 150px shared column (100px at browser widths up to 600px),
|
|
2681
|
+
a `.75em` term gap and `.5em` entry spacing. A long term pushes only the first
|
|
2682
|
+
definition line beyond the shared column; continuation lines return to it.
|
|
2671
2683
|
|
|
2672
2684
|
```
|
|
2673
2685
|
# Heading {#id .class1 .class2 attr="value"}
|
|
@@ -3528,11 +3540,13 @@ The exact `.secret` class marks the enclosed text as an inline spoiler:
|
|
|
3528
3540
|
```
|
|
3529
3541
|
|
|
3530
3542
|
The website provides an inline show/hide button and preserves interactive links
|
|
3531
|
-
inside the revealed content. Foundry
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3543
|
+
inside the revealed content. Foundry renders a focusable, CSS-redacted inline
|
|
3544
|
+
span that reveals its text and nested content on hover or keyboard focus.
|
|
3545
|
+
Native Foundry secret sections retain their own controls. Books ignore `.secret`
|
|
3546
|
+
on inline spans, preserving their text, emphasis, links, anchors and `.name`
|
|
3547
|
+
bold styling. Secret sections and `:::secret` fences keep their visible labelled
|
|
3548
|
+
boxes. Published HTML retains the text, so opening the spoiler or inspecting the
|
|
3549
|
+
source reveals it.
|
|
3536
3550
|
|
|
3537
3551
|
This span has no effect on a destination's audience. Ordinary wikilinks derive
|
|
3538
3552
|
spoilers from their resolved destinations; `.show` bypasses that automatic
|
|
@@ -4555,7 +4569,7 @@ three fields are built around.
|
|
|
4555
4569
|
unrecorded. Both spellings of `died` mean dead.
|
|
4556
4570
|
- **`age` is optional and is an override.** Written, it wins over anything the
|
|
4557
4571
|
dates say. Unwritten beside a dated `born`, it is the age that follows from
|
|
4558
|
-
the date and the
|
|
4572
|
+
the date and the configured current date — see below. Unwritten beside
|
|
4559
4573
|
`born: unknown` or an absent `born`, the age is unknown.
|
|
4560
4574
|
|
|
4561
4575
|
Most people in a setting do not know their own birth date, so an age beside
|
|
@@ -4568,22 +4582,15 @@ read it.
|
|
|
4568
4582
|
|
|
4569
4583
|
#### The age a dated `born` computes to
|
|
4570
4584
|
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
**An authored `age` beside a dated `born` is expected to agree with what the
|
|
4581
|
-
date computes.** Where it does not, `package-build lint` raises a warning
|
|
4582
|
-
naming both values, so a stale age left behind after a `born` was corrected
|
|
4583
|
-
does not go unnoticed. The warning is silent beside `born: unknown` or an
|
|
4584
|
-
absent `born` — there is nothing there to disagree with — and silent wherever
|
|
4585
|
-
the package declares no present, for the same reason no age is computed at
|
|
4586
|
-
all.
|
|
4585
|
+
The root configuration's `currentDate` states the setting's neutral current
|
|
4586
|
+
date, defaulting to `"720.1"`. A being with a dated `born` and no authored `age`
|
|
4587
|
+
compiles the age implied by those dates. A birthday later in the year counts one
|
|
4588
|
+
less than the difference between the years. A year-only birth date uses day one.
|
|
4589
|
+
Unknown and absent birth dates supply no computed age.
|
|
4590
|
+
|
|
4591
|
+
An authored `age` beside a dated `born` is expected to agree with the configured
|
|
4592
|
+
current date. `package-build lint` warns when the values disagree. Authored ages,
|
|
4593
|
+
including zero and estimates such as `~34`, remain unchanged in built output.
|
|
4587
4594
|
|
|
4588
4595
|
Use `born:` for a being's birth date.
|
|
4589
4596
|
|
|
@@ -6989,7 +6996,6 @@ level with no background prints nothing.
|
|
|
6989
6996
|
| `year.hoursPerDay` | `number` | How many hours the day divides into |
|
|
6990
6997
|
| `year.minutesPerHour` | `number` | How many minutes the hour divides into |
|
|
6991
6998
|
| `year.secondsPerMinute` | `number` | How many seconds the minute divides into |
|
|
6992
|
-
| `present` | `date` | The day the setting stops and play begins |
|
|
6993
6999
|
| `body.diameterKm` | `number` | The body's diameter, in kilometres |
|
|
6994
7000
|
| `body.orbitalRadiusKm` | `number` | How far it orbits from the body it circles, in kilometres |
|
|
6995
7001
|
| `body.orbit` | `string` | The orbit's shape — `circular` means the cycle never varies |
|
|
@@ -7021,7 +7027,6 @@ about the world's year.
|
|
|
7021
7027
|
data:
|
|
7022
7028
|
world: { equatorialCircumferenceKm: 40000, surfaceGravityG: 1.0, axialTiltDegrees: 23.5 }
|
|
7023
7029
|
year: { days: 365, hoursPerDay: 24, minutesPerHour: 60, secondsPerMinute: 60 }
|
|
7024
|
-
present: 720
|
|
7025
7030
|
|
|
7026
7031
|
# its moon — a note of its own, because a moon is a body and not a property
|
|
7027
7032
|
data:
|
|
@@ -7039,14 +7044,13 @@ else wrote down.
|
|
|
7039
7044
|
can say which one a calendar divides; a `moon` with no `year` anywhere is
|
|
7040
7045
|
refused, because a cycle counted in days has no year to be measured against.
|
|
7041
7046
|
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
says it that way.
|
|
7047
|
+
The root configuration's `currentDate` supplies the neutral date used for age
|
|
7048
|
+
calculations throughout the setting. Its default is `"720.1"`.
|
|
7045
7049
|
|
|
7046
7050
|
**`world.axialTiltDegrees`, the circumference, the gravity, the moon's diameter
|
|
7047
7051
|
and its orbital radius are read by nobody.** They are there so a reader knows
|
|
7048
7052
|
what kind of world this is. What the build consumes is the year, the day's
|
|
7049
|
-
divisions, the
|
|
7053
|
+
divisions, the moon's cycle and epoch, and whether its orbit is
|
|
7050
7054
|
circular and inclined.
|
|
7051
7055
|
|
|
7052
7056
|
#### 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/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
|
package/engine/being-age.mjs
CHANGED
|
@@ -11,51 +11,9 @@
|
|
|
11
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* **The middle state of `age`: absent beside a dated `born`, it follows from
|
|
16
|
-
* the date.**
|
|
17
|
-
*
|
|
18
|
-
* `age` is a three-state override — authored it wins, absent beside a dated
|
|
19
|
-
* `born` it is computed, absent beside `born: unknown` or an absent `born` it
|
|
20
|
-
* stays unknown. This module builds the middle state, which nothing else in
|
|
21
|
-
* the toolchain does: `born` and `age` are read straight through to every
|
|
22
|
-
* compiled surface exactly as authored, unless a note's own package states the
|
|
23
|
-
* day the setting stops.
|
|
24
|
-
*
|
|
25
|
-
* ## Where the present comes from
|
|
26
|
-
*
|
|
27
|
-
* A world's `present` is a fact about the world, not about a calendar or a
|
|
28
|
-
* build configuration — it is `data.present` on the package's own `place` note
|
|
29
|
-
* of `subType: world` (or `celestial`), read through
|
|
30
|
-
* {@link module:engine/calendar-notes.worldInvariants}. A package with no such
|
|
31
|
-
* note states no present, computes no age, and says nothing about it: the
|
|
32
|
-
* absence is silent, exactly as a package that declares no calendar is asked
|
|
33
|
-
* no calendar questions.
|
|
34
|
-
*
|
|
35
|
-
* ## The arithmetic is on two dates, not on two years
|
|
36
|
-
*
|
|
37
|
-
* A year subtracted from a year is wrong whenever the birthday has not yet
|
|
38
|
-
* happened in the present's year — the common shape, not the exception. So
|
|
39
|
-
* {@link computeAge} compares the two dates' canonical day ordinals where an
|
|
40
|
-
* era marker supplies them. The birthday that still lies ahead in the current
|
|
41
|
-
* year does not count as a completed year. An unmarked slash date has no
|
|
42
|
-
* calendar to convert, so it uses its written month and day as its ordinal.
|
|
43
|
-
*
|
|
44
|
-
* ## `~` follows the convention {@link module:engine/note-dates} sets
|
|
45
|
-
*
|
|
46
|
-
* A date's `~` marks the value approximate without changing what it parses to,
|
|
47
|
-
* so a renderer keeps the authored text and a comparison still has a number.
|
|
48
|
-
* {@link parseAgeMagnitude} draws the same distinction for `age` — the tilde,
|
|
49
|
-
* the magnitude — without routing through {@link module:engine/note-dates}
|
|
50
|
-
* itself: `age` is a plain count of years, not a date, so it has no month, no
|
|
51
|
-
* day, no era, and — unlike a date — no refusal of `0`. Reusing the date
|
|
52
|
-
* grammar's month/day/era machinery here would accept an age nothing about the
|
|
53
|
-
* field means, and refuse the one age an infant can have.
|
|
54
|
-
*
|
|
55
|
-
* @module
|
|
56
|
-
*/
|
|
14
|
+
/** Authored ages and setting-wide current-date age calculations. @module */
|
|
57
15
|
|
|
58
|
-
import {
|
|
16
|
+
import { DEFAULT_CURRENT_DATE } from "./current-date.mjs";
|
|
59
17
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
60
18
|
import { parseNoteDate } from "./note-dates.mjs";
|
|
61
19
|
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
@@ -107,11 +65,9 @@ export function parseAgeMagnitude(raw) {
|
|
|
107
65
|
*
|
|
108
66
|
* @param {unknown} bornRaw - The being's authored `born`, or `undefined`/`null`
|
|
109
67
|
* where it wrote none.
|
|
110
|
-
* @param {unknown} presentRaw - The
|
|
111
|
-
* {@link module:engine/calendar-notes.worldInvariants} read it, or
|
|
112
|
-
* `undefined`/`null` where the package states none.
|
|
68
|
+
* @param {unknown} presentRaw - The configured neutral current date.
|
|
113
69
|
* @returns {number|null} The age, or `null` where `born` is `unknown` or
|
|
114
|
-
* absent
|
|
70
|
+
* absent or future, or where either date fails to parse.
|
|
115
71
|
*/
|
|
116
72
|
export function computeAge(bornRaw, presentRaw, dateContext = {}) {
|
|
117
73
|
if (bornRaw === undefined || bornRaw === null) return null;
|
|
@@ -126,11 +82,15 @@ export function computeAge(bornRaw, presentRaw, dateContext = {}) {
|
|
|
126
82
|
// A resolved marker supplies a canonical day. An unmarked slash date uses
|
|
127
83
|
// its written month and day. Missing parts read as the first day that the
|
|
128
84
|
// value can name.
|
|
129
|
-
const bornOrdinal =
|
|
130
|
-
|
|
85
|
+
const bornOrdinal =
|
|
86
|
+
born.canonicalDay ?? (born.month === null ? 1 : (born.month ?? 1) * 100 + (born.day ?? 1));
|
|
87
|
+
const presentOrdinal =
|
|
88
|
+
present.canonicalDay ??
|
|
89
|
+
(present.month === null ? 1 : (present.month ?? 1) * 100 + (present.day ?? 1));
|
|
131
90
|
const notYetHappened = bornOrdinal > presentOrdinal;
|
|
132
91
|
|
|
133
|
-
|
|
92
|
+
const age = present.canonicalYear - born.canonicalYear - (notYetHappened ? 1 : 0);
|
|
93
|
+
return age < 0 ? null : age;
|
|
134
94
|
}
|
|
135
95
|
|
|
136
96
|
/**
|
|
@@ -146,8 +106,7 @@ export function computeAge(bornRaw, presentRaw, dateContext = {}) {
|
|
|
146
106
|
* itself untouched.
|
|
147
107
|
*
|
|
148
108
|
* @param {object} fm - A note's frontmatter, as freshly parsed.
|
|
149
|
-
* @param {unknown} presentRaw - The
|
|
150
|
-
* `undefined`/`null` where it states none.
|
|
109
|
+
* @param {unknown} presentRaw - The configured neutral current date.
|
|
151
110
|
* @returns {void}
|
|
152
111
|
*/
|
|
153
112
|
export function applyComputedBeingAge(fm, presentRaw, dateContext = {}) {
|
|
@@ -171,63 +130,21 @@ export function applyComputedBeingAge(fm, presentRaw, dateContext = {}) {
|
|
|
171
130
|
}
|
|
172
131
|
|
|
173
132
|
/**
|
|
174
|
-
*
|
|
175
|
-
* frontmatter objects carries.
|
|
176
|
-
*
|
|
177
|
-
* Two shapes reach this by way of two thin adapters, because the callers that
|
|
178
|
-
* need a present hold their notes two different ways: a flat index record
|
|
179
|
-
* (`type` and `data` at the top level) and a raw walk's `{frontmatter}` pair.
|
|
180
|
-
* Neither shape is the `{fm, type}` pair {@link worldInvariants} reads, so each
|
|
181
|
-
* adapter states the one conversion its caller needs rather than asking every
|
|
182
|
-
* caller to know it.
|
|
183
|
-
*
|
|
184
|
-
* @param {readonly object[]|undefined} records - Flat index records — a
|
|
185
|
-
* content-index record, or anything else spreading a note's frontmatter to
|
|
186
|
-
* its own top level.
|
|
187
|
-
* @returns {unknown} The present, or `null` where no `place` note in the batch
|
|
188
|
-
* declares one.
|
|
189
|
-
*/
|
|
190
|
-
export function presentAmongRecords(records) {
|
|
191
|
-
return worldInvariants({
|
|
192
|
-
notes: (records ?? []).map((record) => ({ fm: record, type: record?.type })),
|
|
193
|
-
}).present;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* The present read from a batch of raw frontmatter objects.
|
|
198
|
-
*
|
|
199
|
-
* @param {readonly (object|null|undefined)[]|undefined} frontmatters - One
|
|
200
|
-
* entry per note walked, as parsed — never a record.
|
|
201
|
-
* @returns {unknown} The present, or `null` where none is declared.
|
|
202
|
-
*/
|
|
203
|
-
export function presentAmongFrontmatters(frontmatters) {
|
|
204
|
-
return worldInvariants({
|
|
205
|
-
notes: (frontmatters ?? []).map((fm) => ({ fm, type: fm?.type })),
|
|
206
|
-
}).present;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Check a being's authored `age` against what `born` and the package's present
|
|
211
|
-
* compute.
|
|
133
|
+
* Check an authored age against the birth date and configured current date.
|
|
212
134
|
*
|
|
213
135
|
* Registered as `being`'s whole-note `check` in `note-vocabulary.mjs`, run the
|
|
214
136
|
* way `checkPlace` and `checkCalendarNote` are — beside every other check the
|
|
215
137
|
* closed `data:` container earns, with the same `index`.
|
|
216
138
|
*
|
|
217
|
-
*
|
|
218
|
-
* there is nothing to disagree with, and a package that declares no present
|
|
219
|
-
* computes no age to disagree with either — both cases are read by
|
|
220
|
-
* {@link computeAge} returning `null`, and neither says a word.
|
|
139
|
+
* An unknown or absent birth date supplies no computed age to compare.
|
|
221
140
|
*
|
|
222
|
-
* @param {object} note - The note, as the link index hands it over
|
|
223
|
-
* {@link module:engine/calendar-notes.worldInvariants}'s `index.notes` shape.
|
|
141
|
+
* @param {object} note - The note, as the link index hands it over.
|
|
224
142
|
* @param {object} [opts]
|
|
225
|
-
* @param {object} [opts.index] -
|
|
226
|
-
*
|
|
227
|
-
* index skips reporting every reference as dead.
|
|
143
|
+
* @param {object} [opts.index] - Calendar context for authored birth dates.
|
|
144
|
+
* @param {string} [opts.currentDate="720.1"] - The configured neutral current date.
|
|
228
145
|
* @returns {object[]} Findings — at most one `warning`, naming both values.
|
|
229
146
|
*/
|
|
230
|
-
export function checkBeingAge(note, { index } = {}) {
|
|
147
|
+
export function checkBeingAge(note, { index, currentDate = DEFAULT_CURRENT_DATE } = {}) {
|
|
231
148
|
if (!isBeingType(note?.type ?? note?.fm?.type)) return [];
|
|
232
149
|
const data = dataOf(note?.fm);
|
|
233
150
|
if (!data) return [];
|
|
@@ -235,10 +152,7 @@ export function checkBeingAge(note, { index } = {}) {
|
|
|
235
152
|
const bornRaw = data.born;
|
|
236
153
|
const ageRaw = data.age;
|
|
237
154
|
if (ageRaw === undefined || ageRaw === null || ageRaw === "") return [];
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const present = worldInvariants(index).present;
|
|
241
|
-
const computed = computeAge(bornRaw, present, reckoningContext(index));
|
|
155
|
+
const computed = computeAge(bornRaw, currentDate, reckoningContext(index));
|
|
242
156
|
if (computed === null) return [];
|
|
243
157
|
|
|
244
158
|
const authored = parseAgeMagnitude(ageRaw);
|