@heroiclands/package-build 22.26.0 → 22.27.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +3 -1
  3. package/assets/styles/content-base.css +46 -3
  4. package/docs/api.md +33 -13
  5. package/docs/authoring/frontmatter.md +2 -2
  6. package/docs/authoring/links-and-markup.md +96 -149
  7. package/docs/content-format.md +5 -3
  8. package/docs/guides/government-migration.md +52 -0
  9. package/docs/guides/site.md +5 -5
  10. package/docs/reference/format-details.md +110 -219
  11. package/docs/reference/note-types.md +27 -29
  12. package/docs/reference/theme-pages.md +31 -29
  13. package/engine/anchors.mjs +85 -13
  14. package/engine/art-fields.mjs +1 -1
  15. package/engine/base-compiler.mjs +9 -1
  16. package/engine/bundles.mjs +1 -1
  17. package/engine/code-fences.mjs +6 -2
  18. package/engine/content-alerts.mjs +195 -0
  19. package/engine/content-blocks.mjs +119 -241
  20. package/engine/content-embeds.mjs +1 -1
  21. package/engine/content-figures.mjs +166 -230
  22. package/engine/content-format-check.mjs +3 -2
  23. package/engine/content-format.yaml +0 -5
  24. package/engine/content-images.mjs +3 -3
  25. package/engine/content-poetry.mjs +141 -0
  26. package/engine/content-spans.mjs +177 -0
  27. package/engine/foundry-entries.mjs +2 -0
  28. package/engine/frontmatter-lint.mjs +2 -1
  29. package/engine/heading-attributes.mjs +1 -1
  30. package/engine/helpers.mjs +8 -3
  31. package/engine/holdings.mjs +165 -273
  32. package/engine/index.mjs +1 -1
  33. package/engine/infobox.mjs +1 -1
  34. package/engine/journals.mjs +8 -6
  35. package/engine/markdown-expressions.mjs +9 -3
  36. package/engine/metadata-index.mjs +2 -3
  37. package/engine/note-vocabulary.mjs +16 -11
  38. package/engine/pdf-build.mjs +1 -1
  39. package/engine/pdf-render.mjs +90 -31
  40. package/engine/populations.mjs +17 -83
  41. package/engine/site-build.mjs +44 -16
  42. package/engine/site-preview.mjs +22 -6
  43. package/engine/wikilinks.mjs +18 -4
  44. package/hugo-theme/layouts/partials/holdings/entry.html +2 -2
  45. package/hugo-theme/layouts/partials/holdings.html +14 -14
  46. package/hugo-theme/static/css/style.css +40 -2
  47. package/package.json +1 -1
  48. package/sohl/item-fields.mjs +0 -16
  49. package/types/engine/anchors.d.mts +13 -4
  50. package/types/engine/art-fields.d.mts +1 -1
  51. package/types/engine/code-fences.d.mts +3 -1
  52. package/types/engine/content-alerts.d.mts +62 -0
  53. package/types/engine/content-blocks.d.mts +3 -10
  54. package/types/engine/content-embeds.d.mts +1 -1
  55. package/types/engine/content-figures.d.mts +13 -20
  56. package/types/engine/content-images.d.mts +1 -1
  57. package/types/engine/content-poetry.d.mts +32 -0
  58. package/types/engine/content-spans.d.mts +18 -0
  59. package/types/engine/holdings.d.mts +59 -105
  60. package/types/engine/markdown-expressions.d.mts +1 -1
  61. package/types/engine/note-vocabulary.d.mts +10 -2
  62. package/types/engine/populations.d.mts +4 -13
  63. package/types/engine/site-build.d.mts +3 -3
  64. package/types/engine/wikilinks.d.mts +5 -1
