scavold 0.2.1 → 0.4.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 CHANGED
@@ -9,6 +9,67 @@ one offered; a patch release does not.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.4.0] — 2026-10-04
13
+
14
+ ### Added
15
+
16
+ - Layout variants for `section`, `article` and `aside`: `layout=float-left` or
17
+ `float-right` lets the content flow around the first block, an image usually;
18
+ `split-left` or `split-right` sets block and content side by side. The editor offers
19
+ them as buttons with icons Scavold brings along, drawn after Font Awesome's solid
20
+ style; a site replaces one by keeping an icon of the same name in `.cratly/icons/`.
21
+ - The section kinds Scavold brings are described to the editor in English and German —
22
+ their names, hints and the choices of their options — so a German editor no longer
23
+ reads "Preload" or "metadata". A site's `.cratly.config.yaml` may give labels and
24
+ hints in several languages the same way, as a map of locale → text.
25
+ - Each section kind Scavold brings explains itself where the editor offers the types to
26
+ choose from, and links its description on a new page for authors at scavold.io — the
27
+ first of the documentation in German as well as English. A site's own containers do
28
+ the same with `hint` and `docs` in `.cratly.config.yaml`.
29
+
30
+ ### Changed
31
+
32
+ - The section-type manifest carries the texts of the built-in section kinds as maps of
33
+ locale → text, and their `docs` as maps of locale → address. A cratly editor deployed
34
+ before these were supported shows such a text as "[object Object]": update the editor
35
+ before a site moves to this version.
36
+
37
+ ## [0.3.0] — 2026-10-04
38
+
39
+ ### Added
40
+
41
+ - A `card` container: a unit of its own, such as an image, a heading and a few lines.
42
+ `link` names a page and makes the whole card clickable, its "Read more" naming the
43
+ page for screen readers; links written into the card stay clickable.
44
+ - A `details` container: content collapsed under a title (`summary`) until the reader
45
+ expands it, rendered as a native `<details>`. `open` starts it expanded, and blocks
46
+ sharing a `group` close each other, so only one of them is open at a time.
47
+ - A `grid` container: the blocks written inside it side by side, at most `columns` (2,
48
+ 3 or 4) in a row, continuing in the next. A cell never gets narrower than
49
+ `--scavold-grid-min`, so a narrow screen drops columns by itself without breakpoints.
50
+ A theme declares the `sizes` of images in a grid by column count through
51
+ `enhanceApp( context, { gridImageSizes } )`.
52
+ - `provideImageSizes()`: a layout component gives the images inside it a `sizes`
53
+ value, replacing the site's default but not a per-image override.
54
+ - Image captions. The title of a Markdown image is its caption: an image standing alone
55
+ in its paragraph becomes a `<figure>` with a `<figcaption>`, one amid text shows it as
56
+ its tooltip. A grid of captioned images makes a gallery.
57
+ - Icons for section controls. A `boolean` prop and the choices of an `enum` prop in
58
+ `.cratly.config.yaml` may name an `icon`, kept as `.cratly/icons/<name>.svg` in the
59
+ site. The section-type manifest embeds the icons it refers to, so the cratly editor
60
+ can offer a toggle button or a group of buttons instead of a checkbox or a select. A
61
+ reference to a missing icon is reported during the build.
62
+
63
+ ### Changed
64
+
65
+ - The title of a Markdown image is shown: below an image standing alone, as its tooltip
66
+ amid text. A site that used image titles for something else will see them now.
67
+
68
+ ### Fixed
69
+
70
+ - A `sizes=` override in an image title no longer shows up as a tooltip
71
+ ("sizes=100vw") on the published image.
72
+
12
73
  ## [0.2.1] — 2026-10-04
13
74
 
14
75
  ### Fixed
@@ -369,7 +430,9 @@ First published release. Version `0.1.0` existed in-tree only.
369
430
  - Only images are processed out of `media_folder`; other file types (video, documents)
370
431
  are never copied into the build output and have to live in the static folder.
371
432
 
372
- [Unreleased]: https://gitlab.com/cratly/scavold/-/compare/v0.2.1...main
433
+ [Unreleased]: https://gitlab.com/cratly/scavold/-/compare/v0.4.0...main
434
+ [0.4.0]: https://gitlab.com/cratly/scavold/-/compare/v0.3.0...v0.4.0
435
+ [0.3.0]: https://gitlab.com/cratly/scavold/-/compare/v0.2.1...v0.3.0
373
436
  [0.2.1]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0...v0.2.1
