@heroiclands/package-build 22.12.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 CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 22.12.0
4
12
 
5
13
  ### Minor Changes
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 two closed vocabularies: a width class, and a `float:` position.
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. | Read the directive on an image. |
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. |
@@ -1860,15 +1860,16 @@ immediately after the closing `]]`, with no space:
1860
1860
 
1861
1861
  ```markdown
1862
1862
  ![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
1863
+ ![[branwldrgr|Brànwâal Dôrgaar]]{size: medium, float: top-left}
1863
1864
  ![[thalornamap|Map of Thalorna]]{.full-width}
1864
1865
  ![[thalornamap|Map of Thalorna]]{.full-width, float: top-left}
1865
1866
  ![[thalornamap|Map of Thalorna]]{float: top-left, .full-width}
1866
1867
  ```
1867
1868
 
1868
- **The same parser reads both**, so an embed takes a width class, a `float:`, or
1869
- both together in either order, comma-separated — everything
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
1870
1871
  [an image's directive](#width-is-a-class-and-the-ordinary-width-carries-no-marker)
1871
- 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
1872
1873
  above, and they are closed here for the same reason: a directive that is quietly
1873
1874
  ignored looks exactly like one that worked, and a directive holding a problem is
1874
1875
  not honoured in part either.
@@ -2123,6 +2124,7 @@ part of this format follows.
2123
2124
 
2124
2125
  ```markdown
2125
2126
  ![[branwldrgr|Brànwâal Dôrgaar]]{float: top-left}
2127
+ ![[branwldrgr|Brànwâal Dôrgaar]]{size: medium, float: top-left}
2126
2128
 
2127
2129
  ![Map of Thalorna](images/maps/thalorna.webp){.full-width}
2128
2130
  ```
@@ -2146,13 +2148,30 @@ place for those words and this is it.
2146
2148
  | `.full-width` | the full page | full content width | full page width |
2147
2149
 
2148
2150
  The simple case needs no spelling, which is why the ordinary width has no name.
2149
- A third width joins this table as a name.
2150
2151
 
2151
2152
  **No pixel values, ever.** A number means something in a browser and nothing
2152
2153
  coherent in print, and a directive carrying a class and a dimension at once
2153
2154
  gives one question two answers with no rule for which wins. `{width=800}` is
2154
2155
  refused.
2155
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
+
2156
2175
  ##### Position is `float:`
2157
2176
 
2158
2177
  | `float` value | book | website and Foundry journal |
@@ -2182,7 +2201,7 @@ A `.full-width` image that **also states a `float:`** is asking for a float and
2182
2201
  gets one, with no break before it. A float is placed where it fits, which may be
2183
2202
  the next page.
2184
2203
 
2185
- ##### Both vocabularies are closed
2204
+ ##### All three vocabularies are closed
2186
2205
 
2187
2206
  `{.fullwidth}`, `{.full_width}`, `{width=800}` and `{float: middle}` are
2188
2207
  refused, located by file, line and column, and the build fails. An unrecognised
@@ -2194,8 +2213,8 @@ even in the part that parsed: a half-honoured directive is the same silent
2194
2213
  failure in a smaller costume. The image keeps its braces and renders them as
2195
2214
  literal text, so the mistake is visible on the page as well as in the log.
2196
2215
 
2197
- **Nothing else reaches emitted markup.** The two vocabularies and the address
2198
- 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
2199
2218
  attribute, because data is never compiled into markup here. An address carries
2200
2219
  a scheme of `http:` or `https:`, or none at all.
2201
2220
 
@@ -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
  * ![Brànwâal Dôrgaar](images/beings/branwldrgr-portrait.webp){float: top-left}
20
20
  * ![Map of Thalorna](images/map.webp){.full-width}
21
21
  *
22
- * ## Two closed vocabularies, and closed is the point
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
- * Both are closed, and an unrecognised value is **refused with a located
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. A third width
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 and no
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 \`float: <position>\` — an ` +
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 — \`float\` is the only one, ` +
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 two vocabularies,
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
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.12.0",
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 and no
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 two vocabularies,
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
  *