@@ -100,7 +100,8 @@ export function anchorPageId(noteId, anchorSlug) {
100
100
  *
101
101
  * @param {Array<{type: string, id: string, shortcode?: string|null,
102
102
  * name?: string, pack?: string, docPack?: string, none?: boolean,
103
- * draft?: boolean}>} docs -
103
+ * draft?: boolean, anchors?: Set<string>, anchorUuids?: Record<string,string>,
104
+ * docAnchorUuids?: Record<string,string>}>} docs -
104
105
  * One entry per content note. `pack` / `docPack` name the packs the note's
105
106
  * document and its documentation entry landed in; omitted, the conventional
106
107
  * one-pack-per-type names stand in. `none` says the note declares
@@ -153,9 +154,11 @@ export function buildWikilinkIndex(
153
154
  // that stored value. Nothing downstream assembles a UUID from parts, so a
154
155
  // link and its target cannot disagree about where the document lives.
155
156
  const uuidByDoc = new Map();
157
+ const byId = new Map();
156
158
 
157
159
  for (const d of docs) {
158
160
  if (!d.id || !d.type) continue;
161
+ byId.set(`${d.type}/${d.id}`, d);
159
162
  types.add(norm(d.type));
160
163
 
161
164
  uuidByDoc.set(
@@ -172,6 +175,8 @@ export function buildWikilinkIndex(
172
175
  // type and a UUID carries the pack name, so the address
173
176
  // cannot be derived from the type alone.
174
177
  uuid: compendiumUuid(packageId, d.type, d.id, d.pack),
178
+ anchors: d.anchorUuids,
179
+ docAnchors: d.docAnchorUuids,
175
180
  // The readable note has a separate JournalEntry whose id
176
181
  // is derived from the item's id.
177
182
  docUuid: compendiumUuid(packageId, "doc", itemDocEntryId(d.id), d.docPack),
@@ -216,6 +221,7 @@ export function buildWikilinkIndex(
216
221
  foreignReferences,
217
222
  types,
218
223
  uuidByDoc,
224
+ byId,
219
225
  packageId,
220
226
  /** The content package this build publishes, which an art address defaults to. */
221
227
  contentPackage: contentPackage ?? packageId,
@@ -433,7 +439,7 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index, cap
433
439
  // Kept for the foreign fallback below, which needs the parsed address.
434
440
  let qualifiedRead = null;
435
441
  if (target === "" && slug) {
436
- doc = { type, id, pack, docPack };
442
+ doc = index.byId?.get(`${type}/${id}`) ?? { type, id, pack, docPack };
437
443
  } else {
438
444
  const qualified = parsed.problem ?? parsed.target;
439
445
  qualifiedRead = qualified;
@@ -546,7 +552,13 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index, cap
546
552
  // reader. A foreign anchor has always been checked this way —
547
553
  // the manifest carries the map — and a local one now is too, from the
548
554
  // anchor set the index carries.
549
- if (slug && isJournal && doc.anchors && !doc.anchors.has(slug)) {
555
+ const anchorUuids = itemDoc ? addresses.docAnchors : addresses.anchors;
556
+ if (
557
+ slug &&
558
+ isJournal &&
559
+ ((doc.anchors && !doc.anchors.has(slug)) ||
560
+ (anchorUuids && !Object.hasOwn(anchorUuids, slug)))
561
+ ) {
550
562
  unresolved.push({
551
563
  link: all,
552
564
  target,
@@ -558,7 +570,9 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index, cap
558
570
  return unresolvedLink(text || doc.name || target, target);
559
571
  }
560
572
  const uuid =
561
- slug && isJournal ? pageUuid(entryUuid, anchorPageId(entryId, slug)) : entryUuid;
573
+ slug && isJournal ?
574
+ (anchorUuids?.[slug] ?? pageUuid(entryUuid, anchorPageId(entryId, slug)))
575
+ : entryUuid;
562
576
  const link = `@UUID[${uuid}]{${text}}`;
563
577
  // A link into a note that exists but is not written renders marked.
564
578
  // Presentation only — the UUID above is unchanged, and a
@@ -1,7 +1,7 @@
1
1
  {{- /*
2
2
  One entry of a holdings list — `{title, url, type, subType}`, as
3
- `@heroiclands/package-build` derives from `contains`, `held_by` and
4
- `holdings` (see the "holdings card" section of the README).
3
+ `@heroiclands/package-build` derives from `contains`, `governed_by` and
4
+ `governed_places` (see the "holdings card" section of the README).
5
5
 
6
6
  `url` is absent for a stub: a note with an empty body, which carries its
7
7
  facts into every list that names it but publishes no page of its own. The
@@ -1,22 +1,22 @@
1
1
  {{- /*
2
- Holdings card.
2
+ Geography and government card.
3
3
 
4
4
  Three small tables, rendered after the body and before the Related card
5
5
  (see `page-body.html`), from front matter `@heroiclands/package-build`
6
6
  derives and a note never authors itself:
7
7
 
8
8
  contains: [{ title, url, type, subType }] places sitting within this one
9
- held_by: [{ title, url, type, subType }] affiliations holding this place
10
- holdings: [{ title, url, type, subType }] places this affiliation holds
9
+ governed_by: [{ title, url, type, subType }] affiliations governing this place
10
+ governed_places: [{ title, url, type, subType }] places this affiliation governs
11
11
 
12
- A place page carries `contains` and/or `held_by`; an affiliation page
13
- carries `holdings`. Neither is checked here — presence of the key is the
12
+ A place page carries `contains` and/or `governed_by`; an affiliation page
13
+ carries `governed_places`. Neither is checked here — presence of the key is the
14
14
  only test, the same silent-disappear convention the Related card follows:
15
15
  a page carrying none of the three renders no card at all.
16
16
 
17
- `contains` and `holdings` list places, so each is broken into groups by
17
+ `contains` and `governed_places` list places, so each is broken into groups by
18
18
  kind (`holdings/kind-groups.html`) — the towns together, the hamlets
19
- together. `held_by` lists affiliations, whose kinds have no fixed
19
+ together. `governed_by` lists affiliations, whose kinds have no fixed
20
20
  hierarchy to group by, so it stays one flat grid with the kind printed
21
21
  beside each name instead of as a group heading.
22
22
 
@@ -27,10 +27,10 @@
27
27
  was established.
28
28
  */ -}}
29
29
  {{- $within := .Params.contains -}}
30
- {{- $heldBy := .Params.held_by -}}
31
- {{- $holds := .Params.holdings -}}
30
+ {{- $government := .Params.governed_by -}}
31
+ {{- $governed := .Params.governed_places -}}
32
32
 
33
- {{- if or $within $heldBy $holds -}}
33
+ {{- if or $within $government $governed -}}
34
34
  <section class="holdings" data-pagefind-ignore>
35
35
  {{- with $within }}
36
36
  <div class="holdings-table">
@@ -38,9 +38,9 @@
38
38
  {{ partial "holdings/kind-groups.html" . }}
39
39
  </div>
40
40
  {{- end }}
41
- {{- with $heldBy }}
41
+ {{- with $government }}
42
42
  <div class="holdings-table">
43
- <h3 class="holdings-table-label">Held by</h3>
43
+ <h3 class="holdings-table-label">Governed by</h3>
44
44
  <ul class="holdings-table-grid">
45
45
  {{- range . }}
46
46
  <li>
@@ -51,9 +51,9 @@
51
51
  </ul>
52
52
  </div>
53
53
  {{- end }}
54
- {{- with $holds }}
54
+ {{- with $governed }}
55
55
  <div class="holdings-table">
56
- <h3 class="holdings-table-label">Holdings</h3>
56
+ <h3 class="holdings-table-label">Governed places</h3>
57
57
  {{ partial "holdings/kind-groups.html" . }}
58
58
  </div>
59
59
  {{- end }}
@@ -2868,9 +2868,9 @@ summary.info-sidebar-title::after {
2868
2868
  group's entries laid out as a CSS grid that reflows its own column count
2869
2869
  from the available width — two or three columns on a desktop, one on a
2870
2870
  phone — with no breakpoint needed. Related groups by the connected page's
2871
- `type`; Holdings groups `contains` and `holdings` by place `subType`
2871
+ `type`; Holdings groups `contains` and `governed_places` by place `subType`
2872
2872
  (`.holdings-kind-group`, one level deeper, for the region/settlement/site
2873
- hierarchy) and leaves `held_by` a flat grid with the kind printed beside
2873
+ hierarchy) and leaves `governed_by` a flat grid with the kind printed beside
2874
2874
  each name. */
2875
2875
 
2876
2876
  .related,
@@ -2980,3 +2980,41 @@ summary.info-sidebar-title::after {
2980
2980
  padding: 1.25rem 1.25rem;
2981
2981
  }
2982
2982
  }
2983
+
2984
+ /* GitHub-style author alerts remain open and inherit the prose foreground. */
2985
+ .single-content .alert {
2986
+ border-inline-start: 0.25rem solid var(--pb-alert-color);
2987
+ padding: 0.5rem 1rem;
2988
+ margin: 1rem 0;
2989
+ color: inherit;
2990
+ background: transparent;
2991
+ }
2992
+ .single-content .alert-title {
2993
+ display: flex;
2994
+ align-items: center;
2995
+ gap: 0.5em;
2996
+ margin: 0 0 0.5rem;
2997
+ font-weight: 600;
2998
+ color: var(--pb-alert-color);
2999
+ }
3000
+ .single-content .alert-icon {
3001
+ width: 1em;
3002
+ height: 1em;
3003
+ flex-shrink: 0;
3004
+ fill: currentColor;
3005
+ }
3006
+ .single-content .alert > :last-child {
3007
+ margin-bottom: 0;
3008
+ }
3009
+ .single-content .alert-note {
3010
+ --pb-alert-color: #4493f8;
3011
+ }
3012
+ .single-content .alert-tip {
3013
+ --pb-alert-color: #3fb950;
3014
+ }
3015
+ .single-content .alert-important {
3016
+ --pb-alert-color: #ab7df8;
3017
+ }
3018
+ .single-content .alert-warning {
3019
+ --pb-alert-color: #d29922;
3020
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.26.0",
3
+ "version": "22.27.0",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -449,22 +449,6 @@ export const ITEM_FIELDS = Object.freeze({
449
449
  default: null,
450
450
  describe: "Where the affiliation's authority sits, by place address.",
451
451
  },
452
- {
453
- // The geographic relation, kept apart from the organisational one
454
- // above: `parents` is *subordinate to*, this is *holds sway over*.
455
- //
456
- // Authored plural and emitted singular, as the content format's
457
- // mapping row states (`data.domains` → `system.domain`). The two
458
- // spellings are deliberate rather than a slip, so the declaration
459
- // carries both rather than either side guessing.
460
- name: "data.domains",
461
- legacyKey: "domains",
462
- to: "domain",
463
- ...ADDRESS_LIST,
464
- address: PLACE_ITEMS,
465
- default: [],
466
- describe: "Places this affiliation holds sway over, by address.",
467
- },
468
452
  ]),
469
453
 
470
454
  affliction: Object.freeze([
@@ -1,9 +1,8 @@
1
1
  /**
2
- * The `{#slug}` and `:::figure {#slug}` anchors a note declares.
2
+ * The identifiers a note declares on headings, captions, divs, poems and spans.
3
3
  *
4
- * A bare `#` heading starts a journal page without declaring a slug, and so
5
- * does a figure fence that declares no id. A fence that declares one declares a
6
- * slug and starts an addressable journal page.
4
+ * Headings and captions may start journal pages. Div, poem and inline-span
5
+ * anchors address locations within their containing pages.
7
6
  *
8
7
  * @param {string} body - The note's markdown body, frontmatter already removed.
9
8
  * @param {number} [bodyLine] - The 1-based file line the body starts on, from
@@ -22,3 +21,13 @@ export function collectAnchors(body: string, bodyLine?: number, resolveRole?: (a
22
21
  level: number;
23
22
  line: number;
24
23
  }>;
24
+ /** Collisions involving a div, verse or inline-span anchor.
25
+ * Heading and caption collisions are diagnosed by their existing scanners.
26
+ * @param {string} body - Authored Markdown.
27
+ * @returns {Array<{line: number, column: number, message: string}>} Located collisions.
28
+ */
29
+ export function markupAnchorFindings(body: string): Array<{
30
+ line: number;
31
+ column: number;
32
+ message: string;
33
+ }>;
@@ -144,7 +144,7 @@ export function resolveArtRecord(index: object, value: unknown, defaultType: str
144
144
  * carries once an embed's address has been rewritten into the ordinary image
145
145
  * every surface renders ({@link module:engine/content-embeds.resolveEmbeds}).
146
146
  *
147
- * This is the lookup a `:::figure` fence's `map` counter reaches through —
147
+ * This is the lookup a captioned item's `map` counter reaches through —
148
148
  * see {@link module:engine/content-figures.scanFigures}'s `resolveRole` —
149
149
  * for a caller that reads a body after that rewrite: a Foundry journal, an
150
150
  * item or actor's documentation, and the book.
@@ -17,11 +17,13 @@ export function parseHeaderArgs(info: string): {
17
17
  * @param {object} [options]
18
18
  * @param {boolean} [options.spans=true] - Include inline code spans. Set false
19
19
  * to consider only block-level code.
20
+ * @param {boolean} [options.poetryAsProse=true] - Treat verse bodies as inline prose.
20
21
  * @returns {Array<{start: number, end: number}>} Non-overlapping regions, in
21
22
  * source order. `end` is exclusive.
22
23
  */
23
- export function codeRegions(markdown: string | null | undefined, { spans }?: {
24
+ export function codeRegions(markdown: string | null | undefined, { spans, poetryAsProse }?: {
24
25
  spans?: boolean | undefined;
26
+ poetryAsProse?: boolean | undefined;
25
27
  }): Array<{
26
28
  start: number;
27
29
  end: number;
@@ -0,0 +1,62 @@
1
+ /** Read alerts outside code examples, retaining inclusive source line ranges.
2
+ * @param {string} source - Authored Markdown.
3
+ */
4
+ export function scanAlerts(source: string): {
5
+ blocks: {
6
+ start: number;
7
+ end: number;
8
+ depth: number;
9
+ type: string;
10
+ title: any;
11
+ id: string;
12
+ classes: never[];
13
+ attributes: {
14
+ [k: string]: any;
15
+ };
16
+ body: string;
17
+ }[];
18
+ errors: {
19
+ line: number;
20
+ column: number;
21
+ message: string;
22
+ }[];
23
+ };
24
+ /** Render alert wrappers while preserving ordinary Markdown and note footnotes.
25
+ * @param {string} source - Authored Markdown.
26
+ * @param {"web"|"foundry"} [target="web"] - Publishing surface.
27
+ */
28
+ export function renderAlerts(source: string, target?: "web" | "foundry"): {
29
+ markdown: string;
30
+ errors: {
31
+ line: number;
32
+ column: number;
33
+ message: string;
34
+ }[];
35
+ };
36
+ /** Mark alert blockquotes and remove their marker before inline parsing.
37
+ * @param {import("markdown-it")} md - Markdown parser to extend.
38
+ */
39
+ export function alertMarkdownPlugin(md: typeof import("markdown-it")): void;
40
+ /** Alert titles and static icons shared by all publishing surfaces. */
41
+ export const ALERT_TYPES: Readonly<{
42
+ note: {
43
+ title: string;
44
+ icon: string;
45
+ svg: string;
46
+ };
47
+ tip: {
48
+ title: string;
49
+ icon: string;
50
+ svg: string;
51
+ };
52
+ important: {
53
+ title: string;
54
+ icon: string;
55
+ svg: string;
56
+ };
57
+ warning: {
58
+ title: string;
59
+ icon: string;
60
+ svg: string;
61
+ };
62
+ }>;
@@ -67,20 +67,13 @@ export function renderBlocks(source: string, target: "foundry" | "web"): {
67
67
  export function renderWithheldSections(source: string): string;
68
68
  /** The blocks an author may open, with the heading each takes by default. */
69
69
  export const BLOCK_NAMES: Readonly<{
70
- info: "Info";
71
70
  secret: "Secret";
72
- warn: "Warn";
73
71
  }>;
74
72
  /**
75
- * Blocks that may hold another named block.
73
+ * Container fences whose bodies are scanned and rendered recursively.
76
74
  *
77
- * A box inside a GM-only section is a real thing to write — the section is a
78
- * container for whatever the GM reads, boxes included — so a `secret` holds one
79
- * and the box stays inside it. A box inside a box is noise, so `info` and `warn`
80
- * hold no named block and a second opener inside one is a finding.
81
- *
82
- * Exported because a renderer has to know it: the body of a container is read
83
- * for blocks of its own, and the body of a box is not.
75
+ * A secret may hold general divs; a general div may hold another div or a
76
+ * secret. A secret nested directly within another secret is refused.
84
77
  *
85
78
  * @type {readonly string[]}
86
79
  */
@@ -49,7 +49,7 @@ export function resolveEmbed(index: object, embed: ParsedEmbed): {
49
49
  * The role an address's asset declares, read through the same resolution an
50
50
  * embed's address takes.
51
51
  *
52
- * This is the lookup a `:::figure` fence's `map` counter reaches through —
52
+ * This is the lookup a captioned item's `map` counter reaches through —
53
53
  * see {@link module:engine/content-figures.scanFigures}'s `resolveRole` — for
54
54
  * an address still in its authored form, before {@link resolveEmbeds} has
55
55
  * rewritten it into the ordinary image every surface renders.
@@ -1,21 +1,15 @@
1
1
  /** Number a sequence of figures with counters shared across documents. */
2
2
  export function numberFigures(figures: any, counts: any): any;
3
3
  /**
4
- * Read every `:::figure` fence in a body.
5
- *
6
- * SQL fences are expanded before this pass, so their output is a table.
7
- *
8
- * @param {string} source - A note body with expanded tables.
4
+ * Read leading captions and their following blocks outside literal fences.
5
+ * Historical figure API names remain for caption references and consumers.
6
+ * @param {string} source - The authored or expanded note body.
9
7
  * @param {object} [options] - Options.
10
- * @param {(address: string) => string|undefined} [options.resolveRole] - From
11
- * a picture's address to the role its asset declares, so a picture whose
12
- * asset declares `role: map` counts as `map` rather than `figure`. Omitted,
13
- * every picture counts as `figure` — the same reading a role other than
14
- * `map` gets.
8
+ * @param {(address: string) => string|undefined} [options.resolveRole] - Asset role lookup.
15
9
  * @returns {{figures: Array<{id: string, slug: string, caption: string,
16
- * hasCaption: boolean, classes: string[], kind: string, number: number,
17
- * label: string, line: number, bodyStart: number, bodyEnd: number,
18
- * captionStart: number, captionEnd: number, close: number}>,
10
+ * hasCaption: boolean, classes: string[], attributes: Record<string,string>,
11
+ * kind: string, numbered: boolean, number: number, label: string, line: number,
12
+ * bodyStart: number, bodyEnd: number, captionStart: number, captionEnd: number, close: number}>,
19
13
  * errors: Array<{line: number, column: number, message: string}>}}
20
14
  */
21
15
  export function scanFigures(source: string, { resolveRole }?: {
@@ -27,7 +21,9 @@ export function scanFigures(source: string, { resolveRole }?: {
27
21
  caption: string;
28
22
  hasCaption: boolean;
29
23
  classes: string[];
24
+ attributes: Record<string, string>;
30
25
  kind: string;
26
+ numbered: boolean;
31
27
  number: number;
32
28
  label: string;
33
29
  line: number;
@@ -44,7 +40,7 @@ export function scanFigures(source: string, { resolveRole }?: {
44
40
  }>;
45
41
  };
46
42
  /**
47
- * Render figure fences as HTML, leaving other Markdown untouched.
43
+ * Render leading captions and their next blocks as HTML, leaving other Markdown untouched.
48
44
  *
49
45
  * A code, table or prose figure's contents are left as Markdown inside the
50
46
  * wrapper, blank-line separated from its tags exactly as
@@ -84,6 +80,7 @@ export function renderFigureBlocks(source: string, renderMarkdown?: (markdown: s
84
80
  /** A figure's automatically determined kind, and the word that labels it. */
85
81
  export const FIGURE_NAMES: Readonly<{
86
82
  code: "Code";
83
+ example: "Example";
87
84
  table: "Table";
88
85
  figure: "Figure";
89
86
  map: "Map";
@@ -91,12 +88,8 @@ export const FIGURE_NAMES: Readonly<{
91
88
  prose: "Prose";
92
89
  }>;
93
90
  /**
94
- * The classes a figure takes.
95
- *
96
- * `.border` states that the captioned thing is drawn inside a border, and each
97
- * medium owns what that means. The vocabulary is closed: a class absent from it
98
- * is a finding naming the class.
99
- *
91
+ * Historical built-in presentation classes, retained for theme consumers.
92
+ * Captions now accept arbitrary classes through the shared attribute grammar.
100
93
  * @type {readonly string[]}
101
94
  */
102
95
  export const FIGURE_CLASSES: readonly string[];
@@ -67,7 +67,7 @@ export function escapeHtml(text: string): string;
67
67
  * function, so the two cannot drift into styling the same directive through
68
68
  * different class names.
69
69
  *
70
- * **No `<figcaption>` is emitted here.** A `:::figure` fence draws its own
70
+ * **No `<figcaption>` is emitted here.** A captioned item draws its own
71
71
  * label and authored caption around whatever this returns; the alt text stays
72
72
  * on the `img` element alone.
73
73
  *
@@ -17,3 +17,35 @@ export function inlineVerse(text: any): string;
17
17
  * definitions remain in scope. The span carries the indent for each line.
18
18
  */
19
19
  export function poetryMarkdown(body: any): string;
20
+ /** Read poetry code fences; start/end are inclusive zero-based source lines.
21
+ * @param {string} source - Authored Markdown.
22
+ */
23
+ export function scanPoetry(source: string): {
24
+ blocks: {
25
+ end: number;
26
+ body: string;
27
+ start: number;
28
+ fence: string;
29
+ name: string;
30
+ attributes: {};
31
+ id: string;
32
+ classes: never[];
33
+ indent: string;
34
+ }[];
35
+ errors: {
36
+ line: number;
37
+ column: number;
38
+ message: string;
39
+ }[];
40
+ };
41
+ /** Replace verse fences with HTML wrappers whose body remains inline Markdown.
42
+ * @param {string} source - Authored Markdown.
43
+ */
44
+ export function renderPoetry(source: string): {
45
+ markdown: string;
46
+ errors: {
47
+ line: number;
48
+ column: number;
49
+ message: string;
50
+ }[];
51
+ };
@@ -0,0 +1,18 @@
1
+ /** Add bracket spans after recognized Markdown links and footnotes.
2
+ * @param {import("markdown-it")} md - Markdown parser to extend.
3
+ */
4
+ export function spanMarkdownPlugin(md: typeof import("markdown-it")): void;
5
+ /** Read span anchors and located attribute diagnostics outside literal code.
6
+ * @param {string} source - Authored Markdown.
7
+ */
8
+ export function scanSpans(source: string): {
9
+ spans: any[];
10
+ errors: any[];
11
+ };
12
+ /** Emit span tags while leaving their inline content as Markdown for Hugo.
13
+ * @param {string} source - Authored Markdown.
14
+ */
15
+ export function renderSpans(source: string): {
16
+ markdown: string;
17
+ errors: any[];
18
+ };