@heroiclands/package-build 23.12.7 → 23.13.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 (76) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +1 -1
  3. package/assets/preview/gitea.ini +2 -2
  4. package/assets/styles/content-base.css +143 -23
  5. package/bin/content-commands.mjs +15 -4
  6. package/bin/package-build-preview.mjs +2 -2
  7. package/docs/api.md +18 -18
  8. package/docs/authoring/assets.md +1 -1
  9. package/docs/authoring/links-and-markup.md +156 -22
  10. package/docs/commands.md +32 -10
  11. package/docs/configuration.md +8 -1
  12. package/docs/content-format.md +3 -3
  13. package/docs/diagnostics.md +42 -0
  14. package/docs/reference/format-details.md +105 -22
  15. package/docs/reference/note-types.md +8 -1
  16. package/docs/reference/theme-pages.md +51 -0
  17. package/docs/source-preview.md +1 -1
  18. package/e2e.mjs +1 -1
  19. package/engine/anchors.mjs +2 -2
  20. package/engine/asset-shipping.mjs +22 -8
  21. package/engine/code-fences.mjs +3 -3
  22. package/engine/content-blocks.mjs +3 -3
  23. package/engine/content-figures.mjs +5 -5
  24. package/engine/content-index.mjs +1 -1
  25. package/engine/content-scripts.mjs +55 -21
  26. package/engine/content-spans.mjs +2 -2
  27. package/engine/content-verse.mjs +750 -0
  28. package/engine/dictionary-rows.mjs +15 -4
  29. package/engine/field-reference.mjs +4 -4
  30. package/engine/first-use.mjs +17 -27
  31. package/engine/key-case-collisions.mjs +136 -0
  32. package/engine/language-data.mjs +203 -6
  33. package/engine/language-terms.mjs +74 -5
  34. package/engine/map-lexicon.mjs +42 -23
  35. package/engine/names.mjs +36 -9
  36. package/engine/note-matter.mjs +66 -0
  37. package/engine/pdf-build.mjs +1 -0
  38. package/engine/pdf-render.mjs +107 -18
  39. package/engine/read-stdin.mjs +29 -0
  40. package/engine/run-rows.mjs +2 -2
  41. package/engine/run-words.mjs +4 -111
  42. package/engine/site-build.mjs +4 -4
  43. package/engine/site-preview.mjs +14 -14
  44. package/engine/sql-tables.mjs +20 -3
  45. package/engine/web-wikilinks.mjs +1 -1
  46. package/engine/word-forms.mjs +349 -0
  47. package/engine/word-terms.mjs +9 -0
  48. package/hugo-theme/static/css/style.css +92 -14
  49. package/package.json +3 -3
  50. package/types/e2e.d.mts +2 -2
  51. package/types/engine/address-fields.d.mts +1 -1
  52. package/types/engine/address-values.d.mts +1 -1
  53. package/types/engine/asset-shipping.d.mts +5 -0
  54. package/types/engine/code-fences.d.mts +3 -3
  55. package/types/engine/content-format-check.d.mts +1 -1
  56. package/types/engine/content-scripts.d.mts +8 -4
  57. package/types/engine/content-verse.d.mts +240 -0
  58. package/types/engine/dictionary-rows.d.mts +10 -0
  59. package/types/engine/field-reference.d.mts +1 -1
  60. package/types/engine/key-case-collisions.d.mts +28 -0
  61. package/types/engine/language-data.d.mts +0 -2
  62. package/types/engine/map-lexicon.d.mts +19 -6
  63. package/types/engine/names.d.mts +34 -3
  64. package/types/engine/note-matter.d.mts +48 -0
  65. package/types/engine/note-vocabulary.d.mts +1 -1
  66. package/types/engine/pdf-coverage.d.mts +1 -1
  67. package/types/engine/read-stdin.d.mts +11 -0
  68. package/types/engine/run-rows.d.mts +2 -2
  69. package/types/engine/run-words.d.mts +0 -15
  70. package/types/engine/sql-tables.d.mts +4 -1
  71. package/types/engine/text-maps.d.mts +1 -1
  72. package/types/engine/web-wikilinks.d.mts +1 -1
  73. package/types/engine/word-forms.d.mts +82 -0
  74. package/types/release.d.mts +1 -1
  75. package/engine/content-poetry.mjs +0 -240
  76. package/types/engine/content-poetry.d.mts +0 -58
