scavold 0.2.0 → 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 +73 -1
- package/COMPONENTS.md +270 -0
- package/components/ScavoldCard.vue +51 -0
- package/components/ScavoldDetails.vue +23 -0
- package/components/ScavoldGrid.vue +53 -0
- package/components/ScavoldImage.vue +13 -4
- package/components/ScavoldMenuItems.vue +3 -1
- package/composables/hierarchy.ts +4 -1
- package/composables/useCard.js +45 -0
- package/composables/useDetails.js +43 -0
- package/composables/useGrid.js +40 -0
- package/composables/useImageSizes.js +42 -0
- package/icons/float-left.svg +1 -0
- package/icons/float-right.svg +1 -0
- package/icons/split-left.svg +1 -0
- package/icons/split-right.svg +1 -0
- package/icons/stacked.svg +1 -0
- package/l10n/de.json +6 -0
- package/l10n/en.json +6 -0
- package/lib/config.js +15 -22
- package/lib/containers.js +6 -0
- package/lib/figures.js +99 -0
- package/lib/icons.js +197 -0
- package/lib/index.d.ts +11 -1
- package/lib/index.js +10 -1
- package/lib/sectionManifest.js +488 -74
- package/package.json +4 -1
- package/scripts/build-icons.js +27 -0
- package/scripts/check-fixture.js +119 -4
- package/styles/layouts.css +64 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,75 @@ 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
|
+
|
|
73
|
+
## [0.2.1] — 2026-10-04
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- Menus and breadcrumbs no longer link a folder without an `index.md` — there is no page
|
|
78
|
+
to go to, and the link was dead. Such a folder is named as text above its pages where a
|
|
79
|
+
menu shows them, left out where a menu does not, and named as text in a breadcrumb.
|
|
80
|
+
|
|
12
81
|
## [0.2.0] — 2026-10-04
|
|
13
82
|
|
|
14
83
|
### Added
|
|
@@ -361,7 +430,10 @@ First published release. Version `0.1.0` existed in-tree only.
|
|
|
361
430
|
- Only images are processed out of `media_folder`; other file types (video, documents)
|
|
362
431
|
are never copied into the build output and have to live in the static folder.
|
|
363
432
|
|
|
364
|
-
[Unreleased]: https://gitlab.com/cratly/scavold/-/compare/v0.
|
|
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
|
|
436
|
+
[0.2.1]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0...v0.2.1
|
|
365
437
|
[0.2.0]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.12...v0.2.0
|
|
366
438
|
[0.2.0-rc.12]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.11...v0.2.0-rc.12
|
|
367
439
|
[0.2.0-rc.11]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.10...v0.2.0-rc.11
|
package/COMPONENTS.md
CHANGED
|
@@ -28,6 +28,9 @@ Each item in the trail carries `.active` (all ancestor items) and `.current` (th
|
|
|
28
28
|
last item, if the current page is included). This makes it straightforward to style
|
|
29
29
|
the current crumb differently in CSS.
|
|
30
30
|
|
|
31
|
+
A folder on the way that has no `index.md` is named as text, a `<span>` in place of the
|
|
32
|
+
`<a>`: it is part of where the page sits, but there is no page to go to.
|
|
33
|
+
|
|
31
34
|
#### Props
|
|
32
35
|
|
|
33
36
|
| Prop | Type | Default | Description |
|
|
@@ -66,6 +69,48 @@ the current crumb differently in CSS.
|
|
|
66
69
|
|
|
67
70
|
---
|
|
68
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
|
+

|
|
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
|
+
|
|
69
114
|
### `<ScavoldContainer>`
|
|
70
115
|
|
|
71
116
|
Fallback component for Markdown container blocks whose name has no dedicated
|
|
@@ -246,6 +291,94 @@ not promise.
|
|
|
246
291
|
|
|
247
292
|
---
|
|
248
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
|
+

|
|
335
|
+
|
|
336
|
+

