@heroiclands/package-build 22.11.0 → 22.12.1
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 +14 -0
- package/CONTENT.md +6 -0
- package/docs/api.md +3 -2
- package/docs/content-format.md +76 -35
- package/docs/getting-started.md +12 -0
- package/engine/content-images.mjs +41 -14
- package/engine/frontmatter-lint.mjs +15 -7
- package/engine/holdings.mjs +7 -9
- package/engine/map-places.mjs +8 -7
- package/engine/populations.mjs +5 -2
- package/package.json +1 -1
- package/types/engine/content-images.d.mts +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.12.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
**Content authoring**
|
|
8
|
+
|
|
9
|
+
- Image embeds and Markdown images accept named size hints, so notes can carry the intended size while their displayed appearance stays the same.
|
|
10
|
+
|
|
11
|
+
## 22.12.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- **References:** An Address in note data that names no local or declared dependency target now reports an error; missing artwork keeps its fallback warning.
|
|
16
|
+
|
|
3
17
|
## 22.11.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/CONTENT.md
CHANGED
|
@@ -1249,6 +1249,12 @@ somewhere other than where they were made, or not at all:
|
|
|
1249
1249
|
reference answered by a vendored manifest lands exactly as it would in a
|
|
1250
1250
|
wikilink. `--no-references` turns this one off for a tree whose cross-package
|
|
1251
1251
|
references it cannot see.
|
|
1252
|
+
- **Dead Address reference** — an Address-valued `data:` field naming no local
|
|
1253
|
+
note, asset or entry in a declared dependency index. The finding names the
|
|
1254
|
+
field and the complete Address it names. A short form means this package;
|
|
1255
|
+
another package requires the full Address. `--no-references` also turns off
|
|
1256
|
+
this target check. Art slots report unresolved art through their own fallback
|
|
1257
|
+
warning.
|
|
1252
1258
|
|
|
1253
1259
|
**A schema says what a note may _write_, not what the compiler emits.** Those
|
|
1254
1260
|
are different, and the difference is the whole calibration of the check: a note
|
package/docs/api.md
CHANGED
|
@@ -935,16 +935,17 @@ Raw HTML in a note's prose, reported. **A note is markdown.** What markdown cann
|
|
|
935
935
|
|
|
936
936
|
### `engine.contentImages`
|
|
937
937
|
|
|
938
|
-
An image saying how wide it is and where it sits. A markdown image carries no indication of either, so each of the three surfaces decides for itself and the author — who is the one who knows — has no way to say. A directive in the curly-attribute convention Pandoc and Kramdown use closes that, in
|
|
938
|
+
An image saying how wide it is and where it sits. A markdown image carries no indication of either, so each of the three surfaces decides for itself and the author — who is the one who knows — has no way to say. A directive in the curly-attribute convention Pandoc and Kramdown use closes that, in three closed vocabularies: a width class, a named `size:`, and a `float:` position. The named size is accepted without affecting display.
|
|
939
939
|
|
|
940
940
|
| Export | Signature | Returns | Use it when |
|
|
941
941
|
| --------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
942
942
|
| `IMAGE_CLASSES` | `const IMAGE_CLASSES` | — | The width classes an image may carry, and what each means to a renderer. |
|
|
943
|
+
| `IMAGE_SIZES` | `const IMAGE_SIZES` | — | The named sizes an image directive accepts without applying a display size. |
|
|
943
944
|
| `IMAGE_FLOATS` | `const IMAGE_FLOATS` | — | The `float:` positions an image may take, and where each puts it. |
|
|
944
945
|
| `IMAGE_FIGURE_CLASS` | `const IMAGE_FIGURE_CLASS` | — | The class every figure carries, whatever its width or position. |
|
|
945
946
|
| `IMAGE_PATTERN` | `const IMAGE_PATTERN` | — | A markdown image, with the directive it may carry. |
|
|
946
947
|
| `imageSourceProblem` | `function imageSourceProblem(src)` | {string} The problem, as a finding's sentence, or `""`. | What is wrong with an image's address, or `""` when nothing is. |
|
|
947
|
-
| `parseImageDirective` | `function parseImageDirective(raw)` | {{classes: string[], float: string, problems: string[]}} What was written, and what cannot be honoured.
|
|
948
|
+
| `parseImageDirective` | `function parseImageDirective(raw)` | {{classes: string[], size: string, float: string, problems: string[]}} What was written, and what cannot be honoured. | Read the directive on an image. |
|
|
948
949
|
| `figureClasses` | `function figureClasses(directive)` | {string} A space-separated class list. | The classes a figure carries, from a parsed directive. |
|
|
949
950
|
| `escapeHtml` | `function escapeHtml(text)` | {string} The same value, safe in markup. | Text going inside an HTML attribute or between tags. |
|
|
950
951
|
| `imageFigureHtml` | `function imageFigureHtml(image)` | {string} The figure, as one HTML block. | One image as the `<figure>` both HTML surfaces render. |
|
package/docs/content-format.md
CHANGED
|
@@ -1315,6 +1315,12 @@ Address-keyed relation maps hold typed targets and standing values. Only generat
|
|
|
1315
1315
|
output serializes these properties to Address strings. Runtime Shortcodes,
|
|
1316
1316
|
Foundry UUIDs, file paths and URLs keep their own representations.
|
|
1317
1317
|
|
|
1318
|
+
An Address-valued `data:` field must name a target in this package or a declared
|
|
1319
|
+
dependency index. `content-build lint` reports a missing target as an error at
|
|
1320
|
+
the value or map key that names it. A stub counts as a target for data even
|
|
1321
|
+
though it publishes no page. Art slots use their own missing-art warning and
|
|
1322
|
+
document fallback.
|
|
1323
|
+
|
|
1318
1324
|
#### An omitted segment defaults from where the link is written
|
|
1319
1325
|
|
|
1320
1326
|
It is **not** a wildcard and resolution is not a search. Every short form expands
|
|
@@ -1854,15 +1860,16 @@ immediately after the closing `]]`, with no space:
|
|
|
1854
1860
|
|
|
1855
1861
|
```markdown
|
|
1856
1862
|
![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
|
|
1863
|
+
![[branwldrgr|Brànwâal Dôrgaar]]{size: medium, float: top-left}
|
|
1857
1864
|
![[thalornamap|Map of Thalorna]]{.full-width}
|
|
1858
1865
|
![[thalornamap|Map of Thalorna]]{.full-width, float: top-left}
|
|
1859
1866
|
![[thalornamap|Map of Thalorna]]{float: top-left, .full-width}
|
|
1860
1867
|
```
|
|
1861
1868
|
|
|
1862
|
-
**The same parser reads both**, so an embed takes a width class, a `
|
|
1863
|
-
|
|
1869
|
+
**The same parser reads both**, so an embed takes a width class, a `size:`, a
|
|
1870
|
+
`float:`, or a combination in any order, comma-separated — everything
|
|
1864
1871
|
[an image's directive](#width-is-a-class-and-the-ordinary-width-carries-no-marker)
|
|
1865
|
-
takes and nothing beyond it. The width and position vocabularies are the ones
|
|
1872
|
+
takes and nothing beyond it. The width, size, and position vocabularies are the ones
|
|
1866
1873
|
above, and they are closed here for the same reason: a directive that is quietly
|
|
1867
1874
|
ignored looks exactly like one that worked, and a directive holding a problem is
|
|
1868
1875
|
not honoured in part either.
|
|
@@ -2117,6 +2124,7 @@ part of this format follows.
|
|
|
2117
2124
|
|
|
2118
2125
|
```markdown
|
|
2119
2126
|
![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
|
|
2127
|
+
![[branwldrgr|Brànwâal Dôrgaar]]{size: medium, float: top-left}
|
|
2120
2128
|
|
|
2121
2129
|
{.full-width}
|
|
2122
2130
|
```
|
|
@@ -2140,13 +2148,30 @@ place for those words and this is it.
|
|
|
2140
2148
|
| `.full-width` | the full page | full content width | full page width |
|
|
2141
2149
|
|
|
2142
2150
|
The simple case needs no spelling, which is why the ordinary width has no name.
|
|
2143
|
-
A third width joins this table as a name.
|
|
2144
2151
|
|
|
2145
2152
|
**No pixel values, ever.** A number means something in a browser and nothing
|
|
2146
2153
|
coherent in print, and a directive carrying a class and a dimension at once
|
|
2147
2154
|
gives one question two answers with no rule for which wins. `{width=800}` is
|
|
2148
2155
|
refused.
|
|
2149
2156
|
|
|
2157
|
+
##### Named size is `size:`
|
|
2158
|
+
|
|
2159
|
+
| `size` value | display |
|
|
2160
|
+
| ------------ | ------------------------ |
|
|
2161
|
+
| `auto` | same as omitting `size:` |
|
|
2162
|
+
| `small` | same as omitting `size:` |
|
|
2163
|
+
| `medium` | same as omitting `size:` |
|
|
2164
|
+
| `large` | same as omitting `size:` |
|
|
2165
|
+
| `xlarge` | same as omitting `size:` |
|
|
2166
|
+
| `full-width` | same as omitting `size:` |
|
|
2167
|
+
|
|
2168
|
+
The parser accepts and records these names for Markdown images and embedded
|
|
2169
|
+
assets. **A `size:` value does not change the displayed dimensions** on the
|
|
2170
|
+
website, in Foundry, or in the book. With no `size:`, the parser records `auto`;
|
|
2171
|
+
the existing rendering rules apply. In particular, book images occupy a column
|
|
2172
|
+
unless they carry `.full-width`. A `size:` may sit before or after `float:` in
|
|
2173
|
+
the same directive, and neither changes the other's behavior.
|
|
2174
|
+
|
|
2150
2175
|
##### Position is `float:`
|
|
2151
2176
|
|
|
2152
2177
|
| `float` value | book | website and Foundry journal |
|
|
@@ -2176,7 +2201,7 @@ A `.full-width` image that **also states a `float:`** is asking for a float and
|
|
|
2176
2201
|
gets one, with no break before it. A float is placed where it fits, which may be
|
|
2177
2202
|
the next page.
|
|
2178
2203
|
|
|
2179
|
-
#####
|
|
2204
|
+
##### All three vocabularies are closed
|
|
2180
2205
|
|
|
2181
2206
|
`{.fullwidth}`, `{.full_width}`, `{width=800}` and `{float: middle}` are
|
|
2182
2207
|
refused, located by file, line and column, and the build fails. An unrecognised
|
|
@@ -2188,8 +2213,8 @@ even in the part that parsed: a half-honoured directive is the same silent
|
|
|
2188
2213
|
failure in a smaller costume. The image keeps its braces and renders them as
|
|
2189
2214
|
literal text, so the mistake is visible on the page as well as in the log.
|
|
2190
2215
|
|
|
2191
|
-
**Nothing else reaches emitted markup.** The
|
|
2192
|
-
are all that leave a note; there is no `style`, no `id` and no arbitrary
|
|
2216
|
+
**Nothing else reaches emitted markup.** The width and position classes and the
|
|
2217
|
+
address are all that leave a note; there is no `style`, no `id` and no arbitrary
|
|
2193
2218
|
attribute, because data is never compiled into markup here. An address carries
|
|
2194
2219
|
a scheme of `http:` or `https:`, or none at all.
|
|
2195
2220
|
|
|
@@ -3015,9 +3040,7 @@ If `sohl` is present, this becomes an `affliction` item.
|
|
|
3015
3040
|
| `subType` | `system.subType` | NA |
|
|
3016
3041
|
| `data.transmission` | `system.transmission` | NA |
|
|
3017
3042
|
| `data.outcome` | `system.outcome` | NA |
|
|
3018
|
-
| `data.healingRate` | `system.healingRateBase` | NA |
|
|
3019
3043
|
| `data.contagionIndex` | `system.contagionIndexBase` | NA |
|
|
3020
|
-
| `data.outcomeTraumas` | `system.outcomeTraumas` | NA |
|
|
3021
3044
|
| `data.onsetDurationFormula` | `system.onsetDurationFormula` | NA |
|
|
3022
3045
|
| `data.onsetDurationBase` | `system.onsetDurationBase` | NA |
|
|
3023
3046
|
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
@@ -3055,6 +3078,10 @@ that writes one fails the build. They are world times, and `0` is a valid one,
|
|
|
3055
3078
|
so there is no blank a note could write either; leave them out and the data
|
|
3056
3079
|
model's `null` stands.
|
|
3057
3080
|
|
|
3081
|
+
`data.healingRate` and `data.outcomeTraumas` are page data. SoHL reads an
|
|
3082
|
+
affliction’s base healing rate from `sohl.healingRateBase`. Its item declaration
|
|
3083
|
+
does not read `data.outcomeTraumas`.
|
|
3084
|
+
|
|
3058
3085
|
### type: armorgear
|
|
3059
3086
|
|
|
3060
3087
|
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
@@ -3129,7 +3156,9 @@ if a `sohl` property is present, a SoHL item of type "concoctiongear" will be cr
|
|
|
3129
3156
|
| `data.value` | `system.valueBase` | NA |
|
|
3130
3157
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3131
3158
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3132
|
-
|
|
3159
|
+
|
|
3160
|
+
`data.quantity` is page data for a concoction. SoHL sets `system.quantity` to
|
|
3161
|
+
`1` for each compendium item.
|
|
3133
3162
|
|
|
3134
3163
|
### type: containergear
|
|
3135
3164
|
|
|
@@ -3142,19 +3171,21 @@ Note: `data.quantity` may not be specified; quantity is always set to 1.
|
|
|
3142
3171
|
| `value` | `number` | Gear value |
|
|
3143
3172
|
| `quality` | `number` | Gear quality |
|
|
3144
3173
|
| `durability` | `number` | Gear durability |
|
|
3145
|
-
| `capacity` | `number` |
|
|
3174
|
+
| `capacity` | `number` | HM3 container capacity (in lbs) |
|
|
3146
3175
|
|
|
3147
3176
|
if a `sohl` property is present, a SoHL item of type "containergear" will be created.
|
|
3148
3177
|
|
|
3149
3178
|
if a `hm3` property is present, an HM3 item of type "containergear" will be created.
|
|
3150
3179
|
|
|
3151
|
-
| shared source | → sohl
|
|
3152
|
-
| ----------------- |
|
|
3153
|
-
| `data.weight` | `system.weightBase`
|
|
3154
|
-
| `data.value` | `system.valueBase`
|
|
3155
|
-
| `data.quality` | `system.qualityBase`
|
|
3156
|
-
| `data.durability` | `system.durabilityBase`
|
|
3157
|
-
| `data.capacity` |
|
|
3180
|
+
| shared source | → sohl | → hm3 |
|
|
3181
|
+
| ----------------- | ----------------------- | --------------------- |
|
|
3182
|
+
| `data.weight` | `system.weightBase` | `system.weight` |
|
|
3183
|
+
| `data.value` | `system.valueBase` | `system.value` |
|
|
3184
|
+
| `data.quality` | `system.qualityBase` | NA |
|
|
3185
|
+
| `data.durability` | `system.durabilityBase` | NA |
|
|
3186
|
+
| `data.capacity` | NA | `system.capacity.max` |
|
|
3187
|
+
|
|
3188
|
+
SoHL reads a container’s capacity in pounds from `sohl.maxCapacity`.
|
|
3158
3189
|
|
|
3159
3190
|
### type: miscgear
|
|
3160
3191
|
|
|
@@ -3165,7 +3196,7 @@ if a `hm3` property is present, an HM3 item of type "containergear" will be crea
|
|
|
3165
3196
|
| `value` | `number` | Gear value |
|
|
3166
3197
|
| `quality` | `number` | Gear quality |
|
|
3167
3198
|
| `durability` | `number` | Gear durability |
|
|
3168
|
-
| `quantity` | `number` |
|
|
3199
|
+
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3169
3200
|
|
|
3170
3201
|
if a `sohl` property is present, a SoHL item of type "miscgear" will be created.
|
|
3171
3202
|
|
|
@@ -3177,7 +3208,9 @@ if a `hm3` property is present, an HM3 item of type "miscgear" will be created.
|
|
|
3177
3208
|
| `data.value` | `system.valueBase` | `system.value` |
|
|
3178
3209
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3179
3210
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3180
|
-
| `data.quantity` |
|
|
3211
|
+
| `data.quantity` | NA | `system.quantity` |
|
|
3212
|
+
|
|
3213
|
+
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3181
3214
|
|
|
3182
3215
|
### type: mystery
|
|
3183
3216
|
|
|
@@ -3213,10 +3246,12 @@ if a `sohl` property is present, a SoHL item of type "mystery" will be created.
|
|
|
3213
3246
|
| --------------------- | ----------------------- | ----- |
|
|
3214
3247
|
| `subType` | `system.subType` | NA |
|
|
3215
3248
|
| `data.skillAptitudes` | `system.skillAptitudes` | NA |
|
|
3216
|
-
| `data.level` | `system.levelBase` | NA |
|
|
3217
3249
|
| `data.charges.value` | `system.charges.value` | NA |
|
|
3218
3250
|
| `data.charges.max` | `system.charges.max` | NA |
|
|
3219
3251
|
|
|
3252
|
+
`data.level` is page data for a mystery. SoHL reads the item’s level from
|
|
3253
|
+
`sohl.levelBase`.
|
|
3254
|
+
|
|
3220
3255
|
### type: mysticalability
|
|
3221
3256
|
|
|
3222
3257
|
**subType**:
|
|
@@ -3250,13 +3285,15 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
3250
3285
|
`sohl.system` as shortcodes and have no shared source; `data.assocSkill` and
|
|
3251
3286
|
`data.assocAffiliation` name the same two notes as Addresses, for the page.
|
|
3252
3287
|
|
|
3253
|
-
| shared source | → sohl
|
|
3254
|
-
| -------------------- |
|
|
3255
|
-
| `subType` | `system.subType`
|
|
3256
|
-
| `data.
|
|
3257
|
-
| `data.
|
|
3258
|
-
|
|
3259
|
-
|
|
3288
|
+
| shared source | → sohl | → hm3 |
|
|
3289
|
+
| -------------------- | ---------------------- | ------------- |
|
|
3290
|
+
| `subType` | `system.subType` | **see above** |
|
|
3291
|
+
| `data.charges.value` | `system.charges.value` | NA |
|
|
3292
|
+
| `data.charges.max` | `system.charges.max` | NA |
|
|
3293
|
+
|
|
3294
|
+
`data.masteryLevel` and `data.level` are page data for an ability. SoHL reads
|
|
3295
|
+
the item’s base mastery and level from `sohl.masteryLevelBase` and
|
|
3296
|
+
`sohl.levelBase`.
|
|
3260
3297
|
|
|
3261
3298
|
### type: projectilegear
|
|
3262
3299
|
|
|
@@ -3276,7 +3313,7 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
3276
3313
|
| `value` | `number` | Gear value |
|
|
3277
3314
|
| `quality` | `number` | Gear quality |
|
|
3278
3315
|
| `durability` | `number` | Gear durability |
|
|
3279
|
-
| `quantity` | `number` |
|
|
3316
|
+
| `quantity` | `number` | HM3 gear quantity (default: 1) |
|
|
3280
3317
|
|
|
3281
3318
|
if a `sohl` property is present, a SoHL item of type "projectilegear" will be created.
|
|
3282
3319
|
|
|
@@ -3293,7 +3330,9 @@ HM3 side while remaining distinct on the SoHL side.
|
|
|
3293
3330
|
| `data.value` | `system.valueBase` | `system.value` |
|
|
3294
3331
|
| `data.quality` | `system.qualityBase` | NA |
|
|
3295
3332
|
| `data.durability` | `system.durabilityBase` | NA |
|
|
3296
|
-
| `data.quantity` |
|
|
3333
|
+
| `data.quantity` | NA | `system.quantity` |
|
|
3334
|
+
|
|
3335
|
+
SoHL sets `system.quantity` to `1` for each compendium item.
|
|
3297
3336
|
|
|
3298
3337
|
### type: skill
|
|
3299
3338
|
|
|
@@ -3313,7 +3352,7 @@ HM3 side while remaining distinct on the SoHL side.
|
|
|
3313
3352
|
| `data` property | Values | Description |
|
|
3314
3353
|
| ------------------ | --------- | ------------------------------------------ |
|
|
3315
3354
|
| `templatePriority` | `number` | Template priority, _null_ = not a template |
|
|
3316
|
-
| `masteryLevel` | `number` |
|
|
3355
|
+
| `masteryLevel` | `number` | HM3 mastery level |
|
|
3317
3356
|
| `parentSkill` | `Address` | Parent skill this skill specializes |
|
|
3318
3357
|
|
|
3319
3358
|
if a `sohl` property is present, a SoHL item of type "skill" will be created.
|
|
@@ -3326,10 +3365,12 @@ Note: `hm3.system.type` (skill types) use the values "Craft", "Physical", "Commu
|
|
|
3326
3365
|
skill this one specializes, and has no shared source; `data.parentSkill` names that
|
|
3327
3366
|
skill as an Address, for the page.
|
|
3328
3367
|
|
|
3329
|
-
| shared source | → sohl
|
|
3330
|
-
| ------------------- |
|
|
3331
|
-
| `subType` | `system.subType`
|
|
3332
|
-
| `data.masteryLevel` |
|
|
3368
|
+
| shared source | → sohl | → hm3 |
|
|
3369
|
+
| ------------------- | ---------------- | --------------------- |
|
|
3370
|
+
| `subType` | `system.subType` | See notes above |
|
|
3371
|
+
| `data.masteryLevel` | NA | `system.masteryLevel` |
|
|
3372
|
+
|
|
3373
|
+
SoHL reads a skill’s base mastery from `sohl.masteryLevelBase`.
|
|
3333
3374
|
|
|
3334
3375
|
### type: trauma
|
|
3335
3376
|
|
package/docs/getting-started.md
CHANGED
|
@@ -332,6 +332,18 @@ an error. A `data:` block and a `sohl:` / `hm3:` block are closed: a misspelled
|
|
|
332
332
|
key there is a finding that names the key you meant.
|
|
333
333
|
[`content-format.md`](content-format.md) is the specification.
|
|
334
334
|
|
|
335
|
+
An image stands in its own paragraph. Its directive accepts a named size and a
|
|
336
|
+
position together:
|
|
337
|
+
|
|
338
|
+
```markdown
|
|
339
|
+
![[icon-anubis|Anubis]]{size: medium, float: top-left}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The accepted sizes are `auto`, `small`, `medium`, `large`, `xlarge`, and
|
|
343
|
+
`full-width`. Omitting `size:` means `auto`. The build checks the name, but
|
|
344
|
+
`size:` does not change the displayed dimensions on the website, in Foundry, or
|
|
345
|
+
in the book. See [Images](content-format.md#images) for the rendering rules.
|
|
346
|
+
|
|
335
347
|
## Step 6 — check the tree
|
|
336
348
|
|
|
337
349
|
Four checks, and they are quick enough to run continuously while authoring.
|
|
@@ -19,14 +19,15 @@
|
|
|
19
19
|
* {float: top-left}
|
|
20
20
|
* {.full-width}
|
|
21
21
|
*
|
|
22
|
-
* ##
|
|
22
|
+
* ## Three closed vocabularies, and closed is the point
|
|
23
23
|
*
|
|
24
24
|
* **Width is a class**, and the ordinary width carries no marker at all — the
|
|
25
25
|
* simple case needs no spelling. {@link IMAGE_CLASSES} holds the one class
|
|
26
26
|
* there is. **Position is `float:`**, and {@link IMAGE_FLOATS} holds the five
|
|
27
|
-
* values it takes.
|
|
27
|
+
* values it takes. **Named size is `size:`**, and {@link IMAGE_SIZES} holds its
|
|
28
|
+
* values. Size is parsed but does not change the figure's current presentation.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
+
* All three are closed, and an unrecognised value is **refused with a located
|
|
30
31
|
* diagnostic** rather than ignored. Ignoring is the failure worth preventing:
|
|
31
32
|
* `{.fullwidth}` rendering as an ordinary image looks exactly like a directive
|
|
32
33
|
* that worked, so the author publishes a page that is not the one they asked
|
|
@@ -36,8 +37,7 @@
|
|
|
36
37
|
*
|
|
37
38
|
* **No pixel values.** A number means something in a browser and nothing
|
|
38
39
|
* coherent in print, and a directive carrying both a class and a dimension
|
|
39
|
-
* gives one question two answers with no rule for which wins.
|
|
40
|
-
* joins the vocabulary as a name.
|
|
40
|
+
* gives one question two answers with no rule for which wins.
|
|
41
41
|
*
|
|
42
42
|
* **Not `markdown-it-attrs`.** It is a general attribute injector: it will set
|
|
43
43
|
* `style`, `id` or `onclick`, and those reach emitted markup on the website and
|
|
@@ -105,6 +105,16 @@ export const IMAGE_CLASSES = Object.freeze({
|
|
|
105
105
|
},
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
/** The named sizes an image directive accepts without applying a display size. */
|
|
109
|
+
export const IMAGE_SIZES = Object.freeze([
|
|
110
|
+
"auto",
|
|
111
|
+
"small",
|
|
112
|
+
"medium",
|
|
113
|
+
"large",
|
|
114
|
+
"xlarge",
|
|
115
|
+
"full-width",
|
|
116
|
+
]);
|
|
117
|
+
|
|
108
118
|
/**
|
|
109
119
|
* The `float:` positions an image may take, and where each puts it.
|
|
110
120
|
*
|
|
@@ -213,16 +223,18 @@ export function imageSourceProblem(src) {
|
|
|
213
223
|
*
|
|
214
224
|
* Values are validated here rather than at the point of rendering, so a mistake
|
|
215
225
|
* is one finding with a position rather than three surfaces quietly drawing
|
|
216
|
-
* something else. A directive that holds a problem yields **no** class
|
|
226
|
+
* something else. A directive that holds a problem yields **no** class, size or
|
|
217
227
|
* float: a half-honoured directive is the silent failure in a smaller costume.
|
|
218
228
|
*
|
|
219
229
|
* @param {string} [raw] - The text between the braces, braces included or not.
|
|
220
|
-
* @returns {{classes: string[], float: string, problems: string[]}} What was
|
|
230
|
+
* @returns {{classes: string[], size: string, float: string, problems: string[]}} What was
|
|
221
231
|
* written, and what cannot be honoured.
|
|
222
232
|
*/
|
|
223
233
|
export function parseImageDirective(raw) {
|
|
224
234
|
/** @type {string[]} */
|
|
225
235
|
const classes = [];
|
|
236
|
+
let size = "auto";
|
|
237
|
+
let sizeSeen = false;
|
|
226
238
|
let float = "";
|
|
227
239
|
/** @type {string[]} */
|
|
228
240
|
const problems = [];
|
|
@@ -230,7 +242,7 @@ export function parseImageDirective(raw) {
|
|
|
230
242
|
const inner = String(raw ?? "")
|
|
231
243
|
.replace(/^\{/, "")
|
|
232
244
|
.replace(/\}$/, "");
|
|
233
|
-
if (!inner.trim()) return { classes, float, problems };
|
|
245
|
+
if (!inner.trim()) return { classes, size, float, problems };
|
|
234
246
|
|
|
235
247
|
// Split on commas, not whitespace: `float: top-left` is one pair with a
|
|
236
248
|
// space in it, and the space after the colon is the spelling people write.
|
|
@@ -259,17 +271,32 @@ export function parseImageDirective(raw) {
|
|
|
259
271
|
const colon = part.indexOf(":");
|
|
260
272
|
if (colon === -1) {
|
|
261
273
|
problems.push(
|
|
262
|
-
`\`${part}\` is neither a width class nor \`
|
|
263
|
-
"image states its width as a class and its position as `float:`, " +
|
|
274
|
+
`\`${part}\` is neither a width class nor \`size: <name>\` or ` +
|
|
275
|
+
"`float: <position>` — an image states its width as a class and its position as `float:`, " +
|
|
264
276
|
"and it states no dimensions at all",
|
|
265
277
|
);
|
|
266
278
|
continue;
|
|
267
279
|
}
|
|
268
280
|
const key = part.slice(0, colon).trim();
|
|
269
281
|
const value = part.slice(colon + 1).trim();
|
|
282
|
+
if (key === "size") {
|
|
283
|
+
if (!IMAGE_SIZES.includes(value)) {
|
|
284
|
+
problems.push(
|
|
285
|
+
`\`size: ${value}\` is not a size — the ones there are: ${IMAGE_SIZES.join(", ")}`,
|
|
286
|
+
);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (sizeSeen) {
|
|
290
|
+
problems.push("`size:` is written twice, and an image has one named size");
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
size = value;
|
|
294
|
+
sizeSeen = true;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
270
297
|
if (key !== "float") {
|
|
271
298
|
problems.push(
|
|
272
|
-
`\`${key}\` is not an image attribute — \`
|
|
299
|
+
`\`${key}\` is not an image attribute — use \`size\` or \`float\`, ` +
|
|
273
300
|
"and width is a class rather than an attribute",
|
|
274
301
|
);
|
|
275
302
|
continue;
|
|
@@ -292,8 +319,8 @@ export function parseImageDirective(raw) {
|
|
|
292
319
|
problems.push("an image states one width, and this states more than one");
|
|
293
320
|
}
|
|
294
321
|
// Nothing partial: a directive with a problem in it is not honoured at all.
|
|
295
|
-
if (problems.length) return { classes: [], float: "", problems };
|
|
296
|
-
return { classes, float, problems };
|
|
322
|
+
if (problems.length) return { classes: [], size: "auto", float: "", problems };
|
|
323
|
+
return { classes, size, float, problems };
|
|
297
324
|
}
|
|
298
325
|
|
|
299
326
|
/**
|
|
@@ -594,7 +621,7 @@ export function lintContentImages(contentBase, { skipDirectories = [], config }
|
|
|
594
621
|
* **Hugo is handed markdown, not a rendered page.** The site emitter writes a
|
|
595
622
|
* note's body through verbatim, so a `{…}` directive left in it reaches the
|
|
596
623
|
* page as its own literal braces: Goldmark's block-attribute parser is off, and
|
|
597
|
-
* turning it on would accept `style` and `id` alongside the
|
|
624
|
+
* turning it on would accept `style` and `id` alongside the closed vocabularies,
|
|
598
625
|
* which is the injection surface this rule exists to avoid. So the directive is
|
|
599
626
|
* resolved here, into markup Goldmark passes through.
|
|
600
627
|
*
|
|
@@ -68,7 +68,7 @@ import { positionInFrontmatter, positionOfFrontmatterPath } from "./diagnostics.
|
|
|
68
68
|
import { pathnameProblem } from "./pathnames.mjs";
|
|
69
69
|
import { checkHomepageAddressFields } from "./homepage.mjs";
|
|
70
70
|
import { RETIRED_TYPES, RENAMED_TYPES, currentType, renamedTypeMessage } from "./ids.mjs";
|
|
71
|
-
import { parseAddress, acceptsType } from "./address.mjs";
|
|
71
|
+
import { parseAddress, acceptsType, renderAddress } from "./address.mjs";
|
|
72
72
|
import { ASSET_TYPE_NAMES } from "./asset-types.mjs";
|
|
73
73
|
import { isAddressSegment } from "./address-charset.mjs";
|
|
74
74
|
// The one place the "every pack not named" key is spelled. Imported rather
|
|
@@ -421,7 +421,7 @@ function dataBlock(fm) {
|
|
|
421
421
|
* was loaded to recognise it would be worse than not checking.
|
|
422
422
|
* @returns {object[]} Findings.
|
|
423
423
|
*/
|
|
424
|
-
function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
424
|
+
function checkDataContainer(note, { type, fields, packs, addressContext, index }) {
|
|
425
425
|
const findings = [];
|
|
426
426
|
const { present, entries, malformed } = dataBlock(note.fm ?? {});
|
|
427
427
|
if (!present) return findings;
|
|
@@ -504,7 +504,7 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
504
504
|
});
|
|
505
505
|
continue;
|
|
506
506
|
}
|
|
507
|
-
findings.push(...checkDataReferences(note, field, value, segments, addressContext));
|
|
507
|
+
findings.push(...checkDataReferences(note, field, value, segments, addressContext, index));
|
|
508
508
|
// A `scalar-or-map` written in its map form is checked entry by entry,
|
|
509
509
|
// because that is the correction an author has to make: one key's
|
|
510
510
|
// value, not the whole map. Quoting the map back would name every
|
|
@@ -521,15 +521,16 @@ function checkDataContainer(note, { type, fields, packs, addressContext }) {
|
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
/**
|
|
524
|
-
* Validate typed entries
|
|
524
|
+
* Validate typed entries and resolve declared non-art Address targets.
|
|
525
525
|
* @param {object} note - The authored note.
|
|
526
526
|
* @param {object} field - Its data field declaration.
|
|
527
527
|
* @param {unknown} value - The field value.
|
|
528
528
|
* @param {string[]} segments - The field's path under `data`.
|
|
529
529
|
* @param {object} context - The builder's Address context.
|
|
530
|
+
* @param {object} [index] - The local and declared dependency Address index.
|
|
530
531
|
* @returns {object[]} Located findings.
|
|
531
532
|
*/
|
|
532
|
-
function checkDataReferences(note, field, value, segments, context) {
|
|
533
|
+
function checkDataReferences(note, field, value, segments, context, index) {
|
|
533
534
|
const checks = [];
|
|
534
535
|
if (field.kind === "address") checks.push({ value, path: [], kind: "address" });
|
|
535
536
|
if (field.entryKind) {
|
|
@@ -573,10 +574,16 @@ function checkDataReferences(note, field, value, segments, context) {
|
|
|
573
574
|
let reason;
|
|
574
575
|
if (!matchesKind(check.value, check.kind, defaults))
|
|
575
576
|
reason = `should be ${check.kind === "address" ? "an Address" : "a Shortcode"}`;
|
|
576
|
-
else if (check.kind === "address"
|
|
577
|
+
else if (check.kind === "address") {
|
|
577
578
|
const tuple = parseAddress(check.value, defaults, { declared: true });
|
|
578
|
-
if (!acceptsType(tuple, accepted))
|
|
579
|
+
if (accepted && !acceptsType(tuple, accepted))
|
|
579
580
|
reason = `accepts ${accepted.join(" or ")}, not ${tuple.type}`;
|
|
581
|
+
else if (
|
|
582
|
+
index?.addressHit &&
|
|
583
|
+
!ART_SLOTS.some((slot) => slot.key === field.name) &&
|
|
584
|
+
!index.addressHit(renderAddress(tuple))
|
|
585
|
+
)
|
|
586
|
+
reason = `names ${renderAddress(tuple)}, which does not resolve in this package or its declared dependencies`;
|
|
580
587
|
}
|
|
581
588
|
if (!reason) continue;
|
|
582
589
|
const path = ["data", ...segments, ...check.path];
|
|
@@ -1519,6 +1526,7 @@ export function lintNote(
|
|
|
1519
1526
|
fields,
|
|
1520
1527
|
packs,
|
|
1521
1528
|
addressContext: referenceContext,
|
|
1529
|
+
index,
|
|
1522
1530
|
}),
|
|
1523
1531
|
);
|
|
1524
1532
|
findings.push(...checkSubType(note, { type, entry }));
|
package/engine/holdings.mjs
CHANGED
|
@@ -124,15 +124,12 @@ export const HELD_SUBTYPES = Object.freeze(["settlement", "site", "structure"]);
|
|
|
124
124
|
* system, only the shortcode {@link module:engine/address} would resolve it
|
|
125
125
|
* to anyway.
|
|
126
126
|
*
|
|
127
|
-
* **The
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* ask an entry's package to agree with the citing note's, and a fetched
|
|
134
|
-
* entry's own `parents` or `domains` is written in *its* package while the
|
|
135
|
-
* blend it takes part in is *this* one's — see
|
|
127
|
+
* **The holdings graph has package-blind Shortcode identity.** `places` and
|
|
128
|
+
* `affiliations` merge local and fetched notes into one shortcode space; the
|
|
129
|
+
* first declaration wins. A fetched place attaches to a local parent with
|
|
130
|
+
* the same shortcode, and a fetched affiliation can name both local and
|
|
131
|
+
* fetched domains. The authored entries remain Addresses; this graph uses
|
|
132
|
+
* their shortcode projection for containment and tenure. See
|
|
136
133
|
* `tests/site-holdings.test.ts`'s `empire.domains: ["mill", "abroad"]`, where
|
|
137
134
|
* `mill` is local and `abroad` is the dependency's own, both bare, both
|
|
138
135
|
* correct only because neither package is asked.
|
|
@@ -321,6 +318,7 @@ export function holdingsPages(nodes) {
|
|
|
321
318
|
// Read through the one reader, so a caller handing in what a note
|
|
322
319
|
// wrote — an address, a wikilink, a repeat — is read as `holdingsNode`
|
|
323
320
|
// reads it.
|
|
321
|
+
// Shortcode identity: the first node of each type owns this graph key.
|
|
324
322
|
by.set(shortcode, {
|
|
325
323
|
...node,
|
|
326
324
|
shortcode,
|
package/engine/map-places.mjs
CHANGED
|
@@ -108,15 +108,14 @@ const PLACE_TYPES = Object.freeze(new Set(["place"]));
|
|
|
108
108
|
* since this position asks neither its package nor its system, only the
|
|
109
109
|
* shortcode {@link module:engine/address} would resolve it to anyway.
|
|
110
110
|
*
|
|
111
|
-
* **The
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
111
|
+
* **The map graph has package-blind Shortcode identity.** `world.places`
|
|
112
|
+
* merges local and fetched places by shortcode, with local declarations
|
|
113
|
+
* taking precedence. A fetched place attaches to the local containment tree
|
|
114
|
+
* by its parent's shortcode. The authored parent remains an Address; the map
|
|
115
|
+
* uses its shortcode projection for containment, layout, and DOT nodes. See
|
|
115
116
|
* `tests/map.test.ts`'s _loads a dependency's places beside the package's
|
|
116
117
|
* own_, where a fetched place's bare `parents: ["world"]` reaches this
|
|
117
|
-
* package's own root.
|
|
118
|
-
* dependency's own package to agree with this one's, which the feature is
|
|
119
|
-
* built to cross.
|
|
118
|
+
* package's own root.
|
|
120
119
|
*
|
|
121
120
|
* @param {unknown} parent - One `parents` entry.
|
|
122
121
|
* @returns {string} Its shortcode, or `""` for an entry that names nothing.
|
|
@@ -193,6 +192,7 @@ export function placesFromRecords(records, { contentBase, base }) {
|
|
|
193
192
|
// lint's finding.
|
|
194
193
|
if (places.has(shortcode)) continue;
|
|
195
194
|
const data = record.data && typeof record.data === "object" ? record.data : {};
|
|
195
|
+
// Shortcode identity: local places claim the map node first.
|
|
196
196
|
places.set(shortcode, {
|
|
197
197
|
shortcode,
|
|
198
198
|
name,
|
|
@@ -243,6 +243,7 @@ function addForeignPlaces(world, foreignIndex) {
|
|
|
243
243
|
if (entry?.type !== "place") continue;
|
|
244
244
|
const shortcode = readCanonicalKey(canonical)?.shortcode?.toLowerCase() ?? "";
|
|
245
245
|
if (!shortcode || world.places.has(shortcode)) continue;
|
|
246
|
+
// Shortcode identity: fetched places fill only unclaimed map nodes.
|
|
246
247
|
world.places.set(shortcode, {
|
|
247
248
|
shortcode,
|
|
248
249
|
name: String(entry.name ?? shortcode),
|
package/engine/populations.mjs
CHANGED
|
@@ -242,8 +242,10 @@ export function foreignNode(canonical, entry) {
|
|
|
242
242
|
/**
|
|
243
243
|
* The corpus a link index describes.
|
|
244
244
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
245
|
+
* Population containment and tenure use the same package-blind Shortcode
|
|
246
|
+
* identity as holdings and maps. Each type has one node per shortcode, with
|
|
247
|
+
* local notes taking precedence over fetched entries. A fetched place can
|
|
248
|
+
* attach to local geography, while its authored `parents` remains an Address.
|
|
247
249
|
*
|
|
248
250
|
* @param {object} index - The link index.
|
|
249
251
|
* @returns {Corpus} The corpus.
|
|
@@ -263,6 +265,7 @@ function corpusOf(index) {
|
|
|
263
265
|
if (!node) return;
|
|
264
266
|
const by = node.type === "place" ? places : affiliations;
|
|
265
267
|
if (by.has(node.shortcode)) return;
|
|
268
|
+
// Shortcode identity: the first node of each type owns this graph key.
|
|
266
269
|
by.set(node.shortcode, node);
|
|
267
270
|
// Not a canonical Address: package-blind by design, so a local place
|
|
268
271
|
// shadows a foreign one of the same type and shortcode. `/` keeps that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.12.1",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -16,15 +16,16 @@ export function imageSourceProblem(src: string): string;
|
|
|
16
16
|
*
|
|
17
17
|
* Values are validated here rather than at the point of rendering, so a mistake
|
|
18
18
|
* is one finding with a position rather than three surfaces quietly drawing
|
|
19
|
-
* something else. A directive that holds a problem yields **no** class
|
|
19
|
+
* something else. A directive that holds a problem yields **no** class, size or
|
|
20
20
|
* float: a half-honoured directive is the silent failure in a smaller costume.
|
|
21
21
|
*
|
|
22
22
|
* @param {string} [raw] - The text between the braces, braces included or not.
|
|
23
|
-
* @returns {{classes: string[], float: string, problems: string[]}} What was
|
|
23
|
+
* @returns {{classes: string[], size: string, float: string, problems: string[]}} What was
|
|
24
24
|
* written, and what cannot be honoured.
|
|
25
25
|
*/
|
|
26
26
|
export function parseImageDirective(raw?: string): {
|
|
27
27
|
classes: string[];
|
|
28
|
+
size: string;
|
|
28
29
|
float: string;
|
|
29
30
|
problems: string[];
|
|
30
31
|
};
|
|
@@ -182,7 +183,7 @@ export function lintContentImages(contentBase: string, { skipDirectories, config
|
|
|
182
183
|
* **Hugo is handed markdown, not a rendered page.** The site emitter writes a
|
|
183
184
|
* note's body through verbatim, so a `{…}` directive left in it reaches the
|
|
184
185
|
* page as its own literal braces: Goldmark's block-attribute parser is off, and
|
|
185
|
-
* turning it on would accept `style` and `id` alongside the
|
|
186
|
+
* turning it on would accept `style` and `id` alongside the closed vocabularies,
|
|
186
187
|
* which is the injection surface this rule exists to avoid. So the directive is
|
|
187
188
|
* resolved here, into markup Goldmark passes through.
|
|
188
189
|
*
|
|
@@ -248,6 +249,8 @@ export const IMAGE_CLASSES: Readonly<Record<string, {
|
|
|
248
249
|
scope: string;
|
|
249
250
|
describe: string;
|
|
250
251
|
}>>;
|
|
252
|
+
/** The named sizes an image directive accepts without applying a display size. */
|
|
253
|
+
export const IMAGE_SIZES: readonly string[];
|
|
251
254
|
/**
|
|
252
255
|
* The `float:` positions an image may take, and where each puts it.
|
|
253
256
|
*
|