@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.
- package/CHANGELOG.md +54 -0
- package/README.md +1 -1
- package/assets/preview/gitea.ini +2 -2
- package/assets/styles/content-base.css +143 -23
- package/bin/content-commands.mjs +15 -4
- package/bin/package-build-preview.mjs +2 -2
- package/docs/api.md +18 -18
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/links-and-markup.md +156 -22
- package/docs/commands.md +32 -10
- package/docs/configuration.md +8 -1
- package/docs/content-format.md +3 -3
- package/docs/diagnostics.md +42 -0
- package/docs/reference/format-details.md +105 -22
- package/docs/reference/note-types.md +8 -1
- package/docs/reference/theme-pages.md +51 -0
- package/docs/source-preview.md +1 -1
- package/e2e.mjs +1 -1
- package/engine/anchors.mjs +2 -2
- package/engine/asset-shipping.mjs +22 -8
- package/engine/code-fences.mjs +3 -3
- package/engine/content-blocks.mjs +3 -3
- package/engine/content-figures.mjs +5 -5
- package/engine/content-index.mjs +1 -1
- package/engine/content-scripts.mjs +55 -21
- package/engine/content-spans.mjs +2 -2
- package/engine/content-verse.mjs +750 -0
- package/engine/dictionary-rows.mjs +15 -4
- package/engine/field-reference.mjs +4 -4
- package/engine/first-use.mjs +17 -27
- package/engine/key-case-collisions.mjs +136 -0
- package/engine/language-data.mjs +203 -6
- package/engine/language-terms.mjs +74 -5
- package/engine/map-lexicon.mjs +42 -23
- package/engine/names.mjs +36 -9
- package/engine/note-matter.mjs +66 -0
- package/engine/pdf-build.mjs +1 -0
- package/engine/pdf-render.mjs +107 -18
- package/engine/read-stdin.mjs +29 -0
- package/engine/run-rows.mjs +2 -2
- package/engine/run-words.mjs +4 -111
- package/engine/site-build.mjs +4 -4
- package/engine/site-preview.mjs +14 -14
- package/engine/sql-tables.mjs +20 -3
- package/engine/web-wikilinks.mjs +1 -1
- package/engine/word-forms.mjs +349 -0
- package/engine/word-terms.mjs +9 -0
- package/hugo-theme/static/css/style.css +92 -14
- package/package.json +3 -3
- package/types/e2e.d.mts +2 -2
- package/types/engine/address-fields.d.mts +1 -1
- package/types/engine/address-values.d.mts +1 -1
- package/types/engine/asset-shipping.d.mts +5 -0
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/content-format-check.d.mts +1 -1
- package/types/engine/content-scripts.d.mts +8 -4
- package/types/engine/content-verse.d.mts +240 -0
- package/types/engine/dictionary-rows.d.mts +10 -0
- package/types/engine/field-reference.d.mts +1 -1
- package/types/engine/key-case-collisions.d.mts +28 -0
- package/types/engine/language-data.d.mts +0 -2
- package/types/engine/map-lexicon.d.mts +19 -6
- package/types/engine/names.d.mts +34 -3
- package/types/engine/note-matter.d.mts +48 -0
- package/types/engine/note-vocabulary.d.mts +1 -1
- package/types/engine/pdf-coverage.d.mts +1 -1
- package/types/engine/read-stdin.d.mts +11 -0
- package/types/engine/run-rows.d.mts +2 -2
- package/types/engine/run-words.d.mts +0 -15
- package/types/engine/sql-tables.d.mts +4 -1
- package/types/engine/text-maps.d.mts +1 -1
- package/types/engine/web-wikilinks.d.mts +1 -1
- package/types/engine/word-forms.d.mts +82 -0
- package/types/release.d.mts +1 -1
- package/engine/content-poetry.mjs +0 -240
- package/types/engine/content-poetry.d.mts +0 -58
|
@@ -1910,7 +1910,15 @@ authored labels supply none.
|
|
|
1910
1910
|
|
|
1911
1911
|
1. A `.name` span whose whole text matches an indexed `name.full` or
|
|
1912
1912
|
`name.aliases` should be a link. Use `[[place-arden|Arden Vale]]` or
|
|
1913
|
-
`[[place-arden|]]` to introduce that note.
|
|
1913
|
+
`[[place-arden|]]` to introduce that note. A span with a `data-type`
|
|
1914
|
+
matches only notes of that type, and only notes of that subtype when it
|
|
1915
|
+
names one; an untyped span matches a note of any type. One name can
|
|
1916
|
+
belong to two things, so
|
|
1917
|
+
`[Theinuvael]{.name data-type="affiliation/lineage"}` draws no warning
|
|
1918
|
+
when _Theinuvael_ is an alias of a place note, while
|
|
1919
|
+
`[Theinuvael]{.name data-type="place"}` and `[Theinuvael]{.name}` should
|
|
1920
|
+
be links to it. The same match decides a span's identity for the rules
|
|
1921
|
+
below and for the unwritten-name advisory.
|
|
1914
1922
|
2. Mark or link each name at most once per note. An indexed full name and its
|
|
1915
1923
|
declared aliases share one identity, including across different heading
|
|
1916
1924
|
anchors. Other labels use their whole text as their identity: different
|
|
@@ -1921,10 +1929,9 @@ authored labels supply none.
|
|
|
1921
1929
|
note under any label. Possessives and regular plurals count as uses; other
|
|
1922
1930
|
indexed forms can be declared aliases.
|
|
1923
1931
|
|
|
1924
|
-
Bold is emphasis
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
receives no such advisory. Unknown bold phrases supply no names.
|
|
1932
|
+
Bold is emphasis and is never read as a name. A bold label such as
|
|
1933
|
+
`**Strength**:` is ordinary emphasis even when its text matches an indexed
|
|
1934
|
+
full name or alias, and it supplies no name occurrence or introduction.
|
|
1928
1935
|
|
|
1929
1936
|
Inline Markdown remains active inside name spans, and name spans remain
|
|
1930
1937
|
valid inside emphasis:
|
|
@@ -1968,7 +1975,8 @@ This is derived metadata, not frontmatter. Documentation mirrors and assets
|
|
|
1968
1975
|
do not duplicate a note's occurrences. The [`names` relation](#content-tables)
|
|
1969
1976
|
flattens these entries with their containing NOTE Address.
|
|
1970
1977
|
|
|
1971
|
-
Lint groups names without an indexed full-name or alias match
|
|
1978
|
+
Lint groups names without an indexed full-name or alias match, among notes
|
|
1979
|
+
of the span's type and subtype when it supplies them, by supplied
|
|
1972
1980
|
classification and lists all their source uses. It also groups conflicting
|
|
1973
1981
|
annotations of the same visible name. Untyped uses do not conflict. A bare
|
|
1974
1982
|
type and its subtype agree; different supplied types or different supplied
|
|
@@ -1978,7 +1986,7 @@ exit status. Supplied-value errors prevent publication and compilation.
|
|
|
1978
1986
|
|
|
1979
1987
|
#### Text marked with a language, a font or a map
|
|
1980
1988
|
|
|
1981
|
-
`lang=`, `font=` and `map=` on a span, a generic div or a
|
|
1989
|
+
`lang=`, `font=` and `map=` on a span, a generic div or a verse fence are read
|
|
1982
1990
|
by the build and never written through as ordinary attributes. Each is
|
|
1983
1991
|
optional, and a run inside a div or a fence inherits the keys the div or fence
|
|
1984
1992
|
states, its own keys overriding them. On a named block the three keys are
|
|
@@ -2015,7 +2023,7 @@ with a real language's tag alone keeps its Markdown. See
|
|
|
2015
2023
|
|
|
2016
2024
|
Each own-note metadata record carries `runs` in source order, an empty array
|
|
2017
2025
|
when the body marks nothing. Every entry has exactly `kind` (`span`, `block` or
|
|
2018
|
-
`
|
|
2026
|
+
`verse`), `lang`, `tag`, `font`, `map`, `line` and `column`: `lang`, `font` and
|
|
2019
2027
|
`map` as written, its own or inherited, `tag` the BCP 47 tag `lang` resolves
|
|
2020
2028
|
to, each `null` when absent, and the position of the span's `[`, the div's
|
|
2021
2029
|
`:::` or the fence's opening. Every value is a string or a number and none is a
|
|
@@ -2184,10 +2192,13 @@ word's start:
|
|
|
2184
2192
|
lists under a `tongue` other than `translation`, or a form one rule derives from a lexicon form —
|
|
2185
2193
|
the `data.grammar.plural` plural of a noun or its own `plural`, a verb's form
|
|
2186
2194
|
with one ending of `data.grammar.verbs.persons` (in place of a closing
|
|
2187
|
-
`data.grammar.verbs.replaces` where one is stated),
|
|
2188
|
-
stating `replaces`
|
|
2195
|
+
`data.grammar.verbs.replaces` where one is stated), a suffix of `data.affixes`
|
|
2196
|
+
stating `replaces` on a word of a class its `attachesTo` names, or on any word
|
|
2197
|
+
where it names none (`vowel` takes the place of a closing `a`, `e`, `i`, `o` or
|
|
2189
2198
|
`u`, accented or not; letters take the place of those letters; a stem closing
|
|
2190
|
-
otherwise takes the suffix whole)
|
|
2199
|
+
otherwise takes the suffix whole), an agreeing form `data.grammar.agreement`
|
|
2200
|
+
gives a word of one of its `classes`, or a participle `data.grammar.participles`
|
|
2201
|
+
gives a verb (see [optional noun and verb forms](#optional-noun-and-verb-forms)). A compound not known whole is checked part by
|
|
2191
2202
|
part at its hyphens and en dashes. Any other word is an error at its first
|
|
2192
2203
|
character, once per run; see
|
|
2193
2204
|
[a mapped run's words](../authoring/links-and-markup.md#a-mapped-runs-words):
|
|
@@ -2493,6 +2504,12 @@ layout serves Foundry (`modules/thalorna/images/thorn.webp`), the website's CDN
|
|
|
2493
2504
|
(`<cdn base>/thalorna/images/thorn.webp`), and every location a world copies
|
|
2494
2505
|
the pictures to.
|
|
2495
2506
|
|
|
2507
|
+
**A row's files are kept.** Staging the pictures writes them beside whatever a
|
|
2508
|
+
`packageBuild.assets` row copied under `icons/` or `images/`, such as a
|
|
2509
|
+
subdirectory of other art, and removes none of it. A picture whose flat path is
|
|
2510
|
+
already a file a row staged is an error, and the row's file stays. Clearing an
|
|
2511
|
+
earlier build's pictures is the stage reset's work.
|
|
2512
|
+
|
|
2496
2513
|
**The package's asset set**, `asset-sets/default.json` under the package
|
|
2497
2514
|
root, lists what it ships. The name is fixed, so a world always finds the
|
|
2498
2515
|
package's own set there:
|
|
@@ -3308,7 +3325,7 @@ inherited words are read through `language_lineage`.
|
|
|
3308
3325
|
**`affixes`** is one row per entry of `data.affixes` on every language note,
|
|
3309
3326
|
and **`fields`** one per entry of `data.fields`, in the order the note states
|
|
3310
3327
|
them. Each has `language` and `position` as `words` does, then every key of the
|
|
3311
|
-
entry as its own column: `form`, `zero`, `kind`, `given`, `where`, `gloss`, `attaches`, `replaces`, `lengthens`, `front`, `examples`, `barredAfter`
|
|
3328
|
+
entry as its own column: `form`, `zero`, `kind`, `given`, `where`, `gloss`, `attaches`, `replaces`, `attachesTo`, `lengthens`, `front`, `examples`, `barredAfter`
|
|
3312
3329
|
and `tones` for an affix, then `written`, its form as a table prints it (`∅` for a zero affix), `name` and `holds` for a field. A key an entry does not state is
|
|
3313
3330
|
`NULL`, `examples` is a list, and a language note tagged `gm` contributes no
|
|
3314
3331
|
row on a public surface. An affix's `tones` is the tone pattern it carries,
|
|
@@ -3392,14 +3409,14 @@ metadata supplies `names` in that package's SQL schema. Public queries exclude
|
|
|
3392
3409
|
GM-only owners; all/native contexts retain them. Only authored occurrences
|
|
3393
3410
|
contribute rows, so generated queries and sections add no further names.
|
|
3394
3411
|
|
|
3395
|
-
**`runs`** is one row per span, generic div and
|
|
3412
|
+
**`runs`** is one row per span, generic div and verse fence an authored note
|
|
3396
3413
|
body marks with `lang=`, `font=` or `map=`. Each row has these columns,
|
|
3397
3414
|
including on an empty relation:
|
|
3398
3415
|
|
|
3399
3416
|
| Column | SQL type | Meaning |
|
|
3400
3417
|
| -------- | --------- | -------------------------------------------------------------------------------------------- |
|
|
3401
3418
|
| `owner` | `VARCHAR` | Containing authored page's canonical NOTE Address, or `NULL` when the record has no Address. |
|
|
3402
|
-
| `kind` | `VARCHAR` | `span`, `block` or `
|
|
3419
|
+
| `kind` | `VARCHAR` | `span`, `block` or `verse`. |
|
|
3403
3420
|
| `lang` | `VARCHAR` | The language as written, its own or inherited, or `NULL`. |
|
|
3404
3421
|
| `tag` | `VARCHAR` | The BCP 47 tag `lang` resolves to, or `NULL` when it resolves to none. |
|
|
3405
3422
|
| `font` | `VARCHAR` | The font as written, its own or inherited, or `NULL`. |
|
|
@@ -3854,11 +3871,11 @@ The caption attaches to exactly the next supported block:
|
|
|
3854
3871
|
| Markdown table | `table` | Table |
|
|
3855
3872
|
| Image, Markdown or wikilink | `figure` | Figure |
|
|
3856
3873
|
| SQL fence, including backtick SQL | `table` | Table |
|
|
3857
|
-
|
|
|
3874
|
+
| Verse fence | `verse` | Poem |
|
|
3858
3875
|
| Any other code fence | `code` | Code |
|
|
3859
3876
|
| Paragraph, quote block or fenced div | `prose` | Prose |
|
|
3860
3877
|
|
|
3861
|
-
The optional `type` attribute overrides inference and accepts `figure`, `table`, `
|
|
3878
|
+
The optional `type` attribute overrides inference and accepts `figure`, `table`, `verse`, `code`, `prose`, and `example`. `type=example` uses **Example 1**, **Example 2**, and so on. It does not allow an unsupported target. A heading, list, thematic break, another caption or end of document after a caption is an error. Caption markers without separating spaces or caption text are errors too.
|
|
3862
3879
|
|
|
3863
3880
|
Identifiers are optional and must be unique within the note. Classes are unrestricted; `.border` uses the shared border style, while other classes can be styled by the medium. On a captioned table or SQL fence, `.full-width` sets the table across the page in a PDF book instead of in one column (`: Regional trade routes {.full-width}`); the other surfaces ignore it. Named attributes use `key=value`, with quoted values for text containing spaces. Use `#id` and `.class` rather than `id=` and `class=`; event-handler attributes are refused.
|
|
3864
3881
|
|
|
@@ -5876,7 +5893,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5876
5893
|
| `names.<name>` | `string[]` | A list the placeholder of the same name draws from |
|
|
5877
5894
|
| `family` | `Address` | `language` only: the parent language, naming a skill note with `subType: language`; this language reads the parent's words as its own — see [a language's words](#a-languages-words) |
|
|
5878
5895
|
| `abbreviation` | `string` | `language` only: the tag a dictionary prints for it, as `Prov.`; its full name where unstated — see [dictionaries and glossaries](#dictionaries-and-glossaries) |
|
|
5879
|
-
| `grammar` | `{ plural?, verbs? }`
|
|
5896
|
+
| `grammar` | `{ plural?, verbs?, agreement?, participles? }` | Optional noun plurals, one verb pattern, agreement and participles; supplied shapes only are checked |
|
|
5880
5897
|
| `grammar.plural` | `{ ending, becomes }[]` | Literal suffix replacements in authored order; the first match wins |
|
|
5881
5898
|
| `grammar.plural[].ending` | `string`, required, may be empty | Bare suffix to replace; empty matches every noun |
|
|
5882
5899
|
| `grammar.plural[].becomes` | `string`, required, may be empty | Bare replacement; empty deletes the matching suffix |
|
|
@@ -5890,6 +5907,12 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5890
5907
|
| `grammar.verbs.persons.youAll` | `string`, required, may be empty | Ending for you all |
|
|
5891
5908
|
| `grammar.verbs.persons.they` | `string`, required, may be empty | Ending for they |
|
|
5892
5909
|
| `grammar.verbs.replaces` | `string`, nonempty | Closing letters each ending takes the place of; a citation form closing otherwise takes the ending whole. With `persons` only |
|
|
5910
|
+
| `grammar.agreement` | `{ classes, endings }` | Adjectives, pronouns or any other named classes agree by swapping among `endings`; see [optional noun and verb forms](#optional-noun-and-verb-forms) |
|
|
5911
|
+
| `grammar.agreement.classes` | `string[]`, required, at least one | Word class bases from the [master word classes](#word-classes), each once |
|
|
5912
|
+
| `grammar.agreement.endings` | `string[]`, required, at least two | The singular endings, one per gender; the `plural` rule gives each one's plural |
|
|
5913
|
+
| `grammar.participles` | `{ root, affixes }` | Participle suffixes on the verb root; see [optional noun and verb forms](#optional-noun-and-verb-forms) |
|
|
5914
|
+
| `grammar.participles.root` | `string[]`, required, at least one, nonempty | Infinitive endings a citation form drops to give the root; the longest that fits wins |
|
|
5915
|
+
| `grammar.participles.affixes` | `string[]`, required, at least one | Suffixes of `affixes`, by `form`, each attaching to the root as its `replaces` says |
|
|
5893
5916
|
| `classModifiers` | `string[]` | `language` only: the word class modifiers the language uses, each from the [master word classes](#word-classes); empty when unstated |
|
|
5894
5917
|
| `fields` | `{ name, holds }[]` | `language` only: the fields its words are grouped under |
|
|
5895
5918
|
| `fields[].name` | `string`, required | The field's name, which a word's `field` names |
|
|
@@ -5928,6 +5951,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5928
5951
|
| `affixes[].barredAfter` | `string[]` | The sounds a stem may not close on to take the affix, as `[n]` |
|
|
5929
5952
|
| `affixes[].tones` | `string` | The tone pattern it carries, as `L` |
|
|
5930
5953
|
| `affixes[].replaces` | `string` | What it takes the place of at the close of the stem: `vowel` for any closing vowel, or the letters, as `a`; added to the whole stem where absent |
|
|
5954
|
+
| `affixes[].attachesTo` | `string[]` | The word classes it attaches to, by their base, as `[n, adj]`; every class where absent |
|
|
5931
5955
|
| `affixes[].lengthens` | `boolean` | Whether the stem's last vowel lengthens before it; `false` where unstated, and never beside `replaces` |
|
|
5932
5956
|
| `affixes[].front` | `string` | Its form after front vowels, where the language keeps vowel harmony, as `-ür` |
|
|
5933
5957
|
| `tongues` | `{ tag, meaning, language? }[]` | `language` only: the tags a register row's `tongue` names — see [a language's register, sounds and history](#a-languages-register-sounds-and-history) |
|
|
@@ -6203,6 +6227,59 @@ A language uses only its own stated grammar, including for words
|
|
|
6203
6227
|
listed from an ancestor; an inherited word retains its explicit plural.
|
|
6204
6228
|
Grammar is not inherited through `family`.
|
|
6205
6229
|
|
|
6230
|
+
**Agreement** states which word classes agree with a noun and the singular
|
|
6231
|
+
endings they agree by, one for each gender:
|
|
6232
|
+
|
|
6233
|
+
```yaml
|
|
6234
|
+
grammar:
|
|
6235
|
+
plural:
|
|
6236
|
+
- { ending: os, becomes: oi }
|
|
6237
|
+
- { ending: on, becomes: oi }
|
|
6238
|
+
- { ending: a, becomes: ai }
|
|
6239
|
+
agreement:
|
|
6240
|
+
classes: [adj, pron]
|
|
6241
|
+
endings: [os, a, on]
|
|
6242
|
+
```
|
|
6243
|
+
|
|
6244
|
+
A word whose class base is one of `classes` and whose form closes on one of
|
|
6245
|
+
`endings` (the longest that leaves a stem) also has that ending swapped for
|
|
6246
|
+
each other one, and each of those forms, and the word itself, takes the plural
|
|
6247
|
+
the `plural` rule gives. So the adjective _alathos_ gives _alatha_ and
|
|
6248
|
+
_alathon_, and the plural rule gives _alathoi_ and _alathai_. A word closing on
|
|
6249
|
+
no ending, such as _ethlen_, does not agree, and a word of another class, such
|
|
6250
|
+
as the noun _marendos_, keeps its own plural alone. `classes` names at least
|
|
6251
|
+
one base of the [master word classes](#word-classes), each once, and `endings`
|
|
6252
|
+
states at least two endings, each once. Agreement is by these endings alone:
|
|
6253
|
+
there are no cases or declensions.
|
|
6254
|
+
|
|
6255
|
+
**Participles** attach to the verb root, which is the citation form less its
|
|
6256
|
+
infinitive ending:
|
|
6257
|
+
|
|
6258
|
+
```yaml
|
|
6259
|
+
affixes:
|
|
6260
|
+
- { form: "-andar", gloss: "doing it", replaces: vowel }
|
|
6261
|
+
- { form: "-thos", gloss: "done" }
|
|
6262
|
+
grammar:
|
|
6263
|
+
participles:
|
|
6264
|
+
root: [athen, ethen]
|
|
6265
|
+
affixes: ["-andar", "-thos"]
|
|
6266
|
+
```
|
|
6267
|
+
|
|
6268
|
+
A verb closing on one of `root` (the longest that leaves a root) drops it, and
|
|
6269
|
+
each suffix of `data.affixes` that `affixes` names by its `form` attaches to
|
|
6270
|
+
what is left as its own `replaces` says, or whole where it states none: the
|
|
6271
|
+
`front` form attaches too. So _dokathen_ gives the root _dok-_, which gives
|
|
6272
|
+
_dokandar_ and _dokthos_. A participle closing on an agreement ending agrees,
|
|
6273
|
+
so _dokthos_ also gives _doktha_, _dokthon_, _dokthoi_ and _dokthai_. A verb
|
|
6274
|
+
closing on no `root` ending has no participle. `root` names at least one
|
|
6275
|
+
nonempty ending and `affixes` at least one suffix the language's `data.affixes`
|
|
6276
|
+
states; an affix it names that is a prefix, or that the language does not
|
|
6277
|
+
state, is an error at the entry.
|
|
6278
|
+
|
|
6279
|
+
Neither rule is used by the dictionary. Both are read by the
|
|
6280
|
+
[mapped-run word check](#mapping-notes), which accepts the forms they give,
|
|
6281
|
+
and `package-build lexicon --derived` lists the same forms.
|
|
6282
|
+
|
|
6206
6283
|
All grammar is optional. Only a supplied key's shape is checked: no language
|
|
6207
6284
|
must supply a pattern, no noun must supply gender or plural, and no word is
|
|
6208
6285
|
checked against a rule or required to provide an exception. There are no
|
|
@@ -6453,8 +6530,8 @@ note of the package: `{ tag: vylari, language: skill-vylarilng }` makes
|
|
|
6453
6530
|
`exonym:vylari` read the Vylari note. That note's `data.register` must hold a
|
|
6454
6531
|
row of the same name, case folded and NFC, whose `tongue` is the tongue itself,
|
|
6455
6532
|
so a misspelt foreign name is caught where it is listed. A tongue that no entry
|
|
6456
|
-
links to a language note, or that entries link to two, is
|
|
6457
|
-
row's `tongue`; a missing name is
|
|
6533
|
+
links to a language note, or that entries link to two, is an error at the
|
|
6534
|
+
row's `tongue`; a missing name is an error at its `name`:
|
|
6458
6535
|
|
|
6459
6536
|
> `` `Dunharra` is registered as `other:vylari`, but the register of Vylari Language (skill-vylarilng) lists no `vylari` name `Dunharra`; correct the spelling, or register the name there first ``
|
|
6460
6537
|
|
|
@@ -6604,7 +6681,12 @@ to take the affix.
|
|
|
6604
6681
|
|
|
6605
6682
|
**An affix states `replaces`** where it takes the place of the stem's close —
|
|
6606
6683
|
`vowel` for any closing vowel, or the letters, as `a` — and is added to the
|
|
6607
|
-
whole stem where it states none. An affix states `
|
|
6684
|
+
whole stem where it states none. An affix states `attachesTo` where it
|
|
6685
|
+
attaches to some word classes and not others, naming each by its base, as
|
|
6686
|
+
`attachesTo: [n, adj]`: a suffix stating `replaces` then derives forms of those
|
|
6687
|
+
classes' words alone, so `-andi` with `attachesTo: [n]` gives _zunelandi_ from
|
|
6688
|
+
the noun _zunel_ and nothing from a verb. Where it states none, the suffix
|
|
6689
|
+
attaches to a word of every class. An affix states `lengthens: true` where the
|
|
6608
6690
|
stem's last vowel lengthens before it, and never beside `replaces`. A language
|
|
6609
6691
|
that keeps vowel harmony gives an affix's form after front vowels in `front`.
|
|
6610
6692
|
|
|
@@ -6639,12 +6721,13 @@ Each finding is located at the key at fault:
|
|
|
6639
6721
|
| A `scope` Address that does not resolve, or a type that is no note type | error |
|
|
6640
6722
|
| A tongue `language` or a cognate form's language that is no language note | error |
|
|
6641
6723
|
| A register row with neither a `tongue` nor a `kind`, at the row | error |
|
|
6642
|
-
| A register row tagged `other:<tongue>` or `exonym:<tongue>` whose tongue no `data.tongues` entry links to one language note, at the tongue |
|
|
6643
|
-
| A register row tagged `other:<tongue>` or `exonym:<tongue>` whose name the tongue's own register does not list under that tongue, at the name |
|
|
6724
|
+
| A register row tagged `other:<tongue>` or `exonym:<tongue>` whose tongue no `data.tongues` entry links to one language note, at the tongue | error |
|
|
6725
|
+
| A register row tagged `other:<tongue>` or `exonym:<tongue>` whose name the tongue's own register does not list under that tongue, at the name | error |
|
|
6644
6726
|
| A sound-change set's name stated twice, at the second | error |
|
|
6645
6727
|
| A step label stated twice in one set, at the second | error |
|
|
6646
6728
|
| A set's `from` or `to` that is no language note, at the value | error |
|
|
6647
6729
|
| A frame class that is no word class base, at the class | error |
|
|
6730
|
+
| An affix's `attachesTo` that names no class, at the key, or a class that is no word class base or one named twice, at the class | error |
|
|
6648
6731
|
| An affix stating both `replaces` and `lengthens: true`, at `lengthens` | error |
|
|
6649
6732
|
| A calling-name closing with both or neither of `form` and `doubled` | error |
|
|
6650
6733
|
|
|
@@ -467,7 +467,7 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
467
467
|
| `data.fields` | list of `{ name, holds }` | The fields the language's words are grouped under. A language skill note only. |
|
|
468
468
|
| `data.words` | list of `{ form, class, field, gloss, … }` | The words this language states, one map each, in authored order; its automatic dictionary sorts them alphabetically. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
|
|
469
469
|
| `data.affixes` | list of `{ form, gloss, attaches?, examples? }` | The affixes a word's `built` pieces may name, each with its gloss. A language skill note only. |
|
|
470
|
-
| `data.grammar` | a map — `{ plural?, verbs? }`
|
|
470
|
+
| `data.grammar` | a map — `{ plural?, verbs?, agreement?, participles? }` | Optional noun plurals, one verb pattern, agreement and participles, for dictionary presentation and the mapped-run word check. Only the shape of supplied keys is checked; no word must follow or state a rule. A language skill note only. |
|
|
471
471
|
| `data.tongues` | list of `{ tag, meaning, language? }` | The tags a register row's `tongue` may name, each with what it means. A language skill note only. |
|
|
472
472
|
| `data.register` | list of `{ name, tongue?, kind?, built?, gloss?, why? }` | Every name the setting gives in the language's reach, with its tongue and pieces; the notes carrying each name are derived. A language skill note only. |
|
|
473
473
|
| `data.retired` | list of `{ form, instead?, why }` | The forms the setting does not write, and why. A language skill note only. |
|
|
@@ -519,6 +519,7 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
519
519
|
| `data.affixes[].gloss` | a string | yes | What the affix means. |
|
|
520
520
|
| `data.affixes[].attaches` | a string | no | How it attaches, in words, such as `replaces a closing vowel`. |
|
|
521
521
|
| `data.affixes[].replaces` | `vowel`, or the letters, such as `a` | no | What it takes the place of at the close of the stem: `vowel` for any closing vowel, or the letters it replaces; added to the whole stem where absent. |
|
|
522
|
+
| `data.affixes[].attachesTo` | a list of word classes, such as `[n, adj]` | no | The word classes it attaches to, by their base; every class where absent. A suffix stating `replaces` derives forms of these classes' words alone. |
|
|
522
523
|
| `data.affixes[].lengthens` | `true` or `false` | no | Whether the stem's last vowel lengthens before the affix; `false` where unstated. An affix that `replaces` the stem's close does not also lengthen it. |
|
|
523
524
|
| `data.affixes[].front` | the affix as written, such as `-ür` | no | Its form after front vowels, where the language keeps vowel harmony. |
|
|
524
525
|
| `data.affixes[].examples` | a list of strings | no | The names the affix is read from. |
|
|
@@ -537,6 +538,12 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
537
538
|
| `data.grammar.verbs.persons.youAll` | an ending string, empty for unchanged | yes | The ending for you all, added to the citation form or in place of its closing `replaces`. |
|
|
538
539
|
| `data.grammar.verbs.persons.they` | an ending string, empty for unchanged | yes | The ending for they, added to the citation form or in place of its closing `replaces`. |
|
|
539
540
|
| `data.grammar.verbs.replaces` | the closing letters every ending takes the place of | no | Letters a citation form closes on, swapped for each `persons` ending: with `then`, `lokathen` and `zor` give `lokazor`. A citation form closing otherwise takes the ending whole. With `persons` only. |
|
|
541
|
+
| `data.grammar.agreement` | a map — `{ classes, endings }` | no | How words of the named classes agree with a noun: a word closing on one of the `endings` also takes each other one, and each form takes the `plural` rule. Read by the mapped-run word check. |
|
|
542
|
+
| `data.grammar.agreement.classes` | a list of word class bases, such as `[adj, pron]` | yes | The word classes that agree, each a base of the master word classes. |
|
|
543
|
+
| `data.grammar.agreement.endings` | a list of at least two literal endings, such as `[os, a, on]` | yes | The singular endings an agreeing word swaps among, one for each gender; the `plural` rule gives each one's plural. A word closing on none does not agree. |
|
|
544
|
+
| `data.grammar.participles` | a map — `{ root, affixes }` | no | Participles on the verb root: a verb closing on a `root` ending drops it, and each named suffix attaches to what is left; a participle closing on an `agreement` ending agrees. Read by the mapped-run word check. |
|
|
545
|
+
| `data.grammar.participles.root` | a list of closing letters, such as `[athen, ethen]` | yes | The infinitive endings a verb's citation form drops to give its root, the longest that matches first. A verb closing on none has no participle. |
|
|
546
|
+
| `data.grammar.participles.affixes` | a list of suffixes of `data.affixes`, such as `[-andar, -thos]` | yes | The participle suffixes, each attaching to the root as its own `replaces` says: `vowel` or letters take the place of the root's close, and none adds it whole. |
|
|
540
547
|
| `data.tongues[].tag` | a tag such as `dunhari`, or `exonym:<tongue>` | yes | The tag a register row's `tongue` names: lowercase letters and digits, or a tag closing on `:<tongue>`, which declares one tag per tongue named after the colon. |
|
|
541
548
|
| `data.tongues[].meaning` | a string | yes | What a name of this tongue is. |
|
|
542
549
|
| `data.tongues[].language` | a language skill Address | no | The language note the tag names, where it is one. |
|
|
@@ -335,6 +335,57 @@ Caption classes are unrestricted. The built-in `border` class draws the block as
|
|
|
335
335
|
| `.content-figure-label` | the label, number and caption together |
|
|
336
336
|
| `.border` | the block as a boxed aside |
|
|
337
337
|
|
|
338
|
+
## Verse
|
|
339
|
+
|
|
340
|
+
A verse fence reaches the page as a `.verse` block holding a paragraph per
|
|
341
|
+
stanza and a `.verse-line` per line, its indent level as `i1` to `i8`. Its
|
|
342
|
+
keys reach the block as written, `lang` as its language and the rest as
|
|
343
|
+
`data-*` attributes. A numbered verse also carries `.verse-numbered`, and each
|
|
344
|
+
numbered line starts with its `.verse-number`.
|
|
345
|
+
|
|
346
|
+
A [verse in columns](../authoring/links-and-markup.md#verse-in-columns) also
|
|
347
|
+
carries `.verse-columns`, and each verse line is one `.verse-row`:
|
|
348
|
+
|
|
349
|
+
```html
|
|
350
|
+
<div class="verse verse-columns verse-numbered" lang="art-x-vyl" data-numstep="5">
|
|
351
|
+
<p>
|
|
352
|
+
<span class="verse-row"
|
|
353
|
+
><span class="verse-number"></span
|
|
354
|
+
><span class="verse-column"
|
|
355
|
+
><span class="verse-part"
|
|
356
|
+
><span class="script hl-semaron-kuriale" lang="art-x-vyl">…</span></span
|
|
357
|
+
></span
|
|
358
|
+
><span class="verse-column"
|
|
359
|
+
><span class="verse-part"><span lang="en">The hallowed boundary I sing,</span></span
|
|
360
|
+
><span class="verse-part verse-secondary"
|
|
361
|
+
><span lang="art-x-vyl">Thiruthriman sorimamen,</span></span
|
|
362
|
+
></span
|
|
363
|
+
></span
|
|
364
|
+
>
|
|
365
|
+
…
|
|
366
|
+
</p>
|
|
367
|
+
</div>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
A numbered verse in columns gives every row a `.verse-number`, empty on the
|
|
371
|
+
lines it does not number, so the columns stay aligned. A part whose text runs
|
|
372
|
+
right to left carries `dir="rtl"`. Each row is its own grid, so a long line
|
|
373
|
+
keeps its partner level with it. Below a block width of `32rem` a row stacks
|
|
374
|
+
its columns in order; it is a container query, so a narrow journal window
|
|
375
|
+
stacks as well. The Foundry stylesheet draws the same.
|
|
376
|
+
|
|
377
|
+
| class | drawn as |
|
|
378
|
+
| ------------------ | ------------------------------------------------------------- |
|
|
379
|
+
| `.verse` | the poem |
|
|
380
|
+
| `.verse-line` | one line of a poem without columns |
|
|
381
|
+
| `.verse-numbered` | a numbered poem, with a margin for its numbers |
|
|
382
|
+
| `.verse-number` | a line's number, muted |
|
|
383
|
+
| `.verse-columns` | a poem in columns, and the container its rows are measured by |
|
|
384
|
+
| `.verse-row` | one verse line: its number and its columns |
|
|
385
|
+
| `.verse-column` | one column, its entries stacked |
|
|
386
|
+
| `.verse-part` | one entry's text, in the body serif unless its face sets it |
|
|
387
|
+
| `.verse-secondary` | every entry after a column's first, muted |
|
|
388
|
+
|
|
338
389
|
## Search
|
|
339
390
|
|
|
340
391
|
`params.search = true` renders a search box in the header; left unset (or
|
package/docs/source-preview.md
CHANGED
|
@@ -156,7 +156,7 @@ go run check-sanitizer.go /path/to/gitea.ini < preview.html > sanitized.html
|
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
Check both light and dark fixtures containing all four alert types, a custom
|
|
159
|
-
title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes,
|
|
159
|
+
title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes, verse
|
|
160
160
|
and footnotes. All wrapper/token classes and icon shapes should remain, while
|
|
161
161
|
injected scripts and event-handler attributes should disappear. This replay
|
|
162
162
|
checks the sanitizer primitives; also inspect the result in the actual Gitea
|
package/e2e.mjs
CHANGED
|
@@ -799,7 +799,7 @@ export function suiteExecutables(command) {
|
|
|
799
799
|
}
|
|
800
800
|
|
|
801
801
|
/** The Chrome for Testing build shared by local and CI Cypress runs. */
|
|
802
|
-
export const CHROME_BUILD_ID = "
|
|
802
|
+
export const CHROME_BUILD_ID = "158.0.8110.0";
|
|
803
803
|
|
|
804
804
|
/**
|
|
805
805
|
* The browser cache survives reseeding and container teardown.
|
package/engine/anchors.mjs
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
import { scanAlerts } from "./content-alerts.mjs";
|
|
39
39
|
import { scanBlocks } from "./content-blocks.mjs";
|
|
40
|
-
import {
|
|
40
|
+
import { scanVerse } from "./content-verse.mjs";
|
|
41
41
|
import { scanSpans } from "./content-spans.mjs";
|
|
42
42
|
import { slugify } from "./content-slug.mjs";
|
|
43
43
|
import { scanFigures } from "./content-figures.mjs";
|
|
@@ -123,7 +123,7 @@ export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
|
|
|
123
123
|
kind: "prose",
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
-
for (const poem of
|
|
126
|
+
for (const poem of scanVerse(body).blocks) {
|
|
127
127
|
if (poem.id)
|
|
128
128
|
anchors.push({
|
|
129
129
|
slug: slugify(poem.id),
|
|
@@ -270,6 +270,11 @@ export function assetsManifest(staged, stageDir) {
|
|
|
270
270
|
* stage before a file is copied: a package whose provenance cannot be read is
|
|
271
271
|
* not one whose shipping can be decided.
|
|
272
272
|
*
|
|
273
|
+
* The pictures are written beside whatever a `packageBuild.assets` row already
|
|
274
|
+
* put under `icons/` and `images/`; nothing there is removed. A picture whose
|
|
275
|
+
* deployed path is already a file in the stage is a finding, and no picture is
|
|
276
|
+
* written. Clearing an earlier run's pictures is the stage reset's work.
|
|
277
|
+
*
|
|
273
278
|
* @param {object} opts - Options.
|
|
274
279
|
* @param {string} opts.assetsBase - The package's asset directory, the roots'
|
|
275
280
|
* parent.
|
|
@@ -304,15 +309,24 @@ export function stagePackageAssets({
|
|
|
304
309
|
if (findings.length) return { staged: [], excluded: [], findings, manifest: null };
|
|
305
310
|
|
|
306
311
|
const { staged, excluded } = selectStagedAssets(records, shippingLicenses);
|
|
307
|
-
|
|
308
|
-
|
|
312
|
+
const planned = staged.map((record) => ({
|
|
313
|
+
record,
|
|
314
|
+
from: path.join(assetsBase, ...record.asset.path.split("/")),
|
|
315
|
+
to: path.join(stageDir, .../** @type {string} */ (deployedAssetPath(record)).split("/")),
|
|
316
|
+
}));
|
|
317
|
+
for (const { record, from, to } of planned) {
|
|
318
|
+
if (!fs.existsSync(to)) continue;
|
|
319
|
+
findings.push({
|
|
320
|
+
file: to,
|
|
321
|
+
severity: "error",
|
|
322
|
+
message:
|
|
323
|
+
`\`${deployedAssetPath(record)}\` is already in the stage, copied by a ` +
|
|
324
|
+
`\`packageBuild.assets\` row; the picture \`${path.relative(process.cwd(), from)}\` ` +
|
|
325
|
+
"was not staged over it",
|
|
326
|
+
});
|
|
309
327
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const to = path.join(
|
|
313
|
-
stageDir,
|
|
314
|
-
.../** @type {string} */ (deployedAssetPath(record)).split("/"),
|
|
315
|
-
);
|
|
328
|
+
if (findings.length) return { staged: [], excluded: [], findings, manifest: null };
|
|
329
|
+
for (const { from, to } of planned) {
|
|
316
330
|
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
317
331
|
const transformed = transform ? transform(from) : null;
|
|
318
332
|
if (transformed != null) fs.writeFileSync(to, transformed);
|
package/engine/code-fences.mjs
CHANGED
|
@@ -104,11 +104,11 @@ function column(space) {
|
|
|
104
104
|
* @param {object} [options]
|
|
105
105
|
* @param {boolean} [options.spans=true] - Include inline code spans. Set false
|
|
106
106
|
* to consider only block-level code.
|
|
107
|
-
* @param {boolean} [options.
|
|
107
|
+
* @param {boolean} [options.verseAsProse=true] - Treat verse bodies as inline prose.
|
|
108
108
|
* @returns {Array<{start: number, end: number}>} Non-overlapping regions, in
|
|
109
109
|
* source order. `end` is exclusive.
|
|
110
110
|
*/
|
|
111
|
-
export function codeRegions(markdown, { spans = true,
|
|
111
|
+
export function codeRegions(markdown, { spans = true, verseAsProse = true } = {}) {
|
|
112
112
|
const src = String(markdown ?? "");
|
|
113
113
|
if (!src) return [];
|
|
114
114
|
const lines = src.split("\n");
|
|
@@ -145,7 +145,7 @@ export function codeRegions(markdown, { spans = true, poetryAsProse = true } = {
|
|
|
145
145
|
let close = i + 1;
|
|
146
146
|
while (close < lines.length && !closer.test(lines[close])) close++;
|
|
147
147
|
// An unclosed fence runs to the end of the document.
|
|
148
|
-
if (
|
|
148
|
+
if (verseAsProse && /^verse(?:\s|$)/.test(fence[3])) {
|
|
149
149
|
regions.push(through(i, i));
|
|
150
150
|
if (close < lines.length) regions.push(through(close, close));
|
|
151
151
|
} else regions.push(through(i, Math.min(close, lines.length - 1)));
|
|
@@ -53,7 +53,7 @@ import crypto from "node:crypto";
|
|
|
53
53
|
import { slugify } from "./content-slug.mjs";
|
|
54
54
|
import MarkdownIt from "markdown-it";
|
|
55
55
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
56
|
-
import {
|
|
56
|
+
import { scanVerse, renderVerse } from "./content-verse.mjs";
|
|
57
57
|
import { WITHHELD_CLASS, parseHeadingLine, withheldSections } from "./heading-attributes.mjs";
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -200,7 +200,7 @@ export function scanBlocks(source) {
|
|
|
200
200
|
});
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
return { blocks, errors: [...errors, ...
|
|
203
|
+
return { blocks, errors: [...errors, ...scanVerse(source).errors] };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
/**
|
|
@@ -252,7 +252,7 @@ function attributeText(block, target, classes) {
|
|
|
252
252
|
*/
|
|
253
253
|
export function renderBlocks(source, target) {
|
|
254
254
|
const { errors } = scanBlocks(source);
|
|
255
|
-
source =
|
|
255
|
+
source = renderVerse(source).markdown;
|
|
256
256
|
const renderedBlocks = scanBlocks(source).blocks;
|
|
257
257
|
if (!renderedBlocks.length) return { markdown: String(source ?? ""), errors };
|
|
258
258
|
const lines = String(source ?? "").split("\n");
|
|
@@ -154,7 +154,7 @@ function picturesAreMap(contents, resolveRole) {
|
|
|
154
154
|
* @returns {"code"|"table"|"figure"|"map"|"poem"|"prose"} The kind.
|
|
155
155
|
*/
|
|
156
156
|
function figureKind(first, contents, resolveRole) {
|
|
157
|
-
if (/^(?:<div\b[^>]*\bclass="
|
|
157
|
+
if (/^(?:<div\b[^>]*\bclass="verse(?:\s|"))/.test(contents)) return "poem";
|
|
158
158
|
if (first?.type === "table_open") return "table";
|
|
159
159
|
if (first?.type === "fence" && /^\s*sql\b/i.test(first.info ?? "")) return "table";
|
|
160
160
|
if (first?.type === "fence" || first?.type === "code_block") return "code";
|
|
@@ -220,7 +220,7 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
220
220
|
const explicit = attributes.values.type;
|
|
221
221
|
if (
|
|
222
222
|
explicit &&
|
|
223
|
-
!["figure", "table", "
|
|
223
|
+
!["figure", "table", "verse", "code", "prose", "example"].includes(explicit)
|
|
224
224
|
)
|
|
225
225
|
fault(i, `unsupported caption type "${explicit}"`);
|
|
226
226
|
if (!caption) fault(i, "a caption needs text");
|
|
@@ -281,7 +281,7 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
281
281
|
) {
|
|
282
282
|
fault(
|
|
283
283
|
bodyStart,
|
|
284
|
-
"a caption must be followed by an image, table,
|
|
284
|
+
"a caption must be followed by an image, table, verse or code fence, paragraph, quote, or fenced div",
|
|
285
285
|
);
|
|
286
286
|
continue;
|
|
287
287
|
}
|
|
@@ -306,11 +306,11 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
306
306
|
continue;
|
|
307
307
|
}
|
|
308
308
|
kind =
|
|
309
|
-
first.type === "fence" && /^
|
|
309
|
+
first.type === "fence" && /^verse(?:\s|$)/.test(first.info) ?
|
|
310
310
|
"poem"
|
|
311
311
|
: figureKind(first, contents.trim(), resolveRole);
|
|
312
312
|
}
|
|
313
|
-
if (explicit) kind = explicit === "
|
|
313
|
+
if (explicit) kind = explicit === "verse" ? "poem" : explicit;
|
|
314
314
|
const id = attributes.id;
|
|
315
315
|
if (id && ids.has(slugify(id))) fault(i, `duplicate figure id "${id}"`);
|
|
316
316
|
if (id) ids.add(slugify(id));
|
package/engine/content-index.mjs
CHANGED
|
@@ -1512,7 +1512,7 @@ function dynamicPublicationBody(body) {
|
|
|
1512
1512
|
const source = String(body ?? "");
|
|
1513
1513
|
return (
|
|
1514
1514
|
[...matchAllOutsideCode(source, EXPRESSION)].length > 0 ||
|
|
1515
|
-
codeRegions(source, { spans: false,
|
|
1515
|
+
codeRegions(source, { spans: false, verseAsProse: false }).some(({ start, end }) => {
|
|
1516
1516
|
const opener = FENCE_LINE.exec(source.slice(start, end).split("\n", 1)[0]);
|
|
1517
1517
|
const language = opener && parseHeaderArgs(opener[3]).language;
|
|
1518
1518
|
return language === "sql" || language === PAGE_LIST_LANGUAGE;
|