@@ -13,7 +13,7 @@
13
13
 
14
14
  /**
15
15
  * **Text marked with a language, a font and a mapping.** Three keys on the
16
- * attribute grammar spans, divs and poetry fences already share, each optional
16
+ * attribute grammar spans, divs and verse fences already share, each optional
17
17
  * and independent of the others:
18
18
  *
19
19
  * ```markdown
@@ -24,7 +24,7 @@
24
24
  * Zaron tholmeza.
25
25
  * :::
26
26
  *
27
- * ```poetry {form=epic lang=vyl font=semaron-kuriale map=vyl-semaron}
27
+ * ```verse {form=epic lang=vyl font=semaron-kuriale map=vyl-semaron}
28
28
  * ```
29
29
  *
30
30
  * - **`lang=`** gives the language tag and changes nothing else. It is a name
@@ -45,7 +45,7 @@
45
45
  * form. Any other
46
46
  * word is a finding at its first character, once per run; see
47
47
  * {@link module:engine/run-words}.
48
- * - **A run inside a div or a poetry fence inherits its keys**, and its own
48
+ * - **A run inside a div or a verse fence inherits its keys**, and its own
49
49
  * keys override them.
50
50
  * - **Text with a font, a map or a declared language is plain.** A span or a
51
51
  * verse holds no emphasis, link, image, footnote, wikilink, code or HTML,
@@ -59,7 +59,7 @@
59
59
  *
60
60
  * {@link scanRuns} reads the markup through {@link module:engine/content-spans.scanSpans},
61
61
  * {@link module:engine/content-blocks.scanBlocks} and
62
- * {@link module:engine/content-poetry.scanPoetry}, so what counts as a span, a
62
+ * {@link module:engine/content-verse.scanVerse}, so what counts as a span, a
63
63
  * div or a fence is theirs to say.
64
64
  *
65
65
  * @module
@@ -68,7 +68,7 @@
68
68
  import MarkdownIt from "markdown-it";
69
69
 
70
70
  import { scanBlocks, BLOCK_CONTAINERS } from "./content-blocks.mjs";
71
- import { scanPoetry } from "./content-poetry.mjs";
71
+ import { expandVerse, expandVerseOrigins, scanVerse, sourcePosition } from "./content-verse.mjs";
72
72
  import { scanSpans } from "./content-spans.mjs";
73
73
  import { isLanguageTag } from "./language-tag.mjs";
74
74
  import { distance, nearest } from "./near-miss.mjs";
@@ -116,12 +116,12 @@ const CONFIG = "package-build.config.yaml";
116
116
  */
117
117
 
118
118
  /**
119
- * One span, div or poetry fence carrying `lang=`, `font=` or `map=`. Its
119
+ * One span, div or verse fence carrying `lang=`, `font=` or `map=`. Its
120
120
  * `lang`, `font` and `map` are the ones it is set with: its own, else the
121
121
  * innermost div or fence around it that states one.
122
122
  *
123
123
  * @typedef {object} Run
124
- * @property {"span"|"block"|"poetry"} kind - Which construct.
124
+ * @property {"span"|"block"|"verse"} kind - Which construct.
125
125
  * @property {string|undefined} lang - The language as written.
126
126
  * @property {string|undefined} tag - The BCP 47 tag it resolves to; `undefined`
127
127
  * when it resolves to none.
@@ -205,7 +205,7 @@ function withinOneEdit(a, b) {
205
205
  }
206
206
 
207
207
  /** The word each construct is called in a finding. */
208
- const CONSTRUCT = Object.freeze({ span: "span", block: "div", poetry: "poetry fence" });
208
+ const CONSTRUCT = Object.freeze({ span: "span", block: "div", verse: "verse fence" });
209
209
 
210
210
  /** Line starts of a source, for offsets to positions. */
