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