374
437
  [0.2.0]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.12...v0.2.0
375
438
  [0.2.0-rc.12]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.11...v0.2.0-rc.12
package/COMPONENTS.md CHANGED
@@ -69,6 +69,48 @@ A folder on the way that has no `index.md` is named as text, a `<span>` in place
69
69
 
70
70
  ---
71
71
 
72
+ ### `<ScavoldCard>`
73
+
74
+ A unit of its own — an image, a heading and a few lines, say — set apart from the text
75
+ around it. Cards side by side go into a [grid](#scavoldgrid):
76
+
77
+ ```markdown
78
+ :::: grid columns=3
79
+ ::: card link=about/team.md
80
+ ![Our team at the pond](/team.jpg)
81
+
82
+ ### Who we are
83
+
84
+ Twelve people, one pond.
85
+ :::
86
+ ::::
87
+ ```
88
+
89
+ | Argument | Type | Description |
90
+ |---|---|---|
91
+ | `link` | `page-ref` | Page the card links to. Makes the whole card clickable; a reference to a page that does not exist leaves the card unlinked. |
92
+
93
+ ```html
94
+ <div class="scavold-card scavold-card--linked">
95
+ …
96
+ <a class="scavold-card__link" href="/about/team.html" aria-label="Read more: Team">Read more</a>
97
+ </div>
98
+ ```
99
+
100
+ The link's text is the same on every card, translated; its accessible name adds the
101
+ title of the page it leads to, so a list of links read out by a screen reader tells the
102
+ cards apart. Its click area is stretched over the whole card, while links written into
103
+ the card stay clickable on top.
104
+
105
+ Scavold styles only what keeps a card working: images stay within it, and the link
106
+ covers it. Its frame, padding and where its image sits are the theme's, through
107
+ `scavold-card` and `scavold-card--linked`.
108
+
109
+ Register a component under the name `ScavoldCard` before calling `scavoldEnhanceApp` to
110
+ render it differently, and resolve the arguments with [`useCard`](#usecard-props).
111
+
112
+ ---
113
+
72
114
  ### `<ScavoldContainer>`
73
115
 
74
116
  Fallback component for Markdown container blocks whose name has no dedicated
@@ -249,6 +291,94 @@ not promise.
249
291
 
250
292
  ---
251
293
 
294
+ ### `<ScavoldDetails>`
295
+
296
+ Content collapsed under a title until the reader expands it. Written as a container
297
+ block and rendered as a native `<details>` element, so keyboard operation, the expanded
298
+ state for screen readers and revealing the content on find in page come with it.
299
+
300
+ ```markdown
301
+ ::: details summary="Opening hours" group=faq
302
+ Monday to Friday, 9 am to 5 pm.
303
+ :::
304
+ ```
305
+
306
+ | Argument | Type | Description |
307
+ |---|---|---|
308
+ | `summary` | `string` | Title, always visible; clicking it expands or collapses the content. Left out, a translated "Details" stands in. |
309
+ | `open` | flag | Starts expanded. |
310
+ | `group` | `string` | Blocks with the same group close each other, so only one is expanded at a time. Rendered as the `name` attribute of `<details>`. |
311
+
312
+ ```html
313
+ <details class="scavold-details" name="faq">
314
+ <summary class="scavold-details__summary">Opening hours</summary>
315
+ <div class="scavold-details__content">…</div>
316
+ </details>
317
+ ```
318
+
319
+ Scavold adds no styles of its own; the browser's disclosure marker stays until a theme
320
+ replaces it. Register a component under the name `ScavoldDetails` before calling
321
+ `scavoldEnhanceApp` to render it differently, and resolve the arguments with
322
+ [`useDetails`](#usedetails-props).
323
+
324
+ ---
325
+
326
+ ### `<ScavoldGrid>`
327
+
328
+ Puts the blocks written inside it side by side, at most `columns` of them in a row; a
329
+ full row continues in the next. Each block is one cell — a paragraph, an image, or an
330
+ embedded container, which is how several blocks share a cell:
331
+
332
+ ```markdown
333
+ :::: grid columns=3
334
+ ![Pond in spring](/pond.jpg)
335
+
336
+ ![Meadow](/meadow.jpg)
337
+
338
+ ::: section
339
+ ### Opening hours
340
+ Monday to Friday.
341
+ :::
342
+ ::::
343
+ ```
344
+
345
+ The grid needs the longer fence when it contains a container; the cratly editor writes
346
+ it that way by itself.
347
+
348
+ | Argument | Type | Description |
349
+ |---|---|---|
350
+ | `columns` | `2` \| `3` \| `4` | Most cells side by side. Defaults to `2`. |
351
+
352
+ ```html
353
+ <div class="scavold-grid scavold-grid--3" style="--scavold-grid-columns: 3">…</div>
354
+ ```
355
+
356
+ A cell never gets narrower than `--scavold-grid-min` (default `14rem`), so on a narrow
357
+ screen the grid drops columns by itself, down to one, without breakpoints. Themes set
358
+ that variable and `--scavold-grid-gap` (default `1.5rem`), or replace the layout
359
+ through the `scavold-grid--<columns>` class. Margins of the cells themselves —
360
+ including the indent browsers give a `<figure>` — are removed; the gap spaces them.
361
+
362
+ Images in a grid take the `sizes` the theme declares for its column count, since only
363
+ the theme knows how wide a cell is at which screen width. A column count it leaves out
364
+ keeps the site's `image_sizes` of `.cratly.config.yaml` for its images: sharp in every column, but on a wide screen
365
+ a larger variant than a cell needs. An image with a `sizes=` override of its own keeps
366
+ it, and images nested deeper — in a card in a cell — take the grid's value as well.
367
+
368
+ ```js
369
+ // .vitepress/theme/index.js — cells 22rem wide in a 72rem column, two columns from 48rem
370
+ await scavoldEnhanceApp( context, {
371
+ gridImageSizes: {
372
+ 3: "(min-width: 72rem) 22rem, (min-width: 48rem) 50vw, 100vw",
373
+ },
374
+ } );
375
+ ```
376
+
377
+ Register a component under the name `ScavoldGrid` before calling `scavoldEnhanceApp`
378
+ to render it differently, and resolve the arguments with [`useGrid`](#usegrid-props).
379
+
380
+ ---
381
+
252
382
  ### `<ScavoldImage>`
253
383
 
254
384
  Renders a responsive `<picture>` element for local images. Not intended for direct
@@ -264,6 +394,7 @@ frontmatter field can use it directly.
264
394
  | `srcset` | `string` | `""` | `srcset` string for the original format (JPEG/PNG) |
265
395
  | `webp-srcset` | `string` | `""` | `srcset` string for the WebP variants |
266
396
  | `sizes` | `string` | `"100vw"` | CSS `sizes` attribute applied to all sources |
397
+ | `sizes-fallback` | `boolean` | `false` | Marks `sizes` as the site's default only, which a layout around the image may replace (see [`provideImageSizes`](#useimagesizes--provideimagesizes-sizes)). Set by the Markdown renderer. |
267
398
  | `alt` | `string` | `""` | Alt text for the `<img>` element |
268
399
  | `loading` | `string` | `"lazy"` | `loading` attribute of the `<img>`; `"eager"` for an image at the top of a page |
269
400
  | `fetchpriority` | `string` | — | `fetchpriority` attribute of the `<img>`, e.g. `"high"` for the largest image above the fold |
@@ -278,6 +409,30 @@ frontmatter field can use it directly.
278
409
  </picture>
279
410
  ```
280
411
 
412
+ #### Captions
413
+
414
+ The title of a Markdown image is its caption. An image standing alone in its paragraph
415
+ is rendered as a figure with the caption below it; amid text, where no figure fits, the
416
+ caption is the image's tooltip. A `sizes=` override in the title is taken out first and
417
+ never reaches the page:
418
+
419
+ ```markdown
420
+ ![Pond with water lilies](/pond.jpg "The pond in spring")
421
+ ![Pond with water lilies](/pond.jpg "sizes=100vw The pond in spring")
422
+ ```
423
+
424
+ ```html
425
+ <figure class="scavold-figure">
426
+ <picture>…</picture>
427
+ <figcaption class="scavold-figure__caption">The pond in spring</figcaption>
428
+ </figure>
429
+ ```
430
+
431
+ The alt text says what the image shows, for those who cannot see it; the caption adds
432
+ to it for everyone. Scavold adds no styles to a figure, so the indent browsers give
433
+ `<figure>` stays until a theme sets its own margins. A [grid](#scavoldgrid) of
434
+ captioned images is a gallery.
435
+
281
436
  ---
282
437
 
283
438
  ### `<ScavoldLayout>`
@@ -834,6 +989,35 @@ async function enhanceApp( context ) {
834
989
  }
835
990
  ```
836
991
 
992
+ #### Layout
993
+
994
+ `section`, `article` and `aside` take a `layout` arranging their first block — an image,
995
+ usually — and the content after it. Left out, the block stands above the content.
996
+
997
+ ```markdown
998
+ ::: section layout=float-left
999
+ ![The pond in spring](/pond.jpg)
1000
+
1001
+ Text flowing around the image, and on below it.
1002
+ :::
1003
+ ```
1004
+
1005
+ | `layout` | Arrangement |
1006
+ |---|---|
1007
+ | `float-left`, `float-right` | The content flows around the block and on below it. |
1008
+ | `split-left`, `split-right` | Block and content side by side; the content does not run below the block. |
1009
+
1010
+ The element carries the choice as `data-layout`, and `scavold/styles/layouts.css`, which
1011
+ `enhanceApp` brings along, implements it. A section narrower than 30rem stacks its block
1012
+ above the content again, whatever its layout. Themes set the block's width through
1013
+ `--scavold-layout-width` (default `40%`) and the space beside and below it through
1014
+ `--scavold-layout-gap` (default `1.5rem`), or replace the rules.
1015
+
1016
+ The cratly editor offers the choices as buttons with Scavold's own icons, drawn after
1017
+ Font Awesome's solid style. A site replaces one by keeping an icon of the same name —
1018
+ `stacked`, `float-left`, `float-right`, `split-left`, `split-right` — in its
1019
+ `.cratly/icons/`.
1020
+
837
1021
  ---
838
1022
 
839
1023
  ## Composables
@@ -884,6 +1068,24 @@ Leave the `autoplay` attribute off the element; it would start the video regardl
884
1068
 
885
1069
  ---
886
1070
 
1071
+ ### `useCard( props )`
1072
+
1073
+ ```js
1074
+ import { useCard, cardProps } from "scavold/composables/useCard.js";
1075
+ ```
1076
+
1077
+ Resolves the arguments of a `:::card` block for a site's own card component:
1078
+
1079
+ ```js
1080
+ const props = defineProps( { ...cardProps } );
1081
+ const { href, title } = useCard( props );
1082
+ ```
1083
+
1084
+ `href` is the URL of the linked page and `title` its title, both empty when the card
1085
+ links nowhere or to a page that does not exist.
1086
+
1087
+ ---
1088
+
887
1089
  ### `useContainer( props )`
888
1090
 
889
1091
  ```js
@@ -988,6 +1190,41 @@ are exported as well.
988
1190
 
989
1191
  ---
990
1192
 
1193
+ ### `useDetails( props )`
1194
+
1195
+ ```js
1196
+ import { useDetails, detailsProps } from "scavold/composables/useDetails.js";
1197
+ ```
1198
+
1199
+ Resolves the arguments of a `:::details` block for a site's own details component:
1200
+
1201
+ ```js
1202
+ const props = defineProps( { ...detailsProps } );
1203
+ const { summary, open, group } = useDetails( props );
1204
+ ```
1205
+
1206
+ `summary` is the trimmed title or an empty string, `open` a boolean, and `group` the
1207
+ group name or `undefined`.
1208
+
1209
+ ---
1210
+
1211
+ ### `useGrid( props )`
1212
+
1213
+ ```js
1214
+ import { useGrid, gridProps, GRID_COLUMNS } from "scavold/composables/useGrid.js";
1215
+ ```
1216
+
1217
+ Resolves the arguments of a `:::grid` block for a site's own grid component:
1218
+
1219
+ ```js
1220
+ const props = defineProps( { ...gridProps } );
1221
+ const { columns } = useGrid( props );
1222
+ ```
1223
+
1224
+ `columns` is one of `GRID_COLUMNS` (`2`, `3`, `4`), falling back to the first.
1225
+
1226
+ ---
1227
+
991
1228
  ### `useHierarchy()`
992
1229
 
993
1230
  ```js
@@ -1070,6 +1307,29 @@ const items = computed( () =>
1070
1307
 
1071
1308
  ---
1072
1309
 
1310
+ ### `useImageSizes()` / `provideImageSizes( sizes )`
1311
+
1312
+ ```js
1313
+ import { provideImageSizes, useImageSizes } from "scavold/composables/useImageSizes.js";
1314
+ ```
1315
+
1316
+ A layout component that knows how wide it draws its content provides a `sizes` value
1317
+ for the images inside it — a string or a ref:
1318
+
1319
+ ```js
1320
+ // SiteSidebar.vue — content 18rem wide from 64rem, full width below
1321
+ provideImageSizes( "(min-width: 64rem) 18rem, 100vw" );
1322
+ ```
1323
+
1324
+ Every `<ScavoldImage>` inside that carries only the site's default takes this value
1325
+ instead; one with a `sizes=` override, or given its `sizes` by a component like the
1326
+ cover, keeps its own. An empty value leaves the images with what the layout around
1327
+ provides. The value is resolved while rendering, so it is in the markup the server
1328
+ sends. `useImageSizes()` reads it, for a component rendering images of its own;
1329
+ `<ScavoldGrid>` provides one from `gridImageSizes`.
1330
+
1331
+ ---
1332
+
1073
1333
  ### `usePageList( props )`
1074
1334
 
1075
1335
  ```js
@@ -1323,6 +1583,9 @@ reserved), loses browser preload scanning (images load later), and produces a fl
1323
1583
  of empty space on every navigation during hydration. The `sizes` + `srcset` approach
1324
1584
  was specifically designed to avoid all of these.
1325
1585
 
1586
+ A layout wrapping Markdown content rather than rendering images itself — a sidebar, a
1587
+ grid — reaches the images inside through [`provideImageSizes`](#useimagesizes--provideimagesizes-sizes).
1588
+
1326
1589
  The global `image_sizes` in `.cratly.config.yaml` is a fallback for images that have
1327
1590
  no layout-aware component wrapping them. Set it to the most common case in the
1328
1591
  theme (typically the prose column width) and override via dedicated components for
@@ -0,0 +1,51 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useCard, cardProps } from "../composables/useCard.js";
4
+ import { useScavoldI18n } from "../composables/useI18n.js";
5
+
6
+ const props = defineProps( cardProps );
7
+
8
+ const { href, title } = useCard( props );
9
+
10
+ const { t } = useScavoldI18n();
11
+ const more = t( "card.more" );
12
+
13
+ // The visible text is the same on every card; the accessible name says where each
14
+ // one leads, starting with the visible text as WCAG 2.5.3 asks.
15
+ const linkLabel = computed( () => ( title.value ? `${more.value}: ${title.value}` : more.value ) );
16
+ </script>
17
+
18
+ <template>
19
+ <div class="scavold-card" :class="[ props.class, { 'scavold-card--linked': href } ]">
20
+ <slot />
21
+ <a v-if="href" class="scavold-card__link" :href="href" :aria-label="linkLabel">{{ more }}</a>
22
+ </div>
23
+ </template>
24
+
25
+ <style scoped>
26
+ /*
27
+ * Functional only: how a card looks — its frame, its padding, where its image sits —
28
+ * is the theme's. What is set here keeps it working: images stay within the card,
29
+ * and the link of a linked card covers all of it, so the whole card can be clicked.
30
+ * Links written into the card stay clickable on top of that.
31
+ */
32
+ .scavold-card :deep(img) {
33
+ max-width: 100%;
34
+ height: auto;
35
+ }
36
+
37
+ .scavold-card--linked {
38
+ position: relative;
39
+ }
40
+
41
+ .scavold-card__link::after {
42
+ content: "";
43
+ position: absolute;
44
+ inset: 0;
45
+ }
46
+
47
+ .scavold-card--linked :deep(a:not(.scavold-card__link)) {
48
+ position: relative;
49
+ z-index: 1;
50
+ }
51
+ </style>
@@ -0,0 +1,23 @@
1
+ <script setup>
2
+ import { useDetails, detailsProps } from "../composables/useDetails.js";
3
+ import { useScavoldI18n } from "../composables/useI18n.js";
4
+
5
+ const props = defineProps( detailsProps );
6
+
7
+ const { summary, open, group } = useDetails( props );
8
+
9
+ const { t } = useScavoldI18n();
10
+ const fallbackSummary = t( "details.summary" );
11
+ </script>
12
+
13
+ <template>
14
+ <!-- Native <details>: keyboard operation, the expanded state for screen readers and
15
+ revealing the content on find in page come with the element. `name` makes
16
+ blocks sharing a group close each other. -->
17
+ <details class="scavold-details" :class="props.class" :open="open" :name="group">
18
+ <summary class="scavold-details__summary">{{ summary || fallbackSummary }}</summary>
19
+ <div class="scavold-details__content">
20
+ <slot />
21
+ </div>
22
+ </details>
23
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup>
2
+ import { computed, inject } from "vue";
3
+ import { useGrid, gridProps } from "../composables/useGrid.js";
4
+ import { GRID_IMAGE_SIZES, provideImageSizes } from "../composables/useImageSizes.js";
5
+
6
+ const props = defineProps( gridProps );
7
+
8
+ const { columns } = useGrid( props );
9
+
10
+ // The theme knows how wide a cell is at which screen width; without a value for this
11
+ // column count, images keep what applies around the grid.
12
+ const imageSizes = inject( GRID_IMAGE_SIZES, {} );
13
+ provideImageSizes( computed( () => imageSizes[columns.value] ?? "" ) );
14
+
15
+ const style = computed( () => ( { "--scavold-grid-columns": columns.value } ) );
16
+ </script>
17
+
18
+ <template>
19
+ <div class="scavold-grid" :class="[ props.class, `scavold-grid--${columns}` ]" :style="style">
20
+ <slot />
21
+ </div>
22
+ </template>
23
+
24
+ <style scoped>
25
+ /*
26
+ * Functional layout: every block written in the grid is a cell, at most
27
+ * --scavold-grid-columns of them side by side. A cell never gets narrower than
28
+ * --scavold-grid-min, so on a narrow screen the grid drops columns by itself,
29
+ * down to one, without breakpoints. Themes adjust both variables and the gap, or
30
+ * replace the rule entirely.
31
+ */
32
+ .scavold-grid {
33
+ --_gap: var(--scavold-grid-gap, 1.5rem);
34
+ --_min: var(--scavold-grid-min, 14rem);
35
+ --_fraction: calc((100% - (var(--scavold-grid-columns) - 1) * var(--_gap)) / var(--scavold-grid-columns));
36
+
37
+ display: grid;
38
+ gap: var(--_gap);
39
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, max(var(--_min), var(--_fraction))), 1fr));
40
+ }
41
+
42
+ /* The gap spaces the cells; margins of their own would only put them out of line. */
43
+ .scavold-grid > :slotted(*) {
44
+ min-width: 0;
45
+ margin: 0;
46
+ }
47
+
48
+ /* An image never reaches into the next cell, whatever the theme does elsewhere. */
49
+ .scavold-grid :deep(img) {
50
+ max-width: 100%;
51
+ height: auto;
52
+ }
53
+ </style>
@@ -1,14 +1,23 @@
1
1
  <script setup>
2
- defineProps( {
2
+ import { computed } from "vue";
3
+ import { useImageSizes } from "../composables/useImageSizes.js";
4
+
5
+ const props = defineProps( {
3
6
  src: { type: String, required: true },
4
7
  srcset: { type: String, default: "" },
5
8
  webpSrcset: { type: String, default: "" },
6
9
  sizes: { type: String, default: "100vw" },
10
+ // Set by the Markdown renderer when `sizes` is only the site's default, which a
11
+ // layout around the image may replace with a better fit (see useImageSizes).
12
+ sizesFallback: { type: Boolean, default: false },
7
13
  alt: { type: String, default: "" },
8
14
  // An image above the fold — a cover — is wanted at once, not when it scrolls into view.
9
15
  loading: { type: String, default: "lazy" },
10
16
  fetchpriority: { type: String, default: undefined },
11
17
  } );
18
+
19
+ const provided = useImageSizes();
20
+ const effectiveSizes = computed( () => ( props.sizesFallback && provided.value ) || props.sizes );
12
21
  </script>
13
22
 
14
23
  <template>
@@ -17,18 +26,18 @@ defineProps( {
17
26
  v-if="webpSrcset"
18
27
  type="image/webp"
19
28
  :srcset="webpSrcset"
20
- :sizes="sizes"
29
+ :sizes="effectiveSizes"
21
30
  />
22
31
  <source
23
32
  v-if="srcset"
24
33
  :srcset="srcset"
25
- :sizes="sizes"
34
+ :sizes="effectiveSizes"
26
35
  />
27
36
  <img
28
37
  :src="src"
29
38
  :alt="alt"
30
39
  :role="alt ? undefined : 'presentation'"
31
- :sizes="sizes"
40
+ :sizes="effectiveSizes"
32
41
  :loading="loading"
33
42
  :fetchpriority="fetchpriority"
34
43
  decoding="async"
@@ -0,0 +1,45 @@
1
+ import { computed } from "vue";
2
+ import { containerProps } from "./useContainer.js";
3
+ import { useHierarchy } from "./hierarchy";
4
+
5
+ /**
6
+ * Props definition for card container components. Spread into `defineProps`
7
+ * alongside any component-specific props.
8
+ */
9
+ export const cardProps = {
10
+ ...containerProps,
11
+
12
+ /** Page the card links to, as a path in the pages folder. */
13
+ dataLink: { type: String, default: null },
14
+ };
15
+
16
+ /**
17
+ * Composable for card container components.
18
+ *
19
+ * Parses the props emitted by the markdown-it container renderer for a
20
+ * `:::card` block and exposes derived values ready for binding:
21
+ *
22
+ * - `href` — URL of the linked page, or an empty string when the card links
23
+ * nowhere or to a page that does not exist
24
+ * - `title` — title of the linked page, or an empty string
25
+ *
26
+ * Custom card components should call this composable so they share identical
27
+ * attribute resolution without duplicating the logic.
28
+ *
29
+ * @param {object} props reactive props from defineProps
30
+ * @returns {{ href, title }}
31
+ */
32
+ export function useCard( props ) {
33
+ const { resolveByPath, nodeHref, nodeTitle } = useHierarchy();
34
+
35
+ const target = computed( () => {
36
+ const path = props.dataLink?.trim();
37
+
38
+ return path ? resolveByPath( path ) : undefined;
39
+ } );
40
+
41
+ const href = computed( () => ( target.value ? nodeHref( target.value ) : "" ) );
42
+ const title = computed( () => ( target.value ? nodeTitle( target.value ) : "" ) );
43
+
44
+ return { href, title };
45
+ }
@@ -0,0 +1,43 @@
1
+ import { computed } from "vue";
2
+ import { containerProps } from "./useContainer.js";
3
+
4
+ /**
5
+ * Props definition for details container components. Spread into `defineProps`
6
+ * alongside any component-specific props.
7
+ */
8
+ export const detailsProps = {
9
+ ...containerProps,
10
+
11
+ /** Title shown while the content is collapsed, and clicked to expand it. */
12
+ dataSummary: { type: String, default: null },
13
+
14
+ /** Start expanded. */
15
+ dataOpen: { type: String, default: null },
16
+
17
+ /** Name shared by details blocks of which only one at a time may be expanded. */
18
+ dataGroup: { type: String, default: null },
19
+ };
20
+
21
+ /**
22
+ * Composable for details container components.
23
+ *
24
+ * Parses the props emitted by the markdown-it container renderer for a
25
+ * `:::details` block and exposes derived values ready for binding:
26
+ *
27
+ * - `summary` — title of the block, or an empty string when none is given
28
+ * - `open` — boolean, whether the block starts expanded
29
+ * - `group` — name of the group the block belongs to, or undefined
30
+ *
31
+ * Custom details components should call this composable so they share identical
32
+ * attribute resolution without duplicating the logic.
33
+ *
34
+ * @param {object} props reactive props from defineProps
35
+ * @returns {{ summary, open, group }}
36
+ */
37
+ export function useDetails( props ) {
38
+ const summary = computed( () => ( props.dataSummary ?? "" ).trim() );
39
+ const open = computed( () => props.dataOpen != null );
40
+ const group = computed( () => props.dataGroup?.trim() || undefined );
41
+
42
+ return { summary, open, group };
43
+ }