@heroiclands/package-build 22.21.0 → 22.22.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 +191 -0
- package/assets/styles/content-base.css +162 -0
- package/bin/content-commands.mjs +26 -3
- package/bin/package-build.mjs +15 -2
- package/ci/content-format-reference.mjs +68 -1
- package/config.mjs +26 -0
- package/content-config.mjs +73 -4
- package/docs/api.md +36 -18
- package/docs/authoring/dates-and-calendars.md +4 -2
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +237 -28
- package/docs/configuration.md +27 -1
- package/docs/content-format.md +3 -1
- package/docs/guides/reference-pages.md +15 -0
- package/docs/reference/format-details.md +224 -46
- package/docs/reference/note-types.md +21 -4
- package/engine/actor-compiler.mjs +23 -0
- package/engine/anchored-sections.mjs +35 -4
- package/engine/anchors.mjs +10 -20
- package/engine/base-compiler.mjs +86 -21
- package/engine/base-styles.mjs +149 -0
- package/engine/bundles.mjs +0 -1
- package/engine/compendiums.mjs +8 -1
- package/engine/content-blocks.mjs +410 -0
- package/engine/content-captions.mjs +57 -8
- package/engine/content-footnotes.mjs +189 -0
- package/engine/content-format-check.mjs +15 -12
- package/engine/content-index.mjs +2 -2
- package/engine/content-links.mjs +14 -6
- package/engine/content-tables.mjs +45 -4
- package/engine/content-tasklists.mjs +189 -0
- package/engine/docs-checks.mjs +4 -4
- package/engine/extension-attributes.mjs +48 -2
- package/engine/frontmatter-lint.mjs +47 -64
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +308 -0
- package/engine/helpers.mjs +14 -13
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +83 -8
- package/engine/journals.mjs +36 -17
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +178 -53
- package/engine/note-dates.mjs +49 -0
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +172 -37
- package/engine/page-lists.mjs +358 -0
- package/engine/pdf-build.mjs +76 -42
- package/engine/pdf-render.mjs +422 -49
- package/engine/rank-ladder.mjs +198 -0
- package/engine/reckoning-markers.mjs +1 -1
- package/engine/retired-fields.mjs +8 -13
- package/engine/scenes.mjs +10 -3
- package/engine/site-build.mjs +41 -12
- package/engine/site-config.mjs +10 -1
- package/engine/site-index.mjs +18 -2
- package/engine/site-preview.mjs +18 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standings.mjs +89 -6
- package/engine/system-block.mjs +12 -14
- package/engine/web-wikilinks.mjs +48 -1
- package/hm3/actors.mjs +6 -3
- package/manifest.mjs +13 -0
- package/package.json +1 -1
- package/sohl/actors.mjs +22 -99
- package/sohl/item-fields.mjs +4 -5
- package/sohl/items.mjs +1 -1
- package/sohl/note-schemas.mjs +11 -21
- package/types/config.d.mts +9 -0
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/anchored-sections.d.mts +24 -0
- package/types/engine/base-compiler.d.mts +19 -0
- package/types/engine/base-styles.d.mts +93 -0
- package/types/engine/content-blocks.d.mts +87 -0
- package/types/engine/content-captions.d.mts +14 -1
- package/types/engine/content-footnotes.d.mts +73 -0
- package/types/engine/content-format-check.d.mts +8 -6
- package/types/engine/content-tables.d.mts +12 -4
- package/types/engine/content-tasklists.d.mts +72 -0
- package/types/engine/extension-attributes.d.mts +22 -1
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +174 -0
- package/types/engine/helpers.d.mts +7 -4
- package/types/engine/homepage.d.mts +7 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-render.d.mts +6 -1
- package/types/engine/journals.d.mts +14 -7
- package/types/engine/markdown-expressions.d.mts +23 -0
- package/types/engine/note-frontmatter.d.mts +1 -4
- package/types/engine/note-vocabulary.d.mts +82 -6
- package/types/engine/page-lists.d.mts +126 -0
- package/types/engine/pdf-render.d.mts +72 -2
- package/types/engine/rank-ladder.d.mts +32 -0
- package/types/engine/retired-fields.d.mts +4 -6
- package/types/engine/site-build.d.mts +13 -1
- package/types/engine/site-config.d.mts +9 -0
- package/types/engine/site-index.d.mts +13 -1
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -7
- package/types/engine/standings.d.mts +21 -3
- package/types/engine/system-block.d.mts +11 -12
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/hm3/actors.d.mts +3 -3
- package/types/sohl/actors.d.mts +6 -7
- package/engine/content-admonitions.mjs +0 -114
- package/engine/content-secrets.mjs +0 -112
- package/types/engine/content-admonitions.d.mts +0 -24
- package/types/engine/content-secrets.d.mts +0 -17
package/content-config.mjs
CHANGED
|
@@ -1580,6 +1580,7 @@ export const DERIVED_HUGO_KEYS = Object.freeze({
|
|
|
1580
1580
|
"params.brand": "the organisation's brand links, in `engine/site-config.mjs`",
|
|
1581
1581
|
"params.notfound": "`site.notfound`",
|
|
1582
1582
|
"markup.goldmark.renderer.unsafe": "the toolchain, whose pages carry raw HTML",
|
|
1583
|
+
"markup.goldmark.extensions.taskList": "the toolchain, whose format has no checkbox",
|
|
1583
1584
|
menu: "the navigation `package-build deps fetch` caches from heroiclands.org",
|
|
1584
1585
|
});
|
|
1585
1586
|
|
|
@@ -1903,6 +1904,44 @@ function normalizePdf(value, rootDir) {
|
|
|
1903
1904
|
});
|
|
1904
1905
|
}
|
|
1905
1906
|
|
|
1907
|
+
/**
|
|
1908
|
+
* A dotted version string as the three-element tuple Foundry compares.
|
|
1909
|
+
*
|
|
1910
|
+
* A missing component reads as `0`, so `"14"` compares as `14.0.0` — the
|
|
1911
|
+
* common way an author writes a major-only floor or pin. A non-numeric
|
|
1912
|
+
* component reads as `0` too, rather than throwing: this runs on values
|
|
1913
|
+
* {@link requireNonEmptyString} has already accepted as non-empty strings,
|
|
1914
|
+
* and a version with a qualifier (`"14.359.0-beta"`) is not this function's
|
|
1915
|
+
* place to refuse.
|
|
1916
|
+
*
|
|
1917
|
+
* @param {string} version - A dotted version string.
|
|
1918
|
+
* @returns {readonly [number, number, number]} Major, minor, patch.
|
|
1919
|
+
*/
|
|
1920
|
+
function versionTriple(version) {
|
|
1921
|
+
const parts = version.split(".");
|
|
1922
|
+
return [0, 1, 2].map((index) => {
|
|
1923
|
+
const n = Number.parseInt(parts[index] ?? "0", 10);
|
|
1924
|
+
return Number.isNaN(n) ? 0 : n;
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* Whether `verified` names a build below `minimum`, compared as version
|
|
1930
|
+
* triples.
|
|
1931
|
+
*
|
|
1932
|
+
* @param {string} minimum - The declared floor.
|
|
1933
|
+
* @param {string} verified - The declared verified build.
|
|
1934
|
+
* @returns {boolean} `true` when `verified` sorts below `minimum`.
|
|
1935
|
+
*/
|
|
1936
|
+
function verifiedBelowMinimum(minimum, verified) {
|
|
1937
|
+
const a = versionTriple(verified);
|
|
1938
|
+
const b = versionTriple(minimum);
|
|
1939
|
+
for (let i = 0; i < 3; i++) {
|
|
1940
|
+
if (a[i] !== b[i]) return a[i] < b[i];
|
|
1941
|
+
}
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1906
1945
|
/**
|
|
1907
1946
|
* Validate a Foundry version range.
|
|
1908
1947
|
*
|
|
@@ -1912,6 +1951,12 @@ function normalizePdf(value, rootDir) {
|
|
|
1912
1951
|
* load-bearing there is `verified`, and a relationship may reasonably name a
|
|
1913
1952
|
* package without pinning a floor at all.
|
|
1914
1953
|
*
|
|
1954
|
+
* **The two keys are a range, not two independent facts.** Foundry reads
|
|
1955
|
+
* `verified` as the build inside `minimum`'s floor that was actually tested,
|
|
1956
|
+
* so a `verified` below `minimum` names a build the package already refuses
|
|
1957
|
+
* to install on. Caught only when both are present — a relationship naming
|
|
1958
|
+
* `verified` alone has nothing to compare it against.
|
|
1959
|
+
*
|
|
1915
1960
|
* @param {unknown} value - The declared range, or `undefined`.
|
|
1916
1961
|
* @param {string} where - Dotted path, for the error.
|
|
1917
1962
|
* @param {boolean} [requireMinimum] - Whether `minimum` must be present.
|
|
@@ -1929,6 +1974,18 @@ function normalizeCompatibility(value, where, requireMinimum = true) {
|
|
|
1929
1974
|
if (input.verified !== undefined) {
|
|
1930
1975
|
out.verified = requireNonEmptyString(input.verified, `${where}.verified`);
|
|
1931
1976
|
}
|
|
1977
|
+
if (
|
|
1978
|
+
out.minimum !== undefined &&
|
|
1979
|
+
out.verified !== undefined &&
|
|
1980
|
+
verifiedBelowMinimum(out.minimum, out.verified)
|
|
1981
|
+
) {
|
|
1982
|
+
fail(
|
|
1983
|
+
`${where}.verified`,
|
|
1984
|
+
`declares \`${out.verified}\`, below \`${where}.minimum\`'s \`${out.minimum}\` — ` +
|
|
1985
|
+
"Foundry reads the two as one range, so a build below the floor is " +
|
|
1986
|
+
"never the one verified against it",
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1932
1989
|
return Object.freeze(out);
|
|
1933
1990
|
}
|
|
1934
1991
|
|
|
@@ -2022,6 +2079,21 @@ function normalizeSystems(value) {
|
|
|
2022
2079
|
// declaration that cannot answer "which version was this built
|
|
2023
2080
|
// against" is the gap this block exists to close.
|
|
2024
2081
|
const verified = requireNonEmptyString(compat.verified, `${at}.compatibility.verified`);
|
|
2082
|
+
const minimum =
|
|
2083
|
+
compat.minimum === undefined || compat.minimum === null ?
|
|
2084
|
+
null
|
|
2085
|
+
: requireNonEmptyString(compat.minimum, `${at}.compatibility.minimum`);
|
|
2086
|
+
// Caught here too: the shape is the one {@link normalizeCompatibility}
|
|
2087
|
+
// validates, and the same contradiction is possible wherever it
|
|
2088
|
+
// appears.
|
|
2089
|
+
if (minimum !== null && verifiedBelowMinimum(minimum, verified)) {
|
|
2090
|
+
fail(
|
|
2091
|
+
`${at}.compatibility.verified`,
|
|
2092
|
+
`declares \`${verified}\`, below \`${at}.compatibility.minimum\`'s ` +
|
|
2093
|
+
`\`${minimum}\` — Foundry reads the two as one range, so a build ` +
|
|
2094
|
+
"below the floor is never the one verified against it",
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2025
2097
|
|
|
2026
2098
|
out[id] = Object.freeze({
|
|
2027
2099
|
manifest:
|
|
@@ -2029,10 +2101,7 @@ function normalizeSystems(value) {
|
|
|
2029
2101
|
null
|
|
2030
2102
|
: requireNonEmptyString(spec.manifest, `${at}.manifest`),
|
|
2031
2103
|
compatibility: Object.freeze({
|
|
2032
|
-
minimum
|
|
2033
|
-
compat.minimum === undefined || compat.minimum === null ?
|
|
2034
|
-
null
|
|
2035
|
-
: requireNonEmptyString(compat.minimum, `${at}.compatibility.minimum`),
|
|
2104
|
+
minimum,
|
|
2036
2105
|
verified,
|
|
2037
2106
|
}),
|
|
2038
2107
|
});
|
package/docs/api.md
CHANGED
|
@@ -347,7 +347,7 @@ The note types the engine itself declares — vocabulary that is a fact about th
|
|
|
347
347
|
|
|
348
348
|
### `engine.noteVocabulary`
|
|
349
349
|
|
|
350
|
-
An addressed content note accepts exactly these top-level keys, in this order: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `
|
|
350
|
+
An addressed content note accepts exactly these top-level keys, in this order: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `dnd5e`, `hm3`, `sohl`. A key outside this list is an error at its own line. The system blocks are one per system the address vocabulary recognizes, sorted by name after the fixed keys, so a further system is a change to `SYSTEM_IDS` alone. No dnd5e-specific document types are declared. Within that shape, the closed vocabularies are the `data:` container and each type's `subType`.
|
|
351
351
|
|
|
352
352
|
| Export | Signature | Returns | Use it when |
|
|
353
353
|
| ------------------------- | -------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
@@ -355,6 +355,9 @@ An addressed content note accepts exactly these top-level keys, in this order: `
|
|
|
355
355
|
| `GM_TAG` | `const GM_TAG` | — | naming the declared tag that marks a GM-only note |
|
|
356
356
|
| `BEING_ARCHETYPES` | `const BEING_ARCHETYPES` | `Readonly<Record<string, string>>` | looking up the case-sensitive being archetypes and their meanings |
|
|
357
357
|
| `NOTE_VOCABULARY` | `const NOTE_VOCABULARY` | — | looking up, per content type, its `data:` field list and its closed `subType` values |
|
|
358
|
+
| `NOTE_TOP_LEVEL_FIELDS` | `const NOTE_TOP_LEVEL_FIELDS` | `readonly TopLevelFieldSpec[]` | reading the top-level keys a note accepts, in formatted order, with what each one means |
|
|
359
|
+
| `NOTE_TOP_LEVEL_KEYS` | `const NOTE_TOP_LEVEL_KEYS` | `readonly string[]` | reading just the key names, in the order the formatter writes them |
|
|
360
|
+
| `NOTE_TOP_LEVEL_KEY_SET` | `const NOTE_TOP_LEVEL_KEY_SET` | `ReadonlySet<string>` | testing whether a top-level key is one the note format declares |
|
|
358
361
|
| `SHARED_DATA_FIELDS` | `const SHARED_DATA_FIELDS` | `readonly DataFieldSpec[]` | reading the `data:` keys every type accepts, whatever it is — the art slots legal everywhere |
|
|
359
362
|
| `DECLARED_TAGS` | `const DECLARED_TAGS` | — | looking up the tags a note type may declare, grouped |
|
|
360
363
|
| `declaredTags` | `declaredTags(type, groups)` | `readonly string[]` | reading the declared tags a note of a type may carry, flattened |
|
|
@@ -420,15 +423,15 @@ The infobox declarations this toolchain ships, one per system, and the single ca
|
|
|
420
423
|
|
|
421
424
|
Drawing a declared infobox: HTML for separate Foundry pages appended after authored pages, Typst panels after each note’s authored body in the book. The website's boxes travel in front matter and the site theme draws them, so there is no renderer for it here.
|
|
422
425
|
|
|
423
|
-
| Export | Signature
|
|
424
|
-
| ---------------------- |
|
|
425
|
-
| `infoboxTypstPreamble` | `infoboxTypstPreamble()`
|
|
426
|
-
| `infoboxesToHtml` | `infoboxesToHtml(boxes, options)`
|
|
427
|
-
| `infoboxesToTypst` | `infoboxesToTypst(boxes, options)`
|
|
428
|
-
| `linkToHtml` | `linkToHtml(value)`
|
|
429
|
-
| `linkToTypst` | `linkToTypst(value, links, labelFor)` | `string` | drawing a link value as a cross-reference into the book |
|
|
430
|
-
| `linkToUuid` | `linkToUuid(value)`
|
|
431
|
-
| `sectionHasContent` | `sectionHasContent(section)`
|
|
426
|
+
| Export | Signature | Returns | Use it when |
|
|
427
|
+
| ---------------------- | ------------------------------------------- | --------- | ------------------------------------------------------------------------ |
|
|
428
|
+
| `infoboxTypstPreamble` | `infoboxTypstPreamble()` | `string` | emitting the panel definitions the book's bodies call, once per document |
|
|
429
|
+
| `infoboxesToHtml` | `infoboxesToHtml(boxes, options)` | `string` | drawing the boxes as `<details>` disclosures, open by default |
|
|
430
|
+
| `infoboxesToTypst` | `infoboxesToTypst(boxes, options)` | `string` | drawing the boxes as panels that break between their sections |
|
|
431
|
+
| `linkToHtml` | `linkToHtml(value)` | `string` | drawing a link value as an anchor on its URL |
|
|
432
|
+
| `linkToTypst` | `linkToTypst(value, links, labelFor, site)` | `string` | drawing a link value as a cross-reference into the book |
|
|
433
|
+
| `linkToUuid` | `linkToUuid(value)` | `string` | drawing a link value as a Foundry document reference |
|
|
434
|
+
| `sectionHasContent` | `sectionHasContent(section)` | `boolean` | deciding whether a section holds anything worth drawing |
|
|
432
435
|
|
|
433
436
|
### `engine.noteState`
|
|
434
437
|
|
|
@@ -692,12 +695,14 @@ invokes the check during frontmatter lint.
|
|
|
692
695
|
|
|
693
696
|
### `engine.markdownExpressions`
|
|
694
697
|
|
|
695
|
-
`renderMarkdownExpressions(body, { fm, dates, sqlResults, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar`; `gt`, `gte`, `lt`, `lte`, `eq`, `and`, `or`, and `not` evaluate explicit comparisons. The `sql` helper reads a prepared scalar result, which must contain one row and one column. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. The renderer returns `{ markdown, findings }`, with source positions for invalid expressions. Site, Foundry and PDF passes invoke it after generated tables expand and before links resolve. Hugo shortcodes remain literal for the site renderer.
|
|
698
|
+
`renderMarkdownExpressions(body, { fm, dates, sqlResults, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar`; `gt`, `gte`, `lt`, `lte`, `eq`, `and`, `or`, and `not` evaluate explicit comparisons. The `sql` helper reads a prepared scalar result, which must contain one row and one column. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. `EXPRESSION_HELPERS` is the one list of helpers: the engine registers from it and the authoring document is checked against it, so each name there carries the parameters it takes and a sentence on what it does. The renderer returns `{ markdown, findings }`, with source positions for invalid expressions. Site, Foundry and PDF passes invoke it after generated tables expand and before links resolve. Hugo shortcodes remain literal for the site renderer.
|
|
696
699
|
|
|
697
|
-
| Export | Signature | Returns | Use it when
|
|
698
|
-
| --------------------------- | ------------------------------------------ | ---------------------- |
|
|
699
|
-
| `renderMarkdownExpressions` | `renderMarkdownExpressions(body, options)` | `{markdown, findings}` | expanding authored expressions in prose
|
|
700
|
-
| `sqlQueriesInMarkdown` | `sqlQueriesInMarkdown(body)`
|
|
700
|
+
| Export | Signature | Returns | Use it when |
|
|
701
|
+
| --------------------------- | ------------------------------------------ | ---------------------- | ------------------------------------------------ |
|
|
702
|
+
| `renderMarkdownExpressions` | `renderMarkdownExpressions(body, options)` | `{markdown, findings}` | expanding authored expressions in prose |
|
|
703
|
+
| `sqlQueriesInMarkdown` | `sqlQueriesInMarkdown(body, fm?)` | query strings | preparing inline SQL alongside generated tables |
|
|
704
|
+
| `EXPRESSION_HELPERS` | `EXPRESSION_HELPERS` | helpers by name | listing what an expression may call |
|
|
705
|
+
| `EXPRESSION` | `const EXPRESSION` | `RegExp` | recognising an expression a pass has yet to read |
|
|
701
706
|
|
|
702
707
|
### `engine.reckoningMarkers`
|
|
703
708
|
|
|
@@ -1070,6 +1075,17 @@ An image saying how wide it is and where it sits. A directive in the curly-attri
|
|
|
1070
1075
|
| `renderImageFigures` | `function renderImageFigures(body, resolveSrc)` | {string} The same body, with each block image as a `<figure>`. | Rewrite every block image in a body into the figure the website publishes. |
|
|
1071
1076
|
| `imagePlugin` | `function imagePlugin()` | {(md: object) => void} A markdown-it plugin. | A markdown-it plugin that reads an image's directive and renders its figure. |
|
|
1072
1077
|
|
|
1078
|
+
### `engine.contentTaskLists`
|
|
1079
|
+
|
|
1080
|
+
A list item opened with `[ ]` or `[x]`, reported. The format has no checkbox: a list is written in plain markdown, and the two characters right after a list item's marker are read as the start of its own text on every surface.
|
|
1081
|
+
|
|
1082
|
+
| Export | Signature | Returns | Use it when |
|
|
1083
|
+
| ---------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
1084
|
+
| `TASK_LIST_MARKER` | `const TASK_LIST_MARKER` | — | A list item's marker, immediately followed by `[ ]` or `[x]` and a space. |
|
|
1085
|
+
| `taskListMessage` | `function taskListMessage(marker)` | {string} The message, unpunctuated at the end as a finding is. | What a note opening a list item with `[ ]` or `[x]` is told. |
|
|
1086
|
+
| `checkTaskLists` | `function checkTaskLists(body, file,` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} One finding per item, in source order. | Every list item opened with `[ ]` or `[x]` in one note's body. |
|
|
1087
|
+
| `lintContentTaskLists` | `function lintContentTaskLists(contentBase,` | `{{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}}` The findings, and how many files were read. | Walk a content tree and report every list item opened with `[ ]` or `[x]`. |
|
|
1088
|
+
|
|
1073
1089
|
### `engine.pathnames`
|
|
1074
1090
|
|
|
1075
1091
|
One authored pathname, and the four addresses it resolves to. A note names a file once — in `img:`, in `data.portrait:`, in the body of a markdown image — and the first segment says which package owns it when an `assets/` follows. Every surface derives its own address from that one statement: the path inside a Foundry install, the file in the owning repository's tree, the address the website serves, and where the book stages its copy.
|
|
@@ -1111,9 +1127,9 @@ Prepared SQL content tables are expanded into Markdown before link resolution.
|
|
|
1111
1127
|
A query selecting no rows reports an error unless its fence states
|
|
1112
1128
|
`{allow-empty=true}`.
|
|
1113
1129
|
|
|
1114
|
-
| Export | Signature
|
|
1115
|
-
| --------------------- |
|
|
1116
|
-
| `expandContentTables` | `expandContentTables(markdown, { source, sqlTables })` | `{markdown: string, errors: object[], lineMap: object[], warnings: object[]}` | Replace prepared SQL fences and preserve source locations. |
|
|
1130
|
+
| Export | Signature | Returns | Use it when |
|
|
1131
|
+
| --------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
1132
|
+
| `expandContentTables` | `expandContentTables(markdown, { source, sqlTables, pageLists })` | `{markdown: string, errors: object[], lineMap: object[], warnings: object[]}` | Replace prepared SQL fences and page lists, and preserve source locations. |
|
|
1117
1133
|
|
|
1118
1134
|
### `engine.helpers`
|
|
1119
1135
|
|
|
@@ -1250,6 +1266,8 @@ A note's markdown, and a document plan, rendered as Typst source. **This module
|
|
|
1250
1266
|
| `iconNamesIn` | `function iconNamesIn(markdown)` | {string[]} The names, in order of appearance, with repeats. | Every icon name a body uses, so a build can resolve them once. |
|
|
1251
1267
|
| `BOOK_IMAGE_WIDTHS` | `const BOOK_IMAGE_WIDTHS` | — | The book measure requested by each named image size. |
|
|
1252
1268
|
| `bookTypstPreamble` | `function bookTypstPreamble()` | {string} Typst markup. | The Typst definitions the book's page furniture is drawn with. |
|
|
1269
|
+
| `stripHtmlComments` | `function stripHtmlComments(markdown)` | {string} The same body, its comments replaced by newlines. | A markdown body with its HTML comments taken out. |
|
|
1270
|
+
| `absolutePageUrl` | `function absolutePageUrl(url, site)` | {string} An absolute URL a reader of the book can follow. | A page address as a reader of the book can follow it. |
|
|
1253
1271
|
|
|
1254
1272
|
### `engine.pdfFonts`
|
|
1255
1273
|
|
|
@@ -9,9 +9,11 @@ subType: howto
|
|
|
9
9
|
|
|
10
10
|
A canonical date uses the world's year and day count without choosing a culture's calendar. Write `<year>[.<day>[:HHMMSS]]`: `720` covers year 720, `720.136` covers its day 136, and `720.136:143005` identifies 14:30:05 on that day. The dot separates the year from the **day of year**, not a month. Negative years are valid, and the day must fit the world's declared year length. Prefix a date with `~` when its uncertainty extends outside the stated year, day, or second. `unknown` records an occurrence whose date is not known, in fields that permit it.
|
|
11
11
|
|
|
12
|
+
**Put a date that states a day in quotes.** Write `born: "667.130"`, not `born: 667.130`. Unquoted, YAML reads the scalar as a number, and a number keeps no trailing zero — `667.130` becomes `667.13`, which is day 13 rather than day 130, and the authored digits are gone before any reader of the date can object. A date whose lost zero would still name a day inside the world's year is therefore refused, naming the line and both readings, so quoting it is the fix. A bare year such as `born: 667` loses nothing and needs no quotes.
|
|
13
|
+
|
|
12
14
|
## Define a calendar
|
|
13
15
|
|
|
14
|
-
A `lore` note with `subType: calendar` names months, weekdays, seasons, named days, eras, and date formats. Its `data.epoch` is the **canonical day that equals calendar year 1, day 1**. For a calendar beginning on canonical day `1.91`, write `epoch: 1.91
|
|
16
|
+
A `lore` note with `subType: calendar` names months, weekdays, seasons, named days, eras, and date formats. Its `data.epoch` is the **canonical day that equals calendar year 1, day 1**. For a calendar beginning on canonical day `1.91`, write `epoch: "1.91"` — quoted, like every canonical date that states a day: calendar year 1 still starts at its own day 1. A calendar does not state the length of the world year: its months must sum to `data.year.days` on the world's `place` note.
|
|
15
17
|
|
|
16
18
|
```yaml
|
|
17
19
|
shortcode: commoncal
|
|
@@ -19,7 +21,7 @@ name: { full: The Common Calendar }
|
|
|
19
21
|
type: lore
|
|
20
22
|
subType: calendar
|
|
21
23
|
data:
|
|
22
|
-
epoch: 1.1
|
|
24
|
+
epoch: "1.1"
|
|
23
25
|
months:
|
|
24
26
|
- { name: Floralis, abbreviation: Flor, days: 30 }
|
|
25
27
|
- { name: Lusenar, abbreviation: Luse, days: 31 }
|
|
@@ -7,7 +7,7 @@ subType: howto
|
|
|
7
7
|
|
|
8
8
|
# Frontmatter and system blocks
|
|
9
9
|
|
|
10
|
-
HeroicLands notes begin with YAML frontmatter. A file is recognized as a note when its opening frontmatter has nonempty `shortcode` and `type` values. Ordinary Markdown files need neither. Keep top-level keys in this order, omitting keys that do not apply: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `hm3`, `sohl`. Any other top-level key is an error.
|
|
10
|
+
HeroicLands notes begin with YAML frontmatter. A file is recognized as a note when its opening frontmatter has nonempty `shortcode` and `type` values. Ordinary Markdown files need neither. Keep top-level keys in this order, omitting keys that do not apply: `shortcode`, `name`, `type`, `subType`, `description`, `tags`, `data`, `dnd5e`, `hm3`, `sohl`. Any other top-level key is an error at its own line, naming the closed vocabulary and the declared key it was most likely meant to be. The [note-type reference](../reference/note-types.md#top-level-keys) says what each one means.
|
|
11
11
|
|
|
12
12
|
```yaml
|
|
13
13
|
---
|
|
@@ -35,7 +35,7 @@ The [note-type reference](../reference/note-types.md) lists every field accepted
|
|
|
35
35
|
|
|
36
36
|
A being's `data.archetypes` names the roles it can fill in an adventure. Choose every specific role that fits, such as `entertainer` for a performer or `guildsperson` for someone whose professional training or connections matter. Use `[commoner]` when no specific archetype fits. `commoner` cannot share the list with another value. The [archetype reference](../reference/format-details.md#actors) defines the available roles.
|
|
37
37
|
|
|
38
|
-
A `sohl:` or `
|
|
38
|
+
A `sohl:`, `hm3:` or `dnd5e:` block holds values for that Foundry system. Its `system:` child contains values sent to the system's DataModel. The system block can override shared routing and art where the declared field permits it. `data.pack` and `data.packFolder` are shared routes; a system-specific route belongs in that system block. The [detailed mapping reference](../reference/format-details.md#mappings-every-type-shares) and [system-block explanation](../reference/format-details.md#frontmatter-has-three-regions) cover precedence and emitted fields.
|
|
39
39
|
|
|
40
40
|
One body describes the subject for both systems. A being's `{#appearance}` and `{#dossier}` sections feed SoHL Actor `system.appearance` and `system.dossier`, and HM3 Actor `system.description` and `system.biography`. An Item's `{#description}` page becomes a shared JournalEntryPage. Its resolved `@UUID[...]` pointer is stored in SoHL Item `system.docHtml` and HM3 Item `system.description`. An Item with no `{#description}` anchor uses its first page. Other H1 pages remain linkable.
|
|
41
41
|
|
|
@@ -17,7 +17,11 @@ An Address names content independently of its filename or folder. Its full form
|
|
|
17
17
|
|
|
18
18
|
The part after `|` is the displayed link text or an embed's alternative text. An empty label uses the target's current name in a regular link and marks an embed decorative. A missing pipe is an error. Search may use `name.full` and aliases; authored links use Addresses. The [Address reference](../reference/format-details.md#addresses) explains short forms, package qualification, and ambiguity diagnostics.
|
|
19
19
|
|
|
20
|
-
A link to a note tagged `draft` retains its target and carries a visible draft cue. In a book, the cue reads `(draft)` after the link.
|
|
20
|
+
A link to a note tagged `draft` retains its target and carries a visible draft cue. In a book, the cue reads `(draft)` after the link.
|
|
21
|
+
|
|
22
|
+
**An address that resolves nowhere is an error on all three surfaces**, worded the same way by each. The pack build refuses the note and produces no pack from it. The site build and the book build write what they can and fail the run: a page marks the link for the reader, and a book sets `(unresolved link)` after its label. The book is written so the page carrying the mistake can be found, not because the mistake is tolerated there.
|
|
23
|
+
|
|
24
|
+
An HTML comment is an aside to whoever reads the note next, and no surface shows one — the packs and the website emit it into the HTML, where it stays a comment, and a book leaves it out. Write a `markdownlint` pragma or a note about a quoted passage freely. A comment inside a fence or a code span is an example and is printed as written, and a comment that is never closed is an error.
|
|
21
25
|
|
|
22
26
|
## Journal pages and anchors
|
|
23
27
|
|
|
@@ -33,7 +37,29 @@ The harbor faces west.
|
|
|
33
37
|
Travelers present their papers here. See [[#harbor|the harbor]].
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
A `:::caption` block starts a JournalEntryPage too, named for the caption's own label, in Foundry alone — the web and the book keep the caption in the flow of the page around it. The prose introducing a captioned block and the block itself land on two different Foundry pages, so a reference such as `[[#trade|]]` written just above a caption sends a Foundry reader to a different page than the one carrying the sentence. Give the introducing prose its own heading, or accept that following the reference leaves the page, before relying on a caption to sit inside a longer page's flow.
|
|
41
|
+
|
|
42
|
+
A heading's braces hold an attribute block, not an anchor alone: `#slug` names the anchor, `.class` adds a CSS class, and `key="value"` writes an attribute. The website and a Foundry journal carry all three onto the heading; a book keeps the anchor and drops the rest, because Typst has no stylesheet to name a class in. Braces holding anything but an attribute block — `# The set {a, b}` — are reported at the heading's line and set as written.
|
|
43
|
+
|
|
44
|
+
Another note can link to `[[lore-harbor#customs-house|Customs House]]`. For Items, `{#description}` selects the JournalEntryPage both systems reference, and accepts a heading at any level. A map note can use an anchored heading for a scene pin; the compiler replaces the pin's exported JournalEntry and page IDs with the built note's IDs and uses the heading text as its label. See [map notes](../reference/format-details.md#type-map) for scene fields.
|
|
45
|
+
|
|
46
|
+
### A section players must not read
|
|
47
|
+
|
|
48
|
+
Write `.secret` on the heading that opens the section:
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
# The Cellar {#cellar .secret}
|
|
52
|
+
|
|
53
|
+
The contraband is behind the false wall.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The section runs from that heading to the next heading that opens a page. In Foundry the page is the GM's alone. On the website and in the book the section sits inside the same expandable spoiler and the same labelled block a `:::secret` block gets. The heading keeps its id, so `[[lore-inn#cellar|the cellar]]` still resolves to it.
|
|
57
|
+
|
|
58
|
+
**On the website this is a spoiler, not access control.** The withheld section is in the published page, so anyone who opens the element or reads the page source reads it. Foundry is the only surface that withholds anything. Write nothing in a `.secret` section that would matter if a player read it on the web.
|
|
59
|
+
|
|
60
|
+
A `:::secret` block inside the section is legal and renders as it does anywhere else. `.secret` on a heading that opens no page — a lower-level heading with no anchor — covers no page, and is reported at its own line: give that heading an anchor, or raise it to the top level.
|
|
61
|
+
|
|
62
|
+
The special Actor anchors `{#appearance}` and `{#dossier}` feed the same authored sections to each system's Actor fields, and take an H1 only — not the lower-level heading a journal page may otherwise start from. The section runs to the next H1, nested headings included. An anchor declared on a lower heading is still found, and is a build error rather than a quietly empty field: move the heading to the top level, or drop the anchor if the section is meant to stay unaddressed prose.
|
|
37
63
|
|
|
38
64
|
## Captions and numbered references
|
|
39
65
|
|
|
@@ -55,29 +81,77 @@ The table displays **Table 1: Regional trade routes**, and the reference display
|
|
|
55
81
|
|
|
56
82
|
## Images and protected content
|
|
57
83
|
|
|
58
|
-
An image or icon embed stands on its own line. `size` accepts `auto`, `small`, `medium`, `large`, and `
|
|
84
|
+
An image or icon embed stands on its own line; one that does not is a lint error naming the line, because a width or a position means nothing applied to a word in the middle of a sentence. `size` accepts `auto`, `small`, `medium`, `large`, `xlarge` and `full-width`; the medium maps those names to suitable dimensions, and `full-width` is the full page in the book and the full content width elsewhere. `float` controls placement and takes `top-left`, `top-right`, `bottom-left`, `bottom-right` or `center`; on a page a float occupies the column measure, so only the vertical half of a corner position has an effect there. The asset remains an Address, so moving the file within its asset root does not change the link. See [assets](assets.md) and [image directives](../reference/format-details.md#images).
|
|
85
|
+
|
|
86
|
+
### Named blocks
|
|
59
87
|
|
|
60
|
-
|
|
88
|
+
Three named blocks set a passage apart from the prose around it. `:::secret`
|
|
89
|
+
marks GM material, `:::info` a neutral note, and `:::warn` a caution:
|
|
61
90
|
|
|
62
91
|
```markdown
|
|
63
|
-
:::
|
|
64
|
-
The
|
|
92
|
+
:::warn
|
|
93
|
+
The bridge closes during the spring flood.
|
|
65
94
|
:::
|
|
66
95
|
```
|
|
67
96
|
|
|
68
|
-
|
|
97
|
+
Each heads itself with its own name — **Secret**, **Info**, **Warn** — and takes
|
|
98
|
+
a closing `:::`. A name that is not one of the three is reported.
|
|
99
|
+
|
|
100
|
+
**A GM-only section holds a box.** A `:::secret` is a container for whatever the
|
|
101
|
+
GM reads, boxes included, and a box written inside one stays inside it. A box
|
|
102
|
+
holds no named block of its own, so a second box inside a box is reported. A
|
|
103
|
+
`:::caption` belongs to the caption construct and may be written inside any of
|
|
104
|
+
them.
|
|
69
105
|
|
|
70
|
-
|
|
71
|
-
|
|
106
|
+
**An attribute block follows the name.** It is the same braced grammar the other
|
|
107
|
+
body extensions use, so it carries an id, classes, and any attribute the element
|
|
108
|
+
should have. `title` is reserved and supplies the heading in place of the default:
|
|
72
109
|
|
|
73
110
|
```markdown
|
|
74
|
-
:::
|
|
75
|
-
The
|
|
111
|
+
:::secret {#harbor title="For the GM" .wide data-stage="two"}
|
|
112
|
+
The harbor master is working for the smugglers.
|
|
76
113
|
:::
|
|
77
114
|
```
|
|
78
115
|
|
|
79
|
-
|
|
80
|
-
|
|
116
|
+
That block carries the classes `secret wide`, the id `harbor`, and
|
|
117
|
+
`data-stage="two"`, and it heads **For the GM**. A title carries emphasis of its
|
|
118
|
+
own — `title="The *Genzet* only"` — and nothing else: a tag written there is
|
|
119
|
+
shown rather than obeyed. Set the id with `#id` and classes with `.class` rather
|
|
120
|
+
than through an `id=` or `class=` key, and note that an event handler such as
|
|
121
|
+
`onclick=` is refused rather than written.
|
|
122
|
+
|
|
123
|
+
**The name is the first class on the element, and the appearance belongs to a
|
|
124
|
+
stylesheet.** No block is emitted with an inline `style`, so a package restyles
|
|
125
|
+
one from its own sheet. `info` and `warn` are styled by the base stylesheet;
|
|
126
|
+
`secret` is left to Foundry, which styles `section.secret` itself and owns the
|
|
127
|
+
reveal control on it.
|
|
128
|
+
|
|
129
|
+
Each surface renders the same block its own way:
|
|
130
|
+
|
|
131
|
+
| Surface | Output |
|
|
132
|
+
| ------- | ---------------------------------------------------------------------------------- |
|
|
133
|
+
| Foundry | `<section class="secret" id="harbor">` with the title as its first line |
|
|
134
|
+
| Web | `<details class="secret" id="harbor">` the reader opens, titled in its `<summary>` |
|
|
135
|
+
| Book | a coloured print box headed by the title |
|
|
136
|
+
|
|
137
|
+
A `secret` with no id of its own is given one derived from its body on the
|
|
138
|
+
Foundry surface, because Foundry remembers a revealed section by its id.
|
|
139
|
+
|
|
140
|
+
**A secret is a presentation distinction, not access control.** Players who
|
|
141
|
+
deliberately inspect source or generated output may see it. The
|
|
142
|
+
[detailed reference](../reference/format-details.md#what-a-note-produces)
|
|
143
|
+
describes each medium's handling.
|
|
144
|
+
|
|
145
|
+
**One malformed block does not silence the others.** A block whose attributes do
|
|
146
|
+
not parse is reported at its own line and left as written; every well-formed
|
|
147
|
+
block in the same note still renders.
|
|
148
|
+
|
|
149
|
+
**A heading that would start its own journal page cannot be written inside a
|
|
150
|
+
block, or inside a caption.** An H1, or a heading of any level carrying an
|
|
151
|
+
`{#anchor}`, is reported at its own line there — starting a page would tear
|
|
152
|
+
the block in two, and a reader could open the second half with none of the
|
|
153
|
+
box around it. A lower heading with no anchor is unaffected and is the
|
|
154
|
+
ordinary way to structure a long box.
|
|
81
155
|
|
|
82
156
|
## Footnotes and definition lists
|
|
83
157
|
|
|
@@ -95,14 +169,18 @@ The road is passable in summer.[^season] The bridge has a toll.[^2]
|
|
|
95
169
|
[^2]: The toll is collected at the eastern gate.
|
|
96
170
|
```
|
|
97
171
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
the
|
|
172
|
+
A reference may be written anywhere prose can be written — a word in a
|
|
173
|
+
quotation, a bullet, a table cell, a heading, a named block, a caption. A
|
|
174
|
+
definition belongs at the top level of the note: indent a following paragraph
|
|
175
|
+
or code block by four spaces to include it in the same footnote, but never
|
|
176
|
+
inside a list, a block quote, or a table. A definition written there, or a
|
|
177
|
+
reference with none at the top level to resolve against, is reported at its
|
|
178
|
+
own line rather than rendered. On the web, linked footnotes appear in a
|
|
179
|
+
Footnotes section at the bottom of the HTML page. In Foundry, each
|
|
180
|
+
JournalEntryPage places its referenced footnotes in a Footnotes section at the
|
|
181
|
+
bottom of that page; definitions remain available across pages of the same
|
|
182
|
+
note. In a book, footnotes appear in smaller type at the bottom of the page
|
|
183
|
+
that contains their reference.
|
|
106
184
|
|
|
107
185
|
A definition list gives a term one or more definitions:
|
|
108
186
|
|
|
@@ -115,9 +193,19 @@ Janapada
|
|
|
115
193
|
The web and Foundry render a definition list with HTML `<dl>`, `<dt>`, and
|
|
116
194
|
`<dd>` elements. The book renders it as a term list.
|
|
117
195
|
|
|
118
|
-
##
|
|
196
|
+
## Lists
|
|
197
|
+
|
|
198
|
+
A list item's text begins right after its marker. `[ ]` and `[x]` there are
|
|
199
|
+
plain words, not a checkbox — write what is done into the item itself:
|
|
200
|
+
|
|
201
|
+
```markdown
|
|
202
|
+
- the harbor toll: paid
|
|
203
|
+
- the ferry crossing: owed
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## SQL generated tables
|
|
119
207
|
|
|
120
|
-
|
|
208
|
+
A fence marked `sql` is replaced by a table built from the content index. The query reads the frontmatter of every indexed note, so a table is written once and stays true as notes are added.
|
|
121
209
|
|
|
122
210
|
````markdown
|
|
123
211
|
```sql
|
|
@@ -125,13 +213,134 @@ SELECT name.full AS "Name" FROM notes WHERE type = 'lore' ORDER BY name.full
|
|
|
125
213
|
```
|
|
126
214
|
````
|
|
127
215
|
|
|
128
|
-
|
|
216
|
+
A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cell that is absent prints an em dash. A `|` or a line break inside a value is escaped, so neither breaks the table.
|
|
217
|
+
|
|
218
|
+
### What a query can read
|
|
219
|
+
|
|
220
|
+
| Relation | What it holds |
|
|
221
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `notes` | Every note except a stub. This is the one to use unless a stub is wanted. |
|
|
223
|
+
| `entries` | Every note, stubs included. |
|
|
224
|
+
| `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
|
|
225
|
+
| `<package>.notes` | The same two relations for each package this one declares a dependency on, in a schema named for it. |
|
|
226
|
+
|
|
227
|
+
Nested frontmatter is addressed exactly as a note writes it: `name.full`, `sohl.weight`, `data.born`. Every note carries a computed `state` column of `full`, `draft` or `stub`. Notes tagged `gm` are absent from the relations on public surfaces.
|
|
228
|
+
|
|
229
|
+
### Fence attributes
|
|
230
|
+
|
|
231
|
+
A braced list may follow the language on the opening fence. Two attributes are accepted, and anything else is reported by name.
|
|
232
|
+
|
|
233
|
+
| Attribute | Value | Default | What it does |
|
|
234
|
+
| --------------- | --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
235
|
+
| `allow-empty` | `true` or `false` | `false` | Whether a query selecting no rows is allowed. A zero-row result is a finding otherwise, because a table that silently prints nothing is the commonest way a query goes wrong unnoticed. |
|
|
236
|
+
| `section-level` | an integer from 1 through 6 | `2` | The heading level given to each group a `_section` column creates. |
|
|
237
|
+
|
|
238
|
+
````markdown
|
|
239
|
+
```sql {allow-empty=true section-level=3}
|
|
240
|
+
SELECT name.full AS "Name" FROM notes WHERE type = 'being' AND data.gender = 'none'
|
|
241
|
+
```
|
|
242
|
+
````
|
|
243
|
+
|
|
244
|
+
A zero-row result reports which relation was queried: a query over `notes` that would have found rows in `entries` says so, so the fix is `FROM entries` rather than `{allow-empty=true}`.
|
|
245
|
+
|
|
246
|
+
### Two column names the renderer reads
|
|
247
|
+
|
|
248
|
+
Two aliases are instructions rather than columns, and neither is printed.
|
|
249
|
+
|
|
250
|
+
| Alias | What it does |
|
|
251
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
252
|
+
| `_ref` | The note a row points at, written as the `type-shortcode` address a wikilink resolves. The row's first column becomes a link to that note. |
|
|
253
|
+
| `_section` | Partitions the result into separate tables, each under a heading of its own. Order the query by the same expression, because a group ends where the value changes. |
|
|
254
|
+
|
|
255
|
+
```sql
|
|
256
|
+
SELECT type AS "_section", name.full AS "Name", address.slug AS "_ref"
|
|
257
|
+
FROM notes WHERE type IN ('lore', 'place') ORDER BY type, name.full
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
See [SQL details](../reference/format-details.md#content-tables) for query options.
|
|
261
|
+
|
|
262
|
+
## Page lists
|
|
263
|
+
|
|
264
|
+
A fence marked `pagelist` is replaced by a list of the pages carrying a tag. Where a content table answers "what do these notes say?", a page list answers "which pages belong to this group?" — and the group is expressed by intent rather than enumerated, so a page joins it by being tagged and leaves it by being untagged.
|
|
265
|
+
|
|
266
|
+
````markdown
|
|
267
|
+
```pagelist {tag="key-concept"}
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
````
|
|
271
|
+
|
|
272
|
+
**The fence holds nothing.** Everything the directive says about itself is written in the braced attribute block, in the same grammar every other body extension uses. Text written inside the fence is reported rather than printed.
|
|
273
|
+
|
|
274
|
+
Each page comes out as a link to the note, so one authored directive reaches a reader as a compendium link in Foundry, a page link on the website, and a page reference in the book. A link into a note tagged `draft` carries the same cue an authored link to it would.
|
|
275
|
+
|
|
276
|
+
### Page-list attributes
|
|
277
|
+
|
|
278
|
+
<!-- page-list-attributes:start -->
|
|
279
|
+
|
|
280
|
+
| Attribute | Value | Default | What it does |
|
|
281
|
+
| -------------- | ----------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
282
|
+
| `tag` | a tag | required | The tag a page carries to join the list. Matched the way `tags:` is read — case and a leading `#` are not significant, the spelling of the tag itself is. |
|
|
283
|
+
| `type` | a note type | every type | Restricts the list to one note type. A page's address is `<type>-<shortcode>`, so the type is what divides the corpus into kinds of page. |
|
|
284
|
+
| `sort` | `name` or `type` | `name` | The order the pages come out in. `name` is alphabetical; `type` groups the note types and orders by name within each. |
|
|
285
|
+
| `descriptions` | `true` or `false` | `false` | Whether each page's `description` follows its link. A page that states none carries its link alone. |
|
|
286
|
+
| `allow-empty` | `true` or `false` | `false` | Whether a tag carried by no page is allowed. It is a finding otherwise, because a heading with nothing under it is how a misspelled tag goes unnoticed. |
|
|
287
|
+
|
|
288
|
+
<!-- page-list-attributes:end -->
|
|
289
|
+
|
|
290
|
+
````markdown
|
|
291
|
+
```pagelist {tag="key-concept" type=lore sort=type descriptions=true}
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
````
|
|
295
|
+
|
|
296
|
+
That list names the `lore` notes tagged `key-concept`, grouped by type and alphabetical within each, with every page's `description` after its link. A page that states no description carries its link alone.
|
|
297
|
+
|
|
298
|
+
**A tag no page carries is a finding** at the fence's own line, naming the tag it looked for. A misspelled tag is the whole of what goes wrong here, and a heading with an empty list under it is how one goes unnoticed. Write `{allow-empty=true}` where a group that is genuinely empty is intended; the directive then leaves nothing behind rather than an empty list.
|
|
299
|
+
|
|
300
|
+
A page list names this package's own pages. A **stub** — a note with an empty body — publishes no page, so it joins no list. A note tagged `gm` is listed in Foundry and left out of the website and the book, exactly as it is left out of a content table's relations.
|
|
301
|
+
|
|
302
|
+
See [page-list details](../reference/format-details.md#page-lists) for the directive's full behaviour.
|
|
303
|
+
|
|
304
|
+
## Expressions
|
|
305
|
+
|
|
306
|
+
An expression in `{{ }}` is replaced by a value, inline in a sentence. The note's own frontmatter is the context, so a field is read by the path a note writes it at.
|
|
129
307
|
|
|
130
308
|
```markdown
|
|
131
|
-
{{name.full}} was born on {{dateformat
|
|
309
|
+
{{name.full}} was born on {{dateformat data.calendar data.born}}.
|
|
132
310
|
There are {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}} beings.
|
|
133
|
-
The index contains {{digits (sql "SELECT COUNT(*) FROM entries")}} entries.
|
|
134
|
-
{{and (gt 3 5) (lt 4 2)}}
|
|
135
311
|
```
|
|
136
312
|
|
|
137
|
-
|
|
313
|
+
### The rules of the construct
|
|
314
|
+
|
|
315
|
+
- **A field that the note does not carry is a finding, not a blank.** Expressions are compiled strictly, so a misspelled path is reported rather than quietly printing nothing.
|
|
316
|
+
- **An expression is one line.** It may not contain a line break or a brace.
|
|
317
|
+
- **A failed expression leaves its own text in the page** and reports the line and column it is written on, so the page is readable and the fault is locatable.
|
|
318
|
+
- **`\{{` writes an expression without evaluating it**, for an example in prose.
|
|
319
|
+
- **`{{{…}}}`, `{{<…}}` and `{{%…}}` are left alone.** A Hugo shortcode is not an expression.
|
|
320
|
+
- **An expression inside a code fence is left alone.**
|
|
321
|
+
- Helpers nest, with the inner call in parentheses: `{{words (sql "…")}}`.
|
|
322
|
+
|
|
323
|
+
### Helpers
|
|
324
|
+
|
|
325
|
+
<!-- expression-helpers:start -->
|
|
326
|
+
|
|
327
|
+
| Helper | Parameters | What it gives you |
|
|
328
|
+
| ------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
329
|
+
| `eq` | `a b` | True when both are the same value. Compares exactly, so 1 and "1" differ. |
|
|
330
|
+
| `gt` | `a b` | True when a is greater than b. Both must be finite numbers. |
|
|
331
|
+
| `gte` | `a b` | True when a is greater than or equal to b. Both must be finite numbers. |
|
|
332
|
+
| `lt` | `a b` | True when a is less than b. Both must be finite numbers. |
|
|
333
|
+
| `lte` | `a b` | True when a is less than or equal to b. Both must be finite numbers. |
|
|
334
|
+
| `not` | `value` | True when the value is false, zero, empty or absent. |
|
|
335
|
+
| `and` | `value …` | True when every value given is true. Takes two or more. |
|
|
336
|
+
| `or` | `value …` | True when any value given is true. Takes two or more. |
|
|
337
|
+
| `words` | `number` | The number spelled out for running prose — `1200` becomes one thousand two hundred. Whole numbers only; a decimal is a finding, and `digits` takes one. |
|
|
338
|
+
| `digits` | `number` | The number as a grouped numeral — `1200` becomes 1,200, and a fractional part is kept. |
|
|
339
|
+
| `sql` | `"query"` | The single value a query returns, for use inside a sentence. The query is a quoted string, the same SQL a table fence takes. |
|
|
340
|
+
| `dateformat` | `calendar date ["format"]` | A date written in a calendar. The calendar is an Address or its shortcode, the date is the value a note carries, and the optional third argument names an output pattern. |
|
|
341
|
+
|
|
342
|
+
<!-- expression-helpers:end -->
|
|
343
|
+
|
|
344
|
+
A helper that is given the wrong kind of value reports it rather than printing something wrong: `words` refuses a decimal, the four comparisons refuse anything that is not a finite number, and `dateformat` refuses a date it cannot resolve in the calendar it was given.
|
|
345
|
+
|
|
346
|
+
See [date rules](dates-and-calendars.md) for what `dateformat` accepts and the patterns its third argument names.
|