@heroiclands/package-build 23.12.8 → 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 +48 -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/links-and-markup.md +156 -22
- package/docs/commands.md +22 -7
- package/docs/configuration.md +1 -1
- package/docs/content-format.md +3 -3
- package/docs/diagnostics.md +42 -0
- package/docs/reference/format-details.md +99 -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/engine/anchors.mjs +2 -2
- 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 +2 -2
- package/types/engine/code-fences.d.mts +3 -3
- 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/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/web-wikilinks.d.mts +1 -1
- package/types/engine/word-forms.d.mts +82 -0
- 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):
|
|
@@ -3314,7 +3325,7 @@ inherited words are read through `language_lineage`.
|
|
|
3314
3325
|
**`affixes`** is one row per entry of `data.affixes` on every language note,
|
|
3315
3326
|
and **`fields`** one per entry of `data.fields`, in the order the note states
|
|
3316
3327
|
them. Each has `language` and `position` as `words` does, then every key of the
|
|
3317
|
-
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`
|
|
3318
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
|
|
3319
3330
|
`NULL`, `examples` is a list, and a language note tagged `gm` contributes no
|
|
3320
3331
|
row on a public surface. An affix's `tones` is the tone pattern it carries,
|
|
@@ -3398,14 +3409,14 @@ metadata supplies `names` in that package's SQL schema. Public queries exclude
|
|
|
3398
3409
|
GM-only owners; all/native contexts retain them. Only authored occurrences
|
|
3399
3410
|
contribute rows, so generated queries and sections add no further names.
|
|
3400
3411
|
|
|
3401
|
-
**`runs`** is one row per span, generic div and
|
|
3412
|
+
**`runs`** is one row per span, generic div and verse fence an authored note
|
|
3402
3413
|
body marks with `lang=`, `font=` or `map=`. Each row has these columns,
|
|
3403
3414
|
including on an empty relation:
|
|
3404
3415
|
|
|
3405
3416
|
| Column | SQL type | Meaning |
|
|
3406
3417
|
| -------- | --------- | -------------------------------------------------------------------------------------------- |
|
|
3407
3418
|
| `owner` | `VARCHAR` | Containing authored page's canonical NOTE Address, or `NULL` when the record has no Address. |
|
|
3408
|
-
| `kind` | `VARCHAR` | `span`, `block` or `
|
|
3419
|
+
| `kind` | `VARCHAR` | `span`, `block` or `verse`. |
|
|
3409
3420
|
| `lang` | `VARCHAR` | The language as written, its own or inherited, or `NULL`. |
|
|
3410
3421
|
| `tag` | `VARCHAR` | The BCP 47 tag `lang` resolves to, or `NULL` when it resolves to none. |
|
|
3411
3422
|
| `font` | `VARCHAR` | The font as written, its own or inherited, or `NULL`. |
|
|
@@ -3860,11 +3871,11 @@ The caption attaches to exactly the next supported block:
|
|
|
3860
3871
|
| Markdown table | `table` | Table |
|
|
3861
3872
|
| Image, Markdown or wikilink | `figure` | Figure |
|
|
3862
3873
|
| SQL fence, including backtick SQL | `table` | Table |
|
|
3863
|
-
|
|
|
3874
|
+
| Verse fence | `verse` | Poem |
|
|
3864
3875
|
| Any other code fence | `code` | Code |
|
|
3865
3876
|
| Paragraph, quote block or fenced div | `prose` | Prose |
|
|
3866
3877
|
|
|
3867
|
-
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.
|
|
3868
3879
|
|
|
3869
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.
|
|
3870
3881
|
|
|
@@ -5882,7 +5893,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5882
5893
|
| `names.<name>` | `string[]` | A list the placeholder of the same name draws from |
|
|
5883
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) |
|
|
5884
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) |
|
|
5885
|
-
| `grammar` | `{ plural?, verbs? }`
|
|
5896
|
+
| `grammar` | `{ plural?, verbs?, agreement?, participles? }` | Optional noun plurals, one verb pattern, agreement and participles; supplied shapes only are checked |
|
|
5886
5897
|
| `grammar.plural` | `{ ending, becomes }[]` | Literal suffix replacements in authored order; the first match wins |
|
|
5887
5898
|
| `grammar.plural[].ending` | `string`, required, may be empty | Bare suffix to replace; empty matches every noun |
|
|
5888
5899
|
| `grammar.plural[].becomes` | `string`, required, may be empty | Bare replacement; empty deletes the matching suffix |
|
|
@@ -5896,6 +5907,12 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5896
5907
|
| `grammar.verbs.persons.youAll` | `string`, required, may be empty | Ending for you all |
|
|
5897
5908
|
| `grammar.verbs.persons.they` | `string`, required, may be empty | Ending for they |
|
|
5898
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 |
|
|
5899
5916
|
| `classModifiers` | `string[]` | `language` only: the word class modifiers the language uses, each from the [master word classes](#word-classes); empty when unstated |
|
|
5900
5917
|
| `fields` | `{ name, holds }[]` | `language` only: the fields its words are grouped under |
|
|
5901
5918
|
| `fields[].name` | `string`, required | The field's name, which a word's `field` names |
|
|
@@ -5934,6 +5951,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5934
5951
|
| `affixes[].barredAfter` | `string[]` | The sounds a stem may not close on to take the affix, as `[n]` |
|
|
5935
5952
|
| `affixes[].tones` | `string` | The tone pattern it carries, as `L` |
|
|
5936
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 |
|
|
5937
5955
|
| `affixes[].lengthens` | `boolean` | Whether the stem's last vowel lengthens before it; `false` where unstated, and never beside `replaces` |
|
|
5938
5956
|
| `affixes[].front` | `string` | Its form after front vowels, where the language keeps vowel harmony, as `-ür` |
|
|
5939
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) |
|
|
@@ -6209,6 +6227,59 @@ A language uses only its own stated grammar, including for words
|
|
|
6209
6227
|
listed from an ancestor; an inherited word retains its explicit plural.
|
|
6210
6228
|
Grammar is not inherited through `family`.
|
|
6211
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
|
+
|
|
6212
6283
|
All grammar is optional. Only a supplied key's shape is checked: no language
|
|
6213
6284
|
must supply a pattern, no noun must supply gender or plural, and no word is
|
|
6214
6285
|
checked against a rule or required to provide an exception. There are no
|
|
@@ -6459,8 +6530,8 @@ note of the package: `{ tag: vylari, language: skill-vylarilng }` makes
|
|
|
6459
6530
|
`exonym:vylari` read the Vylari note. That note's `data.register` must hold a
|
|
6460
6531
|
row of the same name, case folded and NFC, whose `tongue` is the tongue itself,
|
|
6461
6532
|
so a misspelt foreign name is caught where it is listed. A tongue that no entry
|
|
6462
|
-
links to a language note, or that entries link to two, is
|
|
6463
|
-
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`:
|
|
6464
6535
|
|
|
6465
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 ``
|
|
6466
6537
|
|
|
@@ -6610,7 +6681,12 @@ to take the affix.
|
|
|
6610
6681
|
|
|
6611
6682
|
**An affix states `replaces`** where it takes the place of the stem's close —
|
|
6612
6683
|
`vowel` for any closing vowel, or the letters, as `a` — and is added to the
|
|
6613
|
-
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
|
|
6614
6690
|
stem's last vowel lengthens before it, and never beside `replaces`. A language
|
|
6615
6691
|
that keeps vowel harmony gives an affix's form after front vowels in `front`.
|
|
6616
6692
|
|
|
@@ -6645,12 +6721,13 @@ Each finding is located at the key at fault:
|
|
|
6645
6721
|
| A `scope` Address that does not resolve, or a type that is no note type | error |
|
|
6646
6722
|
| A tongue `language` or a cognate form's language that is no language note | error |
|
|
6647
6723
|
| A register row with neither a `tongue` nor a `kind`, at the row | error |
|
|
6648
|
-
| A register row tagged `other:<tongue>` or `exonym:<tongue>` whose tongue no `data.tongues` entry links to one language note, at the tongue |
|
|
6649
|
-
| 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 |
|
|
6650
6726
|
| A sound-change set's name stated twice, at the second | error |
|
|
6651
6727
|
| A step label stated twice in one set, at the second | error |
|
|
6652
6728
|
| A set's `from` or `to` that is no language note, at the value | error |
|
|
6653
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 |
|
|
6654
6731
|
| An affix stating both `replaces` and `lengthens: true`, at `lengthens` | error |
|
|
6655
6732
|
| A calling-name closing with both or neither of `form` and `doubled` | error |
|
|
6656
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/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),
|
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;
|