scavold 0.2.0-rc.9 → 0.2.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 +79 -3
- package/COMPONENTS.md +629 -429
- package/FRONTMATTER.md +30 -0
- package/components/ScavoldCover.vue +162 -0
- package/components/ScavoldImage.vue +5 -1
- package/components/ScavoldMenu.vue +20 -1
- package/components/ScavoldVideo.vue +12 -53
- package/composables/hierarchy.ts +72 -1
- package/composables/useAutoplay.js +128 -0
- package/composables/useCover.js +128 -0
- package/composables/useVideo.js +11 -21
- package/index.d.ts +3 -0
- package/l10n/de.json +4 -0
- package/l10n/en.json +4 -0
- package/lib/config.js +28 -6
- package/lib/containers.js +53 -7
- package/lib/media.js +9 -2
- package/lib/menus.d.ts +17 -0
- package/lib/menus.js +73 -0
- package/lib/sectionManifest.js +40 -10
- package/package.json +1 -1
- package/scripts/check-fixture.js +87 -8
package/COMPONENTS.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Scavold component and composable reference
|
|
2
2
|
|
|
3
|
+
<!-- Components and composables are listed in alphabetical order, each group on its
|
|
4
|
+
own; the sectioning wrappers close the components as one entry. A new one goes
|
|
5
|
+
where its name sorts, never simply at the end. -->
|
|
6
|
+
|
|
3
7
|
All components are registered globally by `enhanceApp()` and are available in any
|
|
4
8
|
Markdown page or Vue template without explicit imports. Composables must be imported
|
|
5
9
|
explicitly.
|
|
@@ -14,154 +18,299 @@ the broader "when and why" context that hover docs cannot convey.
|
|
|
14
18
|
|
|
15
19
|
---
|
|
16
20
|
|
|
17
|
-
### `<
|
|
21
|
+
### `<ScavoldBreadcrumb>`
|
|
18
22
|
|
|
19
|
-
Renders a `<nav>` element containing a
|
|
20
|
-
|
|
23
|
+
Renders a `<nav class="breadcrumb">` element containing a flat list of links
|
|
24
|
+
representing the path from the root to the current page. Uses the same
|
|
25
|
+
`<ul>/<li>/<a>` structure as `<ScavoldMenu>` so both components can share CSS.
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
Each item in the trail carries `.active` (all ancestor items) and `.current` (the
|
|
28
|
+
last item, if the current page is included). This makes it straightforward to style
|
|
29
|
+
the current crumb differently in CSS.
|
|
25
30
|
|
|
26
31
|
#### Props
|
|
27
32
|
|
|
28
33
|
| Prop | Type | Default | Description |
|
|
29
34
|
|---|---|---|---|
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `from` | `number` | `0` | List items relative to the current page. `0` = siblings, `1` = children, `-1` = aunt/uncle level (children of grandparent), etc. Ignored when `from-root` or `from-path` is set. |
|
|
33
|
-
| `depth` | `number` | `0` | Additional levels to descend below the starting level. `0` = flat list, `1` = one level of children, `-1` = unlimited. |
|
|
34
|
-
| `active-only` | `boolean` | `false` | When `true`, only expands children along the branch leading to the current page. Useful with `depth > 0` to show sub-items under the active section only. |
|
|
35
|
-
| `expand` | `boolean` | `false` | When `true`, expands children of all nodes regardless of the active branch. Enables sitemap-style rendering. `active-only` takes precedence when both are set. |
|
|
36
|
-
| `label` | `string` | — | Accessible label for the `<nav>` landmark (`aria-label`). Should be set whenever more than one `<ScavoldMenu>` appears on the same page so screen readers can distinguish them (e.g. `"Main navigation"`, `"Section navigation"`). |
|
|
35
|
+
| `include-current` | `boolean` | `true` | Include the current page as the last crumb |
|
|
36
|
+
| `include-root` | `boolean` | `false` | Include the root node (typically "Home") as the first crumb |
|
|
37
37
|
|
|
38
38
|
#### Rendered markup
|
|
39
39
|
|
|
40
40
|
```html
|
|
41
|
-
|
|
41
|
+
<!-- on page de/leistungen.md with include-root omitted -->
|
|
42
|
+
<nav class="breadcrumb">
|
|
42
43
|
<ul>
|
|
43
|
-
<li class="active">
|
|
44
|
-
<a href="/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
</li>
|
|
49
|
-
</ul>
|
|
44
|
+
<li class="active">
|
|
45
|
+
<a href="/de/">Deutsch</a> <!-- de/ folder node, label from de/index.md -->
|
|
46
|
+
</li>
|
|
47
|
+
<li class="active current">
|
|
48
|
+
<a href="/de/leistungen/">Leistungen</a>
|
|
50
49
|
</li>
|
|
51
50
|
</ul>
|
|
52
51
|
</nav>
|
|
53
52
|
```
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
current page itself. The `.current` class is set only on the item that exactly matches
|
|
57
|
-
the current page.
|
|
54
|
+
#### Examples
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
```html
|
|
57
|
+
<!-- typical usage — section trail + current page -->
|
|
58
|
+
<ScavoldBreadcrumb />
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
<!-- trail without current page, e.g. when page heading serves as final crumb -->
|
|
61
|
+
<ScavoldBreadcrumb :include-current="false" />
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
4. The last path segment of the page file (e.g. `about` from `de/about.md`) — last resort
|
|
63
|
+
<!-- full trail including home link -->
|
|
64
|
+
<ScavoldBreadcrumb include-root />
|
|
65
|
+
```
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
---
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
### `<ScavoldContainer>`
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
Fallback component for Markdown container blocks whose name has no dedicated
|
|
72
|
+
registered component. Renders as the matching HTML sectioning element if the
|
|
73
|
+
container name is one (`section`, `aside`, etc.), otherwise as a `<div>`.
|
|
74
|
+
|
|
75
|
+
Any container name works without being declared first — undeclared names are caught
|
|
76
|
+
by this component. Because a typo would otherwise become a silent `<div>`, the build
|
|
77
|
+
reports each undeclared name once:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
[scavold] container ':::sectoin' is not declared in .cratly.config.yaml — rendering
|
|
81
|
+
it with ScavoldContainer. Declare it to silence this, or fix the name if it is a typo.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Declaring the container in `.cratly.config.yaml` silences the report and gives it
|
|
85
|
+
typed properties in the cratly editor.
|
|
86
|
+
|
|
87
|
+
Not intended for direct use. Theme developers should instead create a dedicated
|
|
88
|
+
component for each container name they declare in `.cratly.config.yaml`.
|
|
89
|
+
|
|
90
|
+
See [`useContainer`](#usecontainer-props) for building custom container components.
|
|
81
91
|
|
|
82
|
-
```yaml
|
|
83
|
-
---
|
|
84
|
-
hide: menu
|
|
85
92
|
---
|
|
93
|
+
|
|
94
|
+
### `<ScavoldCover>`
|
|
95
|
+
|
|
96
|
+
Shows an image or a video across the full width of the window and at least as high as
|
|
97
|
+
the window, cropped to fit — a cover, or hero, at the top of a page. Written as a
|
|
98
|
+
`:::cover` container block; whatever the block contains is laid on top of the picture.
|
|
99
|
+
|
|
100
|
+
#### Markdown syntax
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
::: cover src=/wind-farm.jpg focus="center top" label="Wind turbines at dusk"
|
|
104
|
+
# With new energy into the future
|
|
105
|
+
:::
|
|
106
|
+
|
|
107
|
+
::: cover src=/turbines.mp4 poster=/turbines.jpg
|
|
108
|
+
:::
|
|
86
109
|
```
|
|
87
110
|
|
|
88
|
-
####
|
|
111
|
+
#### Arguments
|
|
112
|
+
|
|
113
|
+
| Argument | Type | Default | Description |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `src` | `media-file` | required | Image or video. A file ending in `.mp4`, `.m4v`, `.webm`, `.ogv` or `.mov` is played as a video, anything else is shown as an image. |
|
|
116
|
+
| `poster` | `media-file` | — | Only for a video: still image shown until it plays, and instead of playing for visitors who asked for reduced motion. |
|
|
117
|
+
| `focus` | `string` | `50% 50%` | Part of the picture kept visible when the window crops it, as a CSS `object-position` — `center top`, `30% 60%`. |
|
|
118
|
+
| `label` | `string` | — | Alternative text of an image, accessible label of a video. Left out, the picture is treated as decoration. |
|
|
119
|
+
| `tone` | `"light" \| "dark"` | — | Whether the content on top is meant to be light (over a dark picture) or dark (over a bright one). Only marks the cover with `scavold-cover--light` or `scavold-cover--dark`; what that looks like is the theme's. Left out, or with any other value, the cover carries neither class. |
|
|
120
|
+
|
|
121
|
+
#### Rendered markup
|
|
89
122
|
|
|
90
123
|
```html
|
|
91
|
-
<!--
|
|
92
|
-
<
|
|
124
|
+
<!-- image, tone=light -->
|
|
125
|
+
<div class="scavold-cover scavold-cover--light" style="--scavold-cover-focus: center top">
|
|
126
|
+
<picture class="scavold-cover__media">
|
|
127
|
+
<source type="image/webp" srcset="..." sizes="(min-aspect-ratio: 2400/1350) 100vw, 177.78vh" />
|
|
128
|
+
<source srcset="..." sizes="..." />
|
|
129
|
+
<img src="..." alt="..." sizes="..." loading="eager" fetchpriority="high" decoding="async" />
|
|
130
|
+
</picture>
|
|
131
|
+
<div class="scavold-cover__overlay"><!-- block content --></div>
|
|
132
|
+
</div>
|
|
93
133
|
|
|
94
|
-
<!--
|
|
95
|
-
<
|
|
134
|
+
<!-- video -->
|
|
135
|
+
<div class="scavold-cover scavold-cover--video">
|
|
136
|
+
<video class="scavold-cover__media" src="..." poster="..." preload="metadata" muted loop playsinline></video>
|
|
137
|
+
<div class="scavold-cover__overlay"><!-- block content --></div>
|
|
138
|
+
<button type="button" class="scavold-cover__toggle" aria-label="Pause video">…</button>
|
|
139
|
+
</div>
|
|
140
|
+
```
|
|
96
141
|
|
|
97
|
-
|
|
98
|
-
|
|
142
|
+
An image comes with all its variants. Its `sizes` value is derived from its aspect
|
|
143
|
+
ratio: a picture covering an upright phone screen is drawn much wider than the screen,
|
|
144
|
+
and the browser has to fetch a variant for that width, not for the width of the screen.
|
|
145
|
+
It is loaded at once rather than lazily, since a cover is the first thing on a page.
|
|
99
146
|
|
|
100
|
-
|
|
101
|
-
|
|
147
|
+
A video plays muted in a loop, started by script — unless the visitor's system asks
|
|
148
|
+
for reduced motion, in which case the still image stays and the button offers to start
|
|
149
|
+
it. It plays while at least half of it is on screen — half of the window, for one higher
|
|
150
|
+
than the window — and pauses when scrolled away. A video the visitor paused stays
|
|
151
|
+
paused until they start it again, and one they turned the sound up on keeps playing
|
|
152
|
+
when scrolled away. The button is always there: moving content that lasts more
|
|
153
|
+
than five seconds has to be pausable (WCAG 2.2.2). Its label follows the state and comes
|
|
154
|
+
from the [`@scavold.cover.*` translations](#built-in-translation-keys).
|
|
102
155
|
|
|
103
|
-
|
|
104
|
-
<ScavoldMenu :from-root="1" :depth="-1" />
|
|
156
|
+
#### Fitting it into the theme
|
|
105
157
|
|
|
106
|
-
|
|
107
|
-
|
|
158
|
+
The component ships functional layout CSS only. It leaves the column it is written in
|
|
159
|
+
by `margin-inline: calc(50% - 50vw)`, which assumes a column centred in the window. Two
|
|
160
|
+
things are the theme's to settle:
|
|
108
161
|
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
```css
|
|
163
|
+
/* 100vw includes a classic scrollbar, so the cover would stick out by its width */
|
|
164
|
+
html { overflow-x: clip; }
|
|
111
165
|
|
|
112
|
-
|
|
113
|
-
|
|
166
|
+
/* leave room for a fixed header; the default is the window height, 100svh */
|
|
167
|
+
.scavold-cover { --scavold-cover-height: calc(100svh - var(--header-height)); }
|
|
168
|
+
```
|
|
114
169
|
|
|
115
|
-
|
|
116
|
-
|
|
170
|
+
The overlay is an empty grid cell over the picture; position its content, add a scrim
|
|
171
|
+
for legibility and restyle `.scavold-cover__toggle` as the design asks.
|
|
172
|
+
|
|
173
|
+
The `tone` an author picked is the theme's to honour. Scavold sets no colour for it;
|
|
174
|
+
a theme gives each tone the colours of its design, including headings and links its
|
|
175
|
+
prose styles colour on their own:
|
|
176
|
+
|
|
177
|
+
```css
|
|
178
|
+
.scavold-cover--light .scavold-cover__overlay { color: #fff; }
|
|
179
|
+
.scavold-cover--dark .scavold-cover__overlay { color: #000; }
|
|
117
180
|
```
|
|
118
181
|
|
|
119
|
-
|
|
182
|
+
#### A cover opening the page
|
|
120
183
|
|
|
121
|
-
|
|
184
|
+
Where the theme's own parts go around a cover is the theme's choice; Scavold places
|
|
185
|
+
none of them. It only tells, in two ways, that a page opens with a container:
|
|
122
186
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
187
|
+
- the container carries an empty `data-leading` attribute;
|
|
188
|
+
- the page data carry `leadingContainer`, the name of that container (`"cover"`), or
|
|
189
|
+
`null` when the page opens with anything else. A layout renders its own parts before
|
|
190
|
+
the page content, so this is known at build time, ahead of the content.
|
|
126
191
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
192
|
+
A theme that wants its breadcrumb below a cover opening the page, rather than above
|
|
193
|
+
it, combines both. The layout leaves out the breadcrumb on such a page:
|
|
194
|
+
|
|
195
|
+
```vue
|
|
196
|
+
<!-- Layout.vue -->
|
|
197
|
+
<script setup>
|
|
198
|
+
import { useData } from "vitepress";
|
|
199
|
+
const { page } = useData();
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<template>
|
|
203
|
+
<ScavoldBreadcrumb v-if="page.leadingContainer !== 'cover'" />
|
|
204
|
+
<Content />
|
|
205
|
+
</template>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
…and the cover is mapped to a component of the site's own that wraps the built-in one
|
|
209
|
+
and adds the breadcrumb after it when it is the leading one:
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
# .cratly.config.yaml — the editor keeps offering the cover's own properties
|
|
213
|
+
containers:
|
|
214
|
+
cover:
|
|
215
|
+
component: SiteCover
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```vue
|
|
219
|
+
<!-- .vitepress/theme/components/SiteCover.vue -->
|
|
220
|
+
<script setup>
|
|
221
|
+
import ScavoldCover from "scavold/components/ScavoldCover.vue";
|
|
222
|
+
import ScavoldBreadcrumb from "scavold/components/ScavoldBreadcrumb.vue";
|
|
223
|
+
|
|
224
|
+
defineOptions( { inheritAttrs: false } );
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<template>
|
|
228
|
+
<ScavoldCover v-bind="$attrs"><slot /></ScavoldCover>
|
|
229
|
+
<ScavoldBreadcrumb v-if="$attrs['data-leading'] != null" />
|
|
230
|
+
</template>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
// .vitepress/theme/index.js — the container renders the component by name
|
|
235
|
+
async function enhanceApp( context ) {
|
|
236
|
+
await scavoldEnhanceApp( context );
|
|
237
|
+
context.app.component( "SiteCover", SiteCover );
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Below a cover the breadcrumb sits inside the content column, so the theme's prose
|
|
242
|
+
styles reach its list; keep them out where they differ from the breadcrumb's own.
|
|
243
|
+
|
|
244
|
+
Visual and reading order stay the same this way, which a reordering stylesheet could
|
|
245
|
+
not promise.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### `<ScavoldImage>`
|
|
250
|
+
|
|
251
|
+
Renders a responsive `<picture>` element for local images. Not intended for direct
|
|
252
|
+
use in templates — it is emitted automatically by the Markdown image renderer when a
|
|
253
|
+
local image path is encountered. Custom components that need to display a media-file
|
|
254
|
+
frontmatter field can use it directly.
|
|
130
255
|
|
|
131
256
|
#### Props
|
|
132
257
|
|
|
133
258
|
| Prop | Type | Default | Description |
|
|
134
259
|
|---|---|---|---|
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
260
|
+
| `src` | `string` | required | URL of the largest fallback image variant |
|
|
261
|
+
| `srcset` | `string` | `""` | `srcset` string for the original format (JPEG/PNG) |
|
|
262
|
+
| `webp-srcset` | `string` | `""` | `srcset` string for the WebP variants |
|
|
263
|
+
| `sizes` | `string` | `"100vw"` | CSS `sizes` attribute applied to all sources |
|
|
264
|
+
| `alt` | `string` | `""` | Alt text for the `<img>` element |
|
|
265
|
+
| `loading` | `string` | `"lazy"` | `loading` attribute of the `<img>`; `"eager"` for an image at the top of a page |
|
|
266
|
+
| `fetchpriority` | `string` | — | `fetchpriority` attribute of the `<img>`, e.g. `"high"` for the largest image above the fold |
|
|
137
267
|
|
|
138
268
|
#### Rendered markup
|
|
139
269
|
|
|
140
270
|
```html
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
</li>
|
|
147
|
-
<li class="active current">
|
|
148
|
-
<a href="/de/leistungen/">Leistungen</a>
|
|
149
|
-
</li>
|
|
150
|
-
</ul>
|
|
151
|
-
</nav>
|
|
271
|
+
<picture>
|
|
272
|
+
<source type="image/webp" srcset="..." sizes="..." />
|
|
273
|
+
<source srcset="..." sizes="..." />
|
|
274
|
+
<img src="..." alt="..." sizes="..." loading="lazy" decoding="async" />
|
|
275
|
+
</picture>
|
|
152
276
|
```
|
|
153
277
|
|
|
154
|
-
|
|
278
|
+
---
|
|
155
279
|
|
|
156
|
-
|
|
157
|
-
<!-- typical usage — section trail + current page -->
|
|
158
|
-
<ScavoldBreadcrumb />
|
|
280
|
+
### `<ScavoldLayout>`
|
|
159
281
|
|
|
160
|
-
|
|
161
|
-
|
|
282
|
+
Base layout wrapper that intercepts pages with locale-conditional redirect front
|
|
283
|
+
matter and renders `<ScavoldLocaleRedirect>` in their place. All other pages render
|
|
284
|
+
the default slot.
|
|
162
285
|
|
|
163
|
-
|
|
164
|
-
|
|
286
|
+
Consuming themes wrap their own layout root element in this component so they
|
|
287
|
+
automatically inherit redirect handling without duplicating the detection logic:
|
|
288
|
+
|
|
289
|
+
```vue
|
|
290
|
+
<template>
|
|
291
|
+
<ScavoldLayout>
|
|
292
|
+
<div class="site-shell">
|
|
293
|
+
<!-- header, main, footer … -->
|
|
294
|
+
</div>
|
|
295
|
+
</ScavoldLayout>
|
|
296
|
+
</template>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Themes that need finer control can skip `<ScavoldLayout>` and compose the two
|
|
300
|
+
building blocks themselves using `useRedirect` and `<ScavoldLocaleRedirect>`:
|
|
301
|
+
|
|
302
|
+
```vue
|
|
303
|
+
<script setup>
|
|
304
|
+
import { useRedirect } from "./scavold/composables/useRedirect.js";
|
|
305
|
+
import ScavoldLocaleRedirect from "./scavold/components/ScavoldLocaleRedirect.vue";
|
|
306
|
+
|
|
307
|
+
const { isLocaleRedirect } = useRedirect();
|
|
308
|
+
</script>
|
|
309
|
+
|
|
310
|
+
<template>
|
|
311
|
+
<ScavoldLocaleRedirect v-if="isLocaleRedirect" />
|
|
312
|
+
<div v-else class="site-shell"><!-- … --></div>
|
|
313
|
+
</template>
|
|
165
314
|
```
|
|
166
315
|
|
|
167
316
|
---
|
|
@@ -263,48 +412,10 @@ pages/
|
|
|
263
412
|
|
|
264
413
|
---
|
|
265
414
|
|
|
266
|
-
### `<
|
|
267
|
-
|
|
268
|
-
Base layout wrapper that intercepts pages with locale-conditional redirect front
|
|
269
|
-
matter and renders `<ScavoldLocaleRedirect>` in their place. All other pages render
|
|
270
|
-
the default slot.
|
|
415
|
+
### `<ScavoldLocaleRedirect>`
|
|
271
416
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
```vue
|
|
276
|
-
<template>
|
|
277
|
-
<ScavoldLayout>
|
|
278
|
-
<div class="site-shell">
|
|
279
|
-
<!-- header, main, footer … -->
|
|
280
|
-
</div>
|
|
281
|
-
</ScavoldLayout>
|
|
282
|
-
</template>
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Themes that need finer control can skip `<ScavoldLayout>` and compose the two
|
|
286
|
-
building blocks themselves using `useRedirect` and `<ScavoldLocaleRedirect>`:
|
|
287
|
-
|
|
288
|
-
```vue
|
|
289
|
-
<script setup>
|
|
290
|
-
import { useRedirect } from "./scavold/composables/useRedirect.js";
|
|
291
|
-
import ScavoldLocaleRedirect from "./scavold/components/ScavoldLocaleRedirect.vue";
|
|
292
|
-
|
|
293
|
-
const { isLocaleRedirect } = useRedirect();
|
|
294
|
-
</script>
|
|
295
|
-
|
|
296
|
-
<template>
|
|
297
|
-
<ScavoldLocaleRedirect v-if="isLocaleRedirect" />
|
|
298
|
-
<div v-else class="site-shell"><!-- … --></div>
|
|
299
|
-
</template>
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
### `<ScavoldLocaleRedirect>`
|
|
305
|
-
|
|
306
|
-
Handles locale-conditional redirect pages declared with an object-form `redirect`
|
|
307
|
-
in front matter:
|
|
417
|
+
Handles locale-conditional redirect pages declared with an object-form `redirect`
|
|
418
|
+
in front matter:
|
|
308
419
|
|
|
309
420
|
```yaml
|
|
310
421
|
---
|
|
@@ -328,138 +439,136 @@ The component renders no visible content of its own. It is used automatically by
|
|
|
328
439
|
|
|
329
440
|
---
|
|
330
441
|
|
|
331
|
-
### `<
|
|
442
|
+
### `<ScavoldMenu>`
|
|
332
443
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
their own menu component using `useHierarchy`.
|
|
444
|
+
Renders a `<nav>` element containing a nested list of page links derived from the
|
|
445
|
+
site's page hierarchy. Nothing is rendered when the resolved item set is empty.
|
|
336
446
|
|
|
337
|
-
|
|
447
|
+
The component has three independent addressing modes — path-based, absolute (from
|
|
448
|
+
root), and relative (from current page) — that cover all common navigation patterns
|
|
449
|
+
without requiring knowledge of the current page's depth.
|
|
338
450
|
|
|
339
|
-
|
|
451
|
+
With `menu`, it renders a **named menu** instead: the pages choosing that menu in
|
|
452
|
+
their [`menus`](./FRONTMATTER.md#menus) front matter, wherever they sit among the folders.
|
|
453
|
+
The names are declared in `.cratly.config.yaml`; where a menu goes is up to the theme.
|
|
340
454
|
|
|
341
|
-
|
|
342
|
-
Nothing is rendered when no `src` argument is provided.
|
|
455
|
+
#### Props
|
|
343
456
|
|
|
344
|
-
|
|
457
|
+
| Prop | Type | Default | Description |
|
|
458
|
+
|---|---|---|---|
|
|
459
|
+
| `menu` | `string` | — | Render the named menu declared under this name in `.cratly.config.yaml`: the pages choosing it in their `menus` front matter, in page-tree order. `from-path` and `from-root` then narrow the part of the site searched — `from-path="{lang}"` keeps a multilingual site's menu to one language — and `from` is ignored. `depth`, `active-only` and `expand` descend below each listed page. A page choosing the menu along with one of its ancestors is listed below that ancestor when `depth` allows sub-pages, and beside it when the menu is flat. |
|
|
460
|
+
| `from-path` | `string` | — | Select the parent node by path. Supports `{locale}` (full BCP 47 tag, e.g. `de-CH`) and `{lang}` (primary language subtag only, e.g. `de`) placeholders, replaced with the current page's locale at runtime. Takes priority over `from-root` and `from`. Examples: `"de/footer"`, `"{lang}/footer"`, `"{locale}/footer"`. |
|
|
461
|
+
| `from-root` | `number` | — | List items at this absolute depth from root. `1` = top-level pages, `2` = second level, etc. When set, `from` is ignored. If the current page has no ancestor at this depth, nothing is rendered. Ignored when `from-path` is set. |
|
|
462
|
+
| `from` | `number` | `0` | List items relative to the current page. `0` = siblings, `1` = children, `-1` = aunt/uncle level (children of grandparent), etc. Ignored when `from-root` or `from-path` is set. |
|
|
463
|
+
| `depth` | `number` | `0` | Additional levels to descend below the starting level. `0` = flat list, `1` = one level of children, `-1` = unlimited. |
|
|
464
|
+
| `active-only` | `boolean` | `false` | When `true`, only expands children along the branch leading to the current page. Useful with `depth > 0` to show sub-items under the active section only. |
|
|
465
|
+
| `expand` | `boolean` | `false` | When `true`, expands children of all nodes regardless of the active branch. Enables sitemap-style rendering. `active-only` takes precedence when both are set. |
|
|
466
|
+
| `label` | `string` | — | Accessible label for the `<nav>` landmark (`aria-label`). Should be set whenever more than one `<ScavoldMenu>` appears on the same page so screen readers can distinguish them (e.g. `"Main navigation"`, `"Section navigation"`). |
|
|
345
467
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
468
|
+
#### Rendered markup
|
|
469
|
+
|
|
470
|
+
```html
|
|
471
|
+
<nav>
|
|
472
|
+
<ul>
|
|
473
|
+
<li class="active"> <!-- .active when node is current or an ancestor -->
|
|
474
|
+
<a href="/section/">Section</a>
|
|
475
|
+
<ul> <!-- nested only when children are included -->
|
|
476
|
+
<li class="current">
|
|
477
|
+
<a href="/section/page/">Page</a>
|
|
478
|
+
</li>
|
|
479
|
+
</ul>
|
|
480
|
+
</li>
|
|
481
|
+
</ul>
|
|
482
|
+
</nav>
|
|
350
483
|
```
|
|
351
484
|
|
|
352
|
-
|
|
485
|
+
The `.active` class is set on any item that is an ancestor of the current page or the
|
|
486
|
+
current page itself. The `.current` class is set only on the item that exactly matches
|
|
487
|
+
the current page.
|
|
353
488
|
|
|
354
|
-
|
|
355
|
-
|---|---|---|---|
|
|
356
|
-
| `src` | `string` | required | URL of the video file |
|
|
357
|
-
| `poster` | `string` | — | URL of the poster image shown before playback |
|
|
358
|
-
| `autoplay` | flag | — | Play automatically on load. Forces `muted` (browser requirement) |
|
|
359
|
-
| `loop` | flag | — | Loop the video when it ends |
|
|
360
|
-
| `muted` | flag | — | Mute the audio track |
|
|
361
|
-
| `overlay` | flag | — | Render the video as a background and lay the block's body content on top of it (hero/background mode). See below. |
|
|
362
|
-
| `controls` | flag | — | Show native playback controls. Always shown in the default player; opt-in in `overlay` mode, which is chrome-free by default. |
|
|
363
|
-
| `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Browser preload hint |
|
|
364
|
-
| `label` | `string` | — | Accessible label (`aria-label`) announced by screen readers. Use when the surrounding context does not already describe the video. |
|
|
489
|
+
#### Label resolution
|
|
365
490
|
|
|
366
|
-
|
|
491
|
+
Item labels are resolved in this order:
|
|
367
492
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
493
|
+
1. `label` frontmatter — explicit navigation text, overrides everything
|
|
494
|
+
2. `title` frontmatter — page title, used when no `label` is set
|
|
495
|
+
3. First `#` heading in the page body — extracted at build time as a fallback when neither `label` nor `title` is declared in frontmatter
|
|
496
|
+
4. The last path segment of the page file (e.g. `about` from `de/about.md`) — last resort
|
|
372
497
|
|
|
373
|
-
####
|
|
498
|
+
#### Hiding pages via front matter
|
|
374
499
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
500
|
+
A page can opt out of appearing in menus by setting `hide` in its front matter:
|
|
501
|
+
|
|
502
|
+
| Value | Effect |
|
|
503
|
+
|---|---|
|
|
504
|
+
| `false` / absent | Visible everywhere (default) |
|
|
505
|
+
| `true` | Kept off every surface: menus, breadcrumbs, page lists and the feed |
|
|
506
|
+
| `"menu"` | Kept out of menus only |
|
|
507
|
+
| `"breadcrumb"` | Kept out of breadcrumbs only |
|
|
508
|
+
| `"list"` | Kept out of page lists only |
|
|
509
|
+
| `"feed"` | Left out of the RSS feed only |
|
|
510
|
+
| `[ "menu", "feed" ]` | Kept off exactly the surfaces named |
|
|
511
|
+
|
|
512
|
+
```yaml
|
|
513
|
+
---
|
|
514
|
+
hide: menu
|
|
515
|
+
---
|
|
379
516
|
```
|
|
380
517
|
|
|
381
|
-
|
|
382
|
-
`muted`, and `poster` are only emitted when the corresponding argument is set.
|
|
518
|
+
Hiding from menus rules over any choice in `menus`.
|
|
383
519
|
|
|
384
|
-
####
|
|
520
|
+
#### Choosing named menus via front matter
|
|
385
521
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
default (add `controls` to bring the native controls back), and a typical hero
|
|
389
|
-
combines `overlay` with `autoplay` + `loop` (both imply/allow `muted`):
|
|
522
|
+
Without `menu`, the component lists only pages that leave `menus` out or choose
|
|
523
|
+
`auto` among others. A page choosing named menus only appears in those:
|
|
390
524
|
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
#
|
|
394
|
-
|
|
395
|
-
:::
|
|
525
|
+
```yaml
|
|
526
|
+
---
|
|
527
|
+
menus: footer # in the footer, not in the page tree's menus
|
|
528
|
+
---
|
|
396
529
|
```
|
|
397
530
|
|
|
398
|
-
|
|
399
|
-
<div class="scavold-video scavold-video--overlay">
|
|
400
|
-
<video class="scavold-video__media" src="..." poster="..." autoplay loop muted
|
|
401
|
-
preload="metadata" playsinline></video>
|
|
402
|
-
<div class="scavold-video__overlay">
|
|
403
|
-
<!-- slot content -->
|
|
404
|
-
</div>
|
|
405
|
-
</div>
|
|
406
|
-
```
|
|
531
|
+
#### Examples
|
|
407
532
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
text colour, alignment, a `min-height` for the hero — belongs to the consuming theme,
|
|
412
|
-
which can hook onto the `.scavold-video`, `.scavold-video__media`, and
|
|
413
|
-
`.scavold-video__overlay` classes:
|
|
533
|
+
```html
|
|
534
|
+
<!-- top-level pages — typical site header nav -->
|
|
535
|
+
<ScavoldMenu :from-root="1" />
|
|
414
536
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
.scavold-video--overlay { min-height: 60vh; }
|
|
418
|
-
.scavold-video__overlay {
|
|
419
|
-
display: grid;
|
|
420
|
-
place-content: center;
|
|
421
|
-
padding: 2rem;
|
|
422
|
-
color: white;
|
|
423
|
-
background: rgba( 0, 0, 0, 0.4 ); /* scrim for legibility */
|
|
424
|
-
}
|
|
425
|
-
```
|
|
537
|
+
<!-- second level of the active top-level section only -->
|
|
538
|
+
<ScavoldMenu :from-root="2" active-only />
|
|
426
539
|
|
|
427
|
-
|
|
428
|
-
|
|
540
|
+
<!-- children of the current page -->
|
|
541
|
+
<ScavoldMenu :from="1" />
|
|
429
542
|
|
|
430
|
-
|
|
543
|
+
<!-- siblings of the current page -->
|
|
544
|
+
<ScavoldMenu />
|
|
431
545
|
|
|
432
|
-
|
|
433
|
-
|
|
546
|
+
<!-- full site hierarchy as a sitemap -->
|
|
547
|
+
<ScavoldMenu :from-root="1" :depth="-1" />
|
|
434
548
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
<track kind="captions" src="./clip.vtt" srclang="de" label="Deutsch" default />
|
|
438
|
-
:::
|
|
439
|
-
```
|
|
549
|
+
<!-- two levels starting from the active top-level section, active branch expanded -->
|
|
550
|
+
<ScavoldMenu :from-root="1" :depth="1" active-only />
|
|
440
551
|
|
|
441
|
-
|
|
442
|
-
|
|
552
|
+
<!-- fixed path — same footer nav regardless of locale -->
|
|
553
|
+
<ScavoldMenu from-path="de/footer" label="Footer-Navigation" />
|
|
443
554
|
|
|
444
|
-
|
|
555
|
+
<!-- locale-aware via primary language tag — works when paths use "de", "en", etc. -->
|
|
556
|
+
<ScavoldMenu from-path="{lang}/footer" label="Footer-Navigation" />
|
|
445
557
|
|
|
446
|
-
|
|
447
|
-
|
|
558
|
+
<!-- locale-aware via full BCP 47 tag — use when paths include region codes like "de-CH" -->
|
|
559
|
+
<ScavoldMenu from-path="{locale}/footer" label="Footer-Navigation" />
|
|
448
560
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
containers: { video: "MyVideoPlayer" },
|
|
452
|
-
} ) );
|
|
561
|
+
<!-- named menu: the pages choosing "footer", wherever they sit, within the current language -->
|
|
562
|
+
<ScavoldMenu menu="footer" from-path="{lang}" label="Footer-Navigation" />
|
|
453
563
|
```
|
|
454
564
|
|
|
455
|
-
|
|
565
|
+
---
|
|
456
566
|
|
|
457
|
-
|
|
458
|
-
import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
|
|
567
|
+
### `<ScavoldMenuItems>`
|
|
459
568
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
569
|
+
Internal recursive component used by `<ScavoldMenu>` to render `<ul>/<li>` trees.
|
|
570
|
+
Not intended for direct use. Documented here for theme developers who want to build
|
|
571
|
+
their own menu component using `useHierarchy`.
|
|
463
572
|
|
|
464
573
|
---
|
|
465
574
|
|
|
@@ -598,90 +707,277 @@ them discoverable.
|
|
|
598
707
|
|
|
599
708
|
---
|
|
600
709
|
|
|
601
|
-
### `<
|
|
710
|
+
### `<ScavoldVideo>`
|
|
602
711
|
|
|
603
|
-
Renders a
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
712
|
+
Renders a video player in the text column for a video embedded via the `:::video`
|
|
713
|
+
container block — a clip visitors watch and listen to, pause and wind back. Nothing is
|
|
714
|
+
rendered when no `src` argument is provided. For a video across the whole window,
|
|
715
|
+
with content laid on top, use [`<ScavoldCover>`](#scavoldcover).
|
|
607
716
|
|
|
608
|
-
####
|
|
717
|
+
#### Markdown syntax
|
|
609
718
|
|
|
610
|
-
|
|
719
|
+
```markdown
|
|
720
|
+
::: video src=./clip.mp4 poster=./thumb.jpg
|
|
721
|
+
Optional caption or fallback text for browsers that do not support the video element.
|
|
722
|
+
:::
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
#### Arguments
|
|
726
|
+
|
|
727
|
+
| Argument | Type | Default | Description |
|
|
611
728
|
|---|---|---|---|
|
|
612
|
-
| `src` | `string` | required | URL of the
|
|
613
|
-
| `
|
|
614
|
-
| `
|
|
615
|
-
| `
|
|
616
|
-
| `
|
|
729
|
+
| `src` | `string` | required | URL of the video file |
|
|
730
|
+
| `poster` | `string` | — | URL of the poster image shown before playback |
|
|
731
|
+
| `autoplay` | flag | — | Play automatically while on screen, unless the visitor asks for reduced motion. Forces `muted` (browser requirement) |
|
|
732
|
+
| `loop` | flag | — | Loop the video when it ends |
|
|
733
|
+
| `muted` | flag | — | Mute the audio track |
|
|
734
|
+
| `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Browser preload hint |
|
|
735
|
+
| `label` | `string` | — | Accessible label (`aria-label`) announced by screen readers. Use when the surrounding context does not already describe the video. |
|
|
736
|
+
|
|
737
|
+
Boolean flags are written without a value:
|
|
738
|
+
|
|
739
|
+
```markdown
|
|
740
|
+
::: video src=./clip.mp4 autoplay loop
|
|
741
|
+
:::
|
|
742
|
+
```
|
|
617
743
|
|
|
618
744
|
#### Rendered markup
|
|
619
745
|
|
|
620
746
|
```html
|
|
621
|
-
<
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
747
|
+
<video class="scavold-video" src="..." poster="..." preload="metadata" controls playsinline>
|
|
748
|
+
<!-- slot content -->
|
|
749
|
+
</video>
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
The `controls` and `playsinline` attributes are always present: every video can be
|
|
753
|
+
paused, which WCAG 2.2.2 asks of anything moving for longer than five seconds. `loop`,
|
|
754
|
+
`muted` and `poster` are only emitted when the corresponding argument is set.
|
|
755
|
+
|
|
756
|
+
`autoplay` is not emitted: the attribute starts a video before hydration, wherever it
|
|
757
|
+
is on the page, and before anyone could ask whether the visitor wants reduced motion.
|
|
758
|
+
The video is started by script instead. A visitor whose system asks for reduced motion
|
|
759
|
+
finds it paused, with the controls to start it. For everyone else it plays while at
|
|
760
|
+
least half of it is on screen — half of the window, for one higher than the window —
|
|
761
|
+
and pauses when scrolled away. A video the visitor paused stays paused until they
|
|
762
|
+
start it again, and one they turned the sound up on keeps playing when scrolled away.
|
|
763
|
+
|
|
764
|
+
#### Captions and accessibility
|
|
765
|
+
|
|
766
|
+
WCAG requires captions for prerecorded video with audio. Add a `<track>` element via
|
|
767
|
+
the slot:
|
|
768
|
+
|
|
769
|
+
```markdown
|
|
770
|
+
::: video src=./clip.mp4
|
|
771
|
+
<track kind="captions" src="./clip.vtt" srclang="de" label="Deutsch" default />
|
|
772
|
+
:::
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
The `label` argument provides an `aria-label` on the `<video>` element for cases where
|
|
776
|
+
the surrounding page context does not already describe the video.
|
|
777
|
+
|
|
778
|
+
#### Replacing with a custom component
|
|
779
|
+
|
|
780
|
+
Register a component under the name `ScavoldVideo` before calling `scavoldEnhanceApp`,
|
|
781
|
+
or map the `video` container name to your component in `augmentConfig`:
|
|
782
|
+
|
|
783
|
+
```js
|
|
784
|
+
export default defineConfig( await augmentConfig( config, {
|
|
785
|
+
containers: { video: "MyVideoPlayer" },
|
|
786
|
+
} ) );
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Custom components can reuse the resolution logic via `useVideo` and `videoProps`:
|
|
790
|
+
|
|
791
|
+
```js
|
|
792
|
+
import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
|
|
793
|
+
|
|
794
|
+
const props = defineProps( { ...videoProps } );
|
|
795
|
+
const { src, poster, autoplay, loop, muted, preload, ariaLabel } = useVideo( props );
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
### Sectioning wrappers
|
|
801
|
+
|
|
802
|
+
`<ScavoldArticle>`, `<ScavoldAside>`, `<ScavoldFooter>`, `<ScavoldHeader>`, `<ScavoldMain>`,
|
|
803
|
+
`<ScavoldNav>`, `<ScavoldSection>`
|
|
804
|
+
|
|
805
|
+
Thin wrapper components for the seven HTML sectioning elements. Each renders its
|
|
806
|
+
corresponding element, forwarding `class` and `data-*` attributes from the container
|
|
807
|
+
arguments.
|
|
808
|
+
|
|
809
|
+
These are the default targets for their respective container names. A theme developer
|
|
810
|
+
can replace any of them by registering a component with the same name before calling
|
|
811
|
+
`scavoldEnhanceApp`, or by passing an explicit override to `augmentConfig`:
|
|
812
|
+
|
|
813
|
+
```js
|
|
814
|
+
// .vitepress/config.js
|
|
815
|
+
export default defineConfig( await augmentConfig( config, {
|
|
816
|
+
containers: {
|
|
817
|
+
section: "MySiteSection",
|
|
818
|
+
}
|
|
819
|
+
} ) );
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
```js
|
|
823
|
+
// .vitepress/theme/index.js
|
|
824
|
+
async function enhanceApp( context ) {
|
|
825
|
+
app.component( "ScavoldSection", MySiteSection ); // registered before scavold
|
|
826
|
+
await scavoldEnhanceApp( context );
|
|
827
|
+
}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## Composables
|
|
833
|
+
|
|
834
|
+
---
|
|
835
|
+
|
|
836
|
+
### `formatTimestamp( timestamp, options )`
|
|
837
|
+
|
|
838
|
+
```js
|
|
839
|
+
import { formatTimestamp } from "scavold/lib/dateFormat.js";
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
Renders a timestamp the way page lists do, for a theme's own components — a byline
|
|
843
|
+
under a post's title, for instance. Free of Vue and VitePress, so build-time code can
|
|
844
|
+
use it too.
|
|
845
|
+
|
|
846
|
+
| Option | Type | Default | Description |
|
|
847
|
+
|---|---|---|---|
|
|
848
|
+
| `locale` | `string` | the runtime's | Locale to phrase the result in |
|
|
849
|
+
| `dateStyle` | `"short" \| "medium" \| "long" \| "full" \| "iso"` | `"long"` | Style of the date; `iso` writes `2020-10-20` in every language |
|
|
850
|
+
| `timeStyle` | same values | — | Style of the time of day; omitted, only the date is rendered |
|
|
851
|
+
| `timeZone` | `string` | `"UTC"` | IANA name of the zone the timestamp is read in |
|
|
852
|
+
|
|
853
|
+
The result depends on the arguments alone, never on the machine it runs on, so
|
|
854
|
+
pre-rendered markup and the browser agree — a timestamp that changes on hydration
|
|
855
|
+
would tear the markup apart. A timestamp that cannot be read renders as an empty
|
|
856
|
+
string rather than as `Invalid Date`.
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
### `useAutoplay( videoRef, enabled )`
|
|
861
|
+
|
|
862
|
+
```js
|
|
863
|
+
import { useAutoplay } from "scavold/composables/useAutoplay.js";
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
The autoplay behaviour of `<ScavoldVideo>` and `<ScavoldCover>`, for a site's own
|
|
867
|
+
video component: plays the video muted while at least half of it is on screen, pauses
|
|
868
|
+
it when scrolled away, leaves it alone once the visitor paused it or turned the sound
|
|
869
|
+
up, and does nothing for visitors who ask for reduced motion.
|
|
870
|
+
|
|
871
|
+
```js
|
|
872
|
+
const video = ref( null ); // template ref of the <video>
|
|
873
|
+
useAutoplay( video, () => autoplay.value );
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Leave the `autoplay` attribute off the element; it would start the video regardless.
|
|
877
|
+
|
|
878
|
+
---
|
|
879
|
+
|
|
880
|
+
### `useContainer( props )`
|
|
881
|
+
|
|
882
|
+
```js
|
|
883
|
+
import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Shared composable for custom container components. Parses the props emitted by the
|
|
887
|
+
Markdown container renderer and exposes derived values ready for template binding.
|
|
888
|
+
|
|
889
|
+
#### Usage
|
|
890
|
+
|
|
891
|
+
```vue
|
|
892
|
+
<script setup>
|
|
893
|
+
import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
|
|
894
|
+
|
|
895
|
+
const props = defineProps( {
|
|
896
|
+
...containerProps,
|
|
897
|
+
background: String, // custom KV argument: background=/media/hero.jpg
|
|
898
|
+
} );
|
|
899
|
+
|
|
900
|
+
const { containerName, classes, dataAttrs } = useContainer( props );
|
|
901
|
+
</script>
|
|
902
|
+
|
|
903
|
+
<template>
|
|
904
|
+
<section
|
|
905
|
+
:class="[containerName, classes]"
|
|
906
|
+
:style="background ? `--bg: url(${background})` : ''"
|
|
907
|
+
v-bind="dataAttrs"
|
|
908
|
+
>
|
|
909
|
+
<slot />
|
|
910
|
+
</section>
|
|
911
|
+
</template>
|
|
626
912
|
```
|
|
627
913
|
|
|
628
|
-
|
|
914
|
+
#### `containerProps`
|
|
629
915
|
|
|
630
|
-
|
|
916
|
+
The shared props definition every container component must declare. Spread it into
|
|
917
|
+
`defineProps` to avoid duplicating the base prop declarations.
|
|
631
918
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
919
|
+
| Prop | Type | Description |
|
|
920
|
+
|---|---|---|
|
|
921
|
+
| `class` | `string` | Space-separated boolean flags from the container opening line |
|
|
922
|
+
| `data-container` | `string` | The container name as written in Markdown |
|
|
635
923
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
924
|
+
All `key=value` pairs from the opening line are passed as additional props with a
|
|
925
|
+
`data-` prefix (e.g. `background=/img.jpg` → prop `data-background`). Declare them
|
|
926
|
+
explicitly in `defineProps` to use them.
|
|
639
927
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
928
|
+
Flags arrive **both ways**: as part of `class` and as an empty `data-<flag>` prop, so
|
|
929
|
+
a boolean container parameter can be declared as a prop (this is how `useVideo` reads
|
|
930
|
+
`autoplay` and `loop`) while purely presentational flags keep working as
|
|
931
|
+
class names. A flag a component does not declare falls through into the markup as an
|
|
932
|
+
empty attribute — `::: section highlight` renders `<section class="highlight"
|
|
933
|
+
data-highlight>`.
|
|
644
934
|
|
|
645
|
-
|
|
646
|
-
|
|
935
|
+
A container that opens the page — its fence is the first thing in the page body —
|
|
936
|
+
additionally receives an empty `data-leading` attribute. It is information only and
|
|
937
|
+
changes nothing by itself; a component or a stylesheet can tell from it that this
|
|
938
|
+
block stands at the very top. The same fact reaches the layout ahead of the content as
|
|
939
|
+
`page.leadingContainer` (see [A cover opening the page](#a-cover-opening-the-page)).
|
|
647
940
|
|
|
648
|
-
|
|
649
|
-
component for each container name they declare in `.cratly.config.yaml`.
|
|
941
|
+
#### Returns
|
|
650
942
|
|
|
651
|
-
|
|
943
|
+
| Name | Type | Description |
|
|
944
|
+
|---|---|---|
|
|
945
|
+
| `containerName` | `ComputedRef<string>` | The container name (value of `data-container`) |
|
|
946
|
+
| `rootTag` | `ComputedRef<string>` | `containerName` if it is a sectioning element, otherwise `"div"` |
|
|
947
|
+
| `classes` | `ComputedRef<string>` | The `class` prop value |
|
|
948
|
+
| `dataAttrs` | `ComputedRef<object>` | All `data-*` props as a plain object, suitable for `v-bind` |
|
|
652
949
|
|
|
653
|
-
|
|
950
|
+
#### Markdown syntax reminder
|
|
654
951
|
|
|
655
|
-
|
|
952
|
+
```markdown
|
|
953
|
+
::: hero dark centered background=/media/hero.jpg
|
|
954
|
+
Content here.
|
|
955
|
+
:::
|
|
956
|
+
```
|
|
656
957
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
arguments.
|
|
958
|
+
This emits `class="dark centered"`, `data-container="hero"`, and
|
|
959
|
+
`data-background="/media/hero.jpg"` onto the component.
|
|
660
960
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
`
|
|
961
|
+
---
|
|
962
|
+
|
|
963
|
+
### `useCover( props )`
|
|
664
964
|
|
|
665
965
|
```js
|
|
666
|
-
|
|
667
|
-
export default defineConfig( await augmentConfig( config, {
|
|
668
|
-
containers: {
|
|
669
|
-
section: "MySiteSection",
|
|
670
|
-
}
|
|
671
|
-
} ) );
|
|
966
|
+
import { useCover, coverProps } from "scavold/composables/useCover.js";
|
|
672
967
|
```
|
|
673
968
|
|
|
969
|
+
Resolves the arguments of a `:::cover` block for a site's own cover component, so it
|
|
970
|
+
renders the same picture the way its design asks:
|
|
971
|
+
|
|
674
972
|
```js
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
app.component( "ScavoldSection", MySiteSection ); // registered before scavold
|
|
678
|
-
await scavoldEnhanceApp( context );
|
|
679
|
-
}
|
|
973
|
+
const props = defineProps( { ...coverProps } );
|
|
974
|
+
const { src, isVideo, srcset, webpSrcset, sizes, poster, focus, label, tone } = useCover( props );
|
|
680
975
|
```
|
|
681
976
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
977
|
+
`srcset`, `webpSrcset` and `sizes` are filled for an image only; the build provides
|
|
978
|
+
the variants and the dimensions `sizes` is derived from. `tone` is `"light"`, `"dark"`
|
|
979
|
+
or empty, one of `COVER_TONES`. `isVideoUrl( url )` and `coverSizes( width, height )`
|
|
980
|
+
are exported as well.
|
|
685
981
|
|
|
686
982
|
---
|
|
687
983
|
|
|
@@ -767,46 +1063,6 @@ const items = computed( () =>
|
|
|
767
1063
|
|
|
768
1064
|
---
|
|
769
1065
|
|
|
770
|
-
### `useVideo( props )`
|
|
771
|
-
|
|
772
|
-
```js
|
|
773
|
-
import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
Composable for custom video components. Resolves the `:::video` container props into
|
|
777
|
-
clean, typed values ready for template binding. `autoplay` automatically forces `muted`
|
|
778
|
-
to satisfy browser autoplay policies.
|
|
779
|
-
|
|
780
|
-
#### `videoProps`
|
|
781
|
-
|
|
782
|
-
Spread into `defineProps` to declare all video-related props at once.
|
|
783
|
-
|
|
784
|
-
| Prop | Argument | Description |
|
|
785
|
-
|---|---|---|
|
|
786
|
-
| `dataSrc` | `src=…` | Video file URL |
|
|
787
|
-
| `dataPoster` | `poster=…` | Poster image URL |
|
|
788
|
-
| `dataAutoplay` | `autoplay` | Present when the `autoplay` flag is set |
|
|
789
|
-
| `dataLoop` | `loop` | Present when the `loop` flag is set |
|
|
790
|
-
| `dataMuted` | `muted` | Present when the `muted` flag is set |
|
|
791
|
-
| `dataOverlay` | `overlay` | Present when the `overlay` flag is set (background mode) |
|
|
792
|
-
| `dataControls` | `controls` | Present when the `controls` flag is set |
|
|
793
|
-
| `dataPreload` | `preload=…` | One of `"none"`, `"metadata"`, `"auto"` |
|
|
794
|
-
|
|
795
|
-
#### Returns
|
|
796
|
-
|
|
797
|
-
| Name | Type | Description |
|
|
798
|
-
|---|---|---|
|
|
799
|
-
| `src` | `ComputedRef<string>` | Video URL |
|
|
800
|
-
| `poster` | `ComputedRef<string>` | Poster URL, empty string if absent |
|
|
801
|
-
| `autoplay` | `ComputedRef<boolean>` | `true` when `autoplay` flag is set |
|
|
802
|
-
| `loop` | `ComputedRef<boolean>` | `true` when `loop` flag is set |
|
|
803
|
-
| `muted` | `ComputedRef<boolean>` | `true` when `muted` flag is set or `autoplay` is true |
|
|
804
|
-
| `overlay` | `ComputedRef<boolean>` | `true` when the `overlay` flag is set (render as background) |
|
|
805
|
-
| `controls` | `ComputedRef<boolean>` | `true` in the default player; in overlay mode only when the `controls` flag is set |
|
|
806
|
-
| `preload` | `ComputedRef<string>` | Resolved preload value, defaults to `"metadata"` |
|
|
807
|
-
|
|
808
|
-
---
|
|
809
|
-
|
|
810
1066
|
### `usePageList( props )`
|
|
811
1067
|
|
|
812
1068
|
```js
|
|
@@ -878,107 +1134,6 @@ argument arrives as `data…`, in the same way as `videoProps`: `dataFrom` for `
|
|
|
878
1134
|
|
|
879
1135
|
---
|
|
880
1136
|
|
|
881
|
-
### `formatTimestamp( timestamp, options )`
|
|
882
|
-
|
|
883
|
-
```js
|
|
884
|
-
import { formatTimestamp } from "scavold/lib/dateFormat.js";
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
Renders a timestamp the way page lists do, for a theme's own components — a byline
|
|
888
|
-
under a post's title, for instance. Free of Vue and VitePress, so build-time code can
|
|
889
|
-
use it too.
|
|
890
|
-
|
|
891
|
-
| Option | Type | Default | Description |
|
|
892
|
-
|---|---|---|---|
|
|
893
|
-
| `locale` | `string` | the runtime's | Locale to phrase the result in |
|
|
894
|
-
| `dateStyle` | `"short" \| "medium" \| "long" \| "full" \| "iso"` | `"long"` | Style of the date; `iso` writes `2020-10-20` in every language |
|
|
895
|
-
| `timeStyle` | same values | — | Style of the time of day; omitted, only the date is rendered |
|
|
896
|
-
| `timeZone` | `string` | `"UTC"` | IANA name of the zone the timestamp is read in |
|
|
897
|
-
|
|
898
|
-
The result depends on the arguments alone, never on the machine it runs on, so
|
|
899
|
-
pre-rendered markup and the browser agree — a timestamp that changes on hydration
|
|
900
|
-
would tear the markup apart. A timestamp that cannot be read renders as an empty
|
|
901
|
-
string rather than as `Invalid Date`.
|
|
902
|
-
|
|
903
|
-
---
|
|
904
|
-
|
|
905
|
-
### `useContainer( props )`
|
|
906
|
-
|
|
907
|
-
```js
|
|
908
|
-
import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
Shared composable for custom container components. Parses the props emitted by the
|
|
912
|
-
Markdown container renderer and exposes derived values ready for template binding.
|
|
913
|
-
|
|
914
|
-
#### Usage
|
|
915
|
-
|
|
916
|
-
```vue
|
|
917
|
-
<script setup>
|
|
918
|
-
import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
|
|
919
|
-
|
|
920
|
-
const props = defineProps( {
|
|
921
|
-
...containerProps,
|
|
922
|
-
background: String, // custom KV argument: background=/media/hero.jpg
|
|
923
|
-
} );
|
|
924
|
-
|
|
925
|
-
const { containerName, classes, dataAttrs } = useContainer( props );
|
|
926
|
-
</script>
|
|
927
|
-
|
|
928
|
-
<template>
|
|
929
|
-
<section
|
|
930
|
-
:class="[containerName, classes]"
|
|
931
|
-
:style="background ? `--bg: url(${background})` : ''"
|
|
932
|
-
v-bind="dataAttrs"
|
|
933
|
-
>
|
|
934
|
-
<slot />
|
|
935
|
-
</section>
|
|
936
|
-
</template>
|
|
937
|
-
```
|
|
938
|
-
|
|
939
|
-
#### `containerProps`
|
|
940
|
-
|
|
941
|
-
The shared props definition every container component must declare. Spread it into
|
|
942
|
-
`defineProps` to avoid duplicating the base prop declarations.
|
|
943
|
-
|
|
944
|
-
| Prop | Type | Description |
|
|
945
|
-
|---|---|---|
|
|
946
|
-
| `class` | `string` | Space-separated boolean flags from the container opening line |
|
|
947
|
-
| `data-container` | `string` | The container name as written in Markdown |
|
|
948
|
-
|
|
949
|
-
All `key=value` pairs from the opening line are passed as additional props with a
|
|
950
|
-
`data-` prefix (e.g. `background=/img.jpg` → prop `data-background`). Declare them
|
|
951
|
-
explicitly in `defineProps` to use them.
|
|
952
|
-
|
|
953
|
-
Flags arrive **both ways**: as part of `class` and as an empty `data-<flag>` prop, so
|
|
954
|
-
a boolean container parameter can be declared as a prop (this is how `useVideo` reads
|
|
955
|
-
`overlay`, `autoplay` and `loop`) while purely presentational flags keep working as
|
|
956
|
-
class names. A flag a component does not declare falls through into the markup as an
|
|
957
|
-
empty attribute — `::: section highlight` renders `<section class="highlight"
|
|
958
|
-
data-highlight>`.
|
|
959
|
-
|
|
960
|
-
#### Returns
|
|
961
|
-
|
|
962
|
-
| Name | Type | Description |
|
|
963
|
-
|---|---|---|
|
|
964
|
-
| `containerName` | `ComputedRef<string>` | The container name (value of `data-container`) |
|
|
965
|
-
| `rootTag` | `ComputedRef<string>` | `containerName` if it is a sectioning element, otherwise `"div"` |
|
|
966
|
-
| `classes` | `ComputedRef<string>` | The `class` prop value |
|
|
967
|
-
| `dataAttrs` | `ComputedRef<object>` | All `data-*` props as a plain object, suitable for `v-bind` |
|
|
968
|
-
|
|
969
|
-
#### Markdown syntax reminder
|
|
970
|
-
|
|
971
|
-
```markdown
|
|
972
|
-
::: hero dark centered background=/media/hero.jpg
|
|
973
|
-
Content here.
|
|
974
|
-
:::
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
This emits `class="dark centered"`, `data-container="hero"`, and
|
|
978
|
-
`data-background="/media/hero.jpg"` onto the component.
|
|
979
|
-
|
|
980
|
-
---
|
|
981
|
-
|
|
982
1137
|
### `useRedirect()`
|
|
983
1138
|
|
|
984
1139
|
```js
|
|
@@ -1040,6 +1195,8 @@ const label = t("nav.breadcrumb"); // ComputedRef<string>, reactive to locale ch
|
|
|
1040
1195
|
| Key | Default (en) | Description |
|
|
1041
1196
|
|---|---|---|
|
|
1042
1197
|
| `@scavold.nav.breadcrumb` | `"Breadcrumb"` | `aria-label` on the breadcrumb `<nav>` |
|
|
1198
|
+
| `@scavold.cover.play` | `"Play video"` | Label of a cover video's button while the video is paused |
|
|
1199
|
+
| `@scavold.cover.pause` | `"Pause video"` | Label of a cover video's button while the video plays |
|
|
1043
1200
|
|
|
1044
1201
|
#### Providing translations in your theme
|
|
1045
1202
|
|
|
@@ -1079,6 +1236,44 @@ the Scavold default.
|
|
|
1079
1236
|
|
|
1080
1237
|
---
|
|
1081
1238
|
|
|
1239
|
+
### `useVideo( props )`
|
|
1240
|
+
|
|
1241
|
+
```js
|
|
1242
|
+
import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
Composable for custom video components. Resolves the `:::video` container props into
|
|
1246
|
+
clean, typed values ready for template binding. `autoplay` automatically forces `muted`
|
|
1247
|
+
to satisfy browser autoplay policies.
|
|
1248
|
+
|
|
1249
|
+
#### `videoProps`
|
|
1250
|
+
|
|
1251
|
+
Spread into `defineProps` to declare all video-related props at once.
|
|
1252
|
+
|
|
1253
|
+
| Prop | Argument | Description |
|
|
1254
|
+
|---|---|---|
|
|
1255
|
+
| `dataSrc` | `src=…` | Video file URL |
|
|
1256
|
+
| `dataPoster` | `poster=…` | Poster image URL |
|
|
1257
|
+
| `dataAutoplay` | `autoplay` | Present when the `autoplay` flag is set |
|
|
1258
|
+
| `dataLoop` | `loop` | Present when the `loop` flag is set |
|
|
1259
|
+
| `dataMuted` | `muted` | Present when the `muted` flag is set |
|
|
1260
|
+
| `dataPreload` | `preload=…` | One of `"none"`, `"metadata"`, `"auto"` |
|
|
1261
|
+
| `dataLabel` | `label=…` | Accessible label |
|
|
1262
|
+
|
|
1263
|
+
#### Returns
|
|
1264
|
+
|
|
1265
|
+
| Name | Type | Description |
|
|
1266
|
+
|---|---|---|
|
|
1267
|
+
| `src` | `ComputedRef<string>` | Video URL |
|
|
1268
|
+
| `poster` | `ComputedRef<string>` | Poster URL, empty string if absent |
|
|
1269
|
+
| `autoplay` | `ComputedRef<boolean>` | `true` when `autoplay` flag is set |
|
|
1270
|
+
| `loop` | `ComputedRef<boolean>` | `true` when `loop` flag is set |
|
|
1271
|
+
| `muted` | `ComputedRef<boolean>` | `true` when `muted` flag is set or `autoplay` is true |
|
|
1272
|
+
| `preload` | `ComputedRef<string>` | Resolved preload value, defaults to `"metadata"` |
|
|
1273
|
+
| `ariaLabel` | `ComputedRef<string \| undefined>` | Accessible label, `undefined` when the author set none |
|
|
1274
|
+
|
|
1275
|
+
---
|
|
1276
|
+
|
|
1082
1277
|
## Design patterns
|
|
1083
1278
|
|
|
1084
1279
|
---
|
|
@@ -1107,6 +1302,12 @@ their context and pass it through to `<ScavoldImage>`:
|
|
|
1107
1302
|
|
|
1108
1303
|
Content authors pick the component (`HeroImage`, `ThumbImage`, etc.) that matches
|
|
1109
1304
|
their intent. The `sizes` value is baked into the component and invisible to them.
|
|
1305
|
+
The built-in [`<ScavoldCover>`](#scavoldcover) works this way.
|
|
1306
|
+
|
|
1307
|
+
A container argument declared as `media-file` that names an image reaches the
|
|
1308
|
+
component with everything it needs for this: besides its URL, as `data-<arg>`, the
|
|
1309
|
+
build adds `data-<arg>-srcset`, `data-<arg>-webp-srcset`, `data-<arg>-width` and
|
|
1310
|
+
`data-<arg>-height` — the variants and the upright dimensions of the source.
|
|
1110
1311
|
|
|
1111
1312
|
**Why not measure at runtime?** A `ResizeObserver`-based approach that reads the
|
|
1112
1313
|
container's pixel size and sets `src` dynamically is accurate but has serious
|
|
@@ -1119,4 +1320,3 @@ The global `image_sizes` in `.cratly.config.yaml` is a fallback for images that
|
|
|
1119
1320
|
no layout-aware component wrapping them. Set it to the most common case in the
|
|
1120
1321
|
theme (typically the prose column width) and override via dedicated components for
|
|
1121
1322
|
everything else.
|
|
1122
|
-
|