|
|
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
|
+
|
|
249
382
|
### `<ScavoldImage>`
|
|
250
383
|
|
|
251
384
|
Renders a responsive `<picture>` element for local images. Not intended for direct
|
|
@@ -261,6 +394,7 @@ frontmatter field can use it directly.
|
|
|
261
394
|
| `srcset` | `string` | `""` | `srcset` string for the original format (JPEG/PNG) |
|
|
262
395
|
| `webp-srcset` | `string` | `""` | `srcset` string for the WebP variants |
|
|
263
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. |
|
|
264
398
|
| `alt` | `string` | `""` | Alt text for the `<img>` element |
|
|
265
399
|
| `loading` | `string` | `"lazy"` | `loading` attribute of the `<img>`; `"eager"` for an image at the top of a page |
|
|
266
400
|
| `fetchpriority` | `string` | — | `fetchpriority` attribute of the `<img>`, e.g. `"high"` for the largest image above the fold |
|
|
@@ -275,6 +409,30 @@ frontmatter field can use it directly.
|
|
|
275
409
|
</picture>
|
|
276
410
|
```
|
|
277
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
|
+

|
|
421
|
+

|
|
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
|
+
|
|
278
436
|
---
|
|
279
437
|
|
|
280
438
|
### `<ScavoldLayout>`
|
|
@@ -486,6 +644,10 @@ The `.active` class is set on any item that is an ancestor of the current page o
|
|
|
486
644
|
current page itself. The `.current` class is set only on the item that exactly matches
|
|
487
645
|
the current page.
|
|
488
646
|
|
|
647
|
+
A folder without an `index.md` has no page to link to. Where the menu shows the
|
|
648
|
+
pages below it, the folder is named as text — a `<span>` in place of the `<a>` — and
|
|
649
|
+
groups them; where it does not, the folder is left out, as it would lead nowhere.
|
|
650
|
+
|
|
489
651
|
#### Label resolution
|
|
490
652
|
|
|
491
653
|
Item labels are resolved in this order:
|
|
@@ -827,6 +989,35 @@ async function enhanceApp( context ) {
|
|
|
827
989
|
}
|
|
828
990
|
```
|
|
829
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
|
+

|
|
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
|
+
|
|
830
1021
|
---
|
|
831
1022
|
|
|
832
1023
|
## Composables
|
|
@@ -877,6 +1068,24 @@ Leave the `autoplay` attribute off the element; it would start the video regardl
|
|
|
877
1068
|
|
|
878
1069
|
---
|
|
879
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
|
+
|
|
880
1089
|
### `useContainer( props )`
|
|
881
1090
|
|
|
882
1091
|
```js
|
|
@@ -981,6 +1190,41 @@ are exported as well.
|
|
|
981
1190
|
|
|
982
1191
|
---
|
|
983
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
|
+
|
|
984
1228
|
### `useHierarchy()`
|
|
985
1229
|
|
|
986
1230
|
```js
|
|
@@ -1063,6 +1307,29 @@ const items = computed( () =>
|
|
|
1063
1307
|
|
|
1064
1308
|
---
|
|
1065
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
|
+
|
|
1066
1333
|
### `usePageList( props )`
|
|
1067
1334
|
|
|
1068
1335
|
```js
|
|
@@ -1316,6 +1583,9 @@ reserved), loses browser preload scanning (images load later), and produces a fl
|
|
|
1316
1583
|
of empty space on every navigation during hydration. The `sizes` + `srcset` approach
|
|
1317
1584
|
was specifically designed to avoid all of these.
|
|
1318
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
|
+
|
|
1319
1589
|
The global `image_sizes` in `.cratly.config.yaml` is a fallback for images that have
|
|
1320
1590
|
no layout-aware component wrapping them. Set it to the most common case in the
|
|
1321
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
|
-
|
|
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="
|
|
29
|
+
:sizes="effectiveSizes"
|
|
21
30
|
/>
|
|
22
31
|
<source
|
|
23
32
|
v-if="srcset"
|
|
24
33
|
:srcset="srcset"
|
|
25
|
-
: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="
|
|
40
|
+
:sizes="effectiveSizes"
|
|
32
41
|
:loading="loading"
|
|
33
42
|
:fetchpriority="fetchpriority"
|
|
34
43
|
decoding="async"
|
|
@@ -19,7 +19,9 @@ const { nodeLabel, nodeHref } = useHierarchy();
|
|
|
19
19
|
current: item.current,
|
|
20
20
|
}"
|
|
21
21
|
>
|
|
22
|
-
<a :href="nodeHref( item.node )" :aria-current="item.current ? 'page' : undefined">{{ nodeLabel( item.node ) }}</a>
|
|
22
|
+
<a v-if="item.node.isPage" :href="nodeHref( item.node )" :aria-current="item.current ? 'page' : undefined">{{ nodeLabel( item.node ) }}</a>
|
|
23
|
+
<!-- A folder without an index.md: there is no page to go to, only the ones below. -->
|
|
24
|
+
<span v-else>{{ nodeLabel( item.node ) }}</span>
|
|
23
25
|
<ScavoldMenuItems
|
|
24
26
|
v-if="item.children.length"
|
|
25
27
|
:items="item.children"
|
package/composables/hierarchy.ts
CHANGED
|
@@ -152,7 +152,10 @@ export function useHierarchy() {
|
|
|
152
152
|
? collectItems( node, remaining === -1 ? -1 : remaining - 1, activeOnly, expand )
|
|
153
153
|
: [],
|
|
154
154
|
};
|
|
155
|
-
} )
|
|
155
|
+
} )
|
|
156
|
+
// A folder without an index.md is no page to link to: it only groups the
|
|
157
|
+
// pages below it, and where those are not shown it leads nowhere.
|
|
158
|
+
.filter( item => item.node.isPage || item.children.length );
|
|
156
159
|
};
|
|
157
160
|
|
|
158
161
|
/**
|
|
@@ -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
|
+
}
|