211
211
  function lineStarts(text) {
@@ -645,7 +645,7 @@ function analyseRuns(source, ctx, syntax) {
645
645
  for (const key of Object.keys(keys))
646
646
  report(
647
647
  at(key, "key"),
648
- `\`${key}=\` is read on a span, a div or a poetry fence, not on a ${block.name} block`,
648
+ `\`${key}=\` is read on a span, a div or a verse fence, not on a ${block.name} block`,
649
649
  );
650
650
  } else {
651
651
  divs.set(first, block);
@@ -679,13 +679,13 @@ function analyseRuns(source, ctx, syntax) {
679
679
  collect(0, text);
680
680
 
681
681
  const poems = new Map();
682
- for (const poem of scanPoetry(text).blocks) {
682
+ for (const poem of scanVerse(text).blocks) {
683
683
  poems.set(poem.start, poem);
684
684
  const keys = runKeys(poem.attributes);
685
685
  if (!Object.keys(keys).length) continue;
686
686
  const opening = lines[poem.start];
687
687
  constructs.push({
688
- kind: "poetry",
688
+ kind: "verse",
689
689
  keys,
690
690
  node: poem,
691
691
  line0: poem.start,
@@ -914,7 +914,11 @@ function analyseRuns(source, ctx, syntax) {
914
914
  const runs = [];
915
915
  const details = [];
916
916
  for (const construct of constructs) {
917
+ const unchecked = errors.length;
917
918
  checkOwn(construct);
919
+ // A key that names nothing is the finding; the text it would have set
920
+ // is not then checked against a face or a language it cannot reach.
921
+ const named = errors.length === unchecked;
918
922
  const keys = Object.assign({}, ...around(construct).map((c) => c.keys), construct.keys);
919
923
  const { lang, font, map } = keys;
920
924
  // A face the run's map is not for, once the face and the map are each
@@ -973,7 +977,7 @@ function analyseRuns(source, ctx, syntax) {
973
977
  const span = construct.node;
974
978
  const inline = text.slice(span.start + 1, span.contentEnd);
975
979
  plainInline(inline, span.start + 1, marking);
976
- if (errors.length === before) {
980
+ if (named && errors.length === before) {
977
981
  lexical([[span.start + 1, span.contentEnd]], detail);
978
982
  capitals([[span.start + 1, span.contentEnd]], detail);
979
983
  covered([[span.start + 1, span.contentEnd]], detail);
@@ -1017,8 +1021,8 @@ function analyseRuns(source, ctx, syntax) {
1017
1021
  if (token.type === "fence")
1018
1022
  said(
1019
1023
  top,
1020
- token.info.trim().split(/\s/)[0] === "poetry" ?
1021
- "a poetry fence"
1024
+ token.info.trim().split(/\s/)[0] === "verse" ?
1025
+ "a verse fence"
1022
1026
  : "a code fence",
1023
1027
  );
1024
1028
  else if (Object.hasOwn(BLOCK_WHAT, token.type)) said(top, BLOCK_WHAT[token.type]);
@@ -1027,7 +1031,7 @@ function analyseRuns(source, ctx, syntax) {
1027
1031
  for (let line = first; line < last; line++)
1028
1032
  plainInline(lines[line], starts[line], marking, detail.holes);
1029
1033
  }
1030
- if (errors.length === before) {
1034
+ if (named && errors.length === before) {
1031
1035
  lexical(outsideHoles(from, to, detail.holes), detail);
1032
1036
  capitals(outsideHoles(from, to, detail.holes), detail);
1033
1037
  covered(outsideHoles(from, to, detail.holes), detail);
@@ -1043,7 +1047,7 @@ function analyseRuns(source, ctx, syntax) {
1043
1047
  }
1044
1048
 
1045
1049
  /**
1046
- * Find the spans, divs and poetry fences marked with `lang=`, `font=` or
1050
+ * Find the spans, divs and verse fences marked with `lang=`, `font=` or
1047
1051
  * `map=`, and every fault in their markup and their text.
1048
1052
  *
1049
1053
  * @param {string} source - Authored Markdown: a note body, or a whole file
@@ -1059,8 +1063,32 @@ function analyseRuns(source, ctx, syntax) {
1059
1063
  * declared name.
1060
1064
  */
1061
1065
  export function scanRuns(source, ctx = {}, syntax) {
1062
- const { runs, errors, warnings } = analyseRuns(source, ctx, syntax);
1063
- return { runs, errors, warnings };
1066
+ const text = String(source ?? "");
1067
+ const { markdown, origins } = expandVerseOrigins(text);
1068
+ const authored = analyseRuns(text, ctx, syntax);
1069
+ if (markdown === text) return authored;
1070
+ // A verse in columns is checked as the spans each entry sets, which are
1071
+ // written only in the expansion; every finding is placed back where the
1072
+ // note wrote the text or the entry it comes from, once.
1073
+ const expanded = markdown.split("\n");
1074
+ const checked = analyseRuns(markdown, ctx);
1075
+ const placed = (findings) => {
1076
+ const seen = new Set();
1077
+ return findings
1078
+ .map((finding) => ({ ...finding, ...sourcePosition(finding, expanded, origins) }))
1079
+ .filter((finding) => {
1080
+ const key = `${finding.line}:${finding.column}:${finding.message}`;
1081
+ if (seen.has(key)) return false;
1082
+ seen.add(key);
1083
+ return true;
1084
+ })
1085
+ .sort((a, b) => a.line - b.line || a.column - b.column);
1086
+ };
1087
+ return {
1088
+ runs: authored.runs,
1089
+ errors: placed(checked.errors),
1090
+ warnings: placed(checked.warnings),
1091
+ };
1064
1092
  }
1065
1093
 
1066
1094
  /**
@@ -1113,7 +1141,7 @@ function rewrittenAttributes(detail, search) {
1113
1141
  const changes =
1114
1142
  run.font !== undefined ||
1115
1143
  run.map !== undefined ||
1116
- (run.tag !== undefined && (keys.lang !== run.tag || run.kind === "poetry"));
1144
+ (run.tag !== undefined && (keys.lang !== run.tag || run.kind === "verse"));
1117
1145
  if (!changes) return undefined;
1118
1146
  const classes = [...own.classes];
1119
1147
  const values = [...own.values];
@@ -1130,8 +1158,12 @@ function rewrittenAttributes(detail, search) {
1130
1158
 
1131
1159
  /**
1132
1160
  * Rewrite text marked with `lang=`, `font=` and `map=` into the plain span,
1133
- * div and poetry grammar the rest of each chain renders, one line for one line.
1161
+ * div and verse grammar the rest of each chain renders, one line for one line.
1134
1162
  *
1163
+ * - **A verse in columns is expanded first** into one span per column entry
1164
+ * per verse line, by {@link module:engine/content-verse.expandVerse}, so
1165
+ * each entry is mapped and set as a span with its keys is. That is the one
1166
+ * rewrite that changes the number of lines.
1135
1167
  * - **The map, if any, is applied first.** Characters Markdown would read are
1136
1168
  * then backslash-escaped, so they reach every later pass as characters.
1137
1169
  * - **The construct carries** the language's tag as `lang`, the face's class
@@ -1156,7 +1188,9 @@ function rewrittenAttributes(detail, search) {
1156
1188
  */
1157
1189
  export function renderRuns(source, ctx = {}, syntax, options = {}) {
1158
1190
  const search = options.target === "site";
1159
- const text = String(source ?? "");
1191
+ const authored = String(source ?? "");
1192
+ const text = expandVerse(authored);
1193
+ if (text !== authored) syntax = undefined;
1160
1194
  if (!/\b(?:lang|font|map)=/.test(text)) return { markdown: text, errors: [] };
1161
1195
  const { details, errors } = analyseRuns(text, ctx, syntax);
1162
1196
  if (!details.length) return { markdown: text, errors };
@@ -14,7 +14,7 @@
14
14
  import MarkdownIt from "markdown-it";
15
15
  import { alertMarkdownPlugin } from "./content-alerts.mjs";
16
16
  import { slugify } from "./content-slug.mjs";
17
- import { scanPoetry } from "./content-poetry.mjs";
17
+ import { scanVerse } from "./content-verse.mjs";
18
18
  import footnotePlugin from "markdown-it-footnote";
19
19
  import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
20
20
  import { inlineText } from "./name-markup.mjs";
@@ -263,7 +263,7 @@ export function scanSpans(source) {
263
263
  }
264
264
  // Verse fences preserve inline Markdown even though the block parser
265
265
  // normally treats every code fence as literal text.
266
- for (const poem of scanPoetry(text).blocks) {
266
+ for (const poem of scanVerse(text).blocks) {
267
267
  for (let line = poem.start + 1; line < poem.end; line++) {
268
268
  const children = [];
269
269
  parser.inline.parse(lines[line], parser, env, children);