create-bearminion 1.2.0 → 1.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 +7 -0
- package/README.md +3 -1
- package/package.json +2 -2
- package/template/README.md +1 -1
- package/template/package.json +1 -1
- package/template/src/components/sections/media/Media.astro +2 -0
- package/template/src/components/sections/media/MediaPlayer.astro +18 -3
- package/template/src/lib/image-url.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# create-bearminion
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 755bad0: The Media section's video cover is a responsive image (a WebP `srcset` from 480 to 1920 pixels wide), so a phone no longer downloads the full-size poster, and it loads at once instead of lazily when the section is one of the first two on the page, where it is often the largest thing on screen.
|
|
8
|
+
- 0a2301e: The package pages link to the new website and documentation: the npm homepage is https://bearminion.com, and the READMEs (including the template's) point to https://docs.bearminion.com instead of Markdown files on GitHub.
|
|
9
|
+
|
|
3
10
|
## 1.2.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -58,8 +58,10 @@ The [default BearMinion template](https://github.com/langehelp-software/bearmini
|
|
|
58
58
|
|
|
59
59
|
## Learn more
|
|
60
60
|
|
|
61
|
+
- [bearminion.com](https://bearminion.com), with a demo of the editor
|
|
62
|
+
- [Getting started](https://docs.bearminion.com/getting-started/): create, edit and put your site online
|
|
63
|
+
- [Documentation](https://docs.bearminion.com)
|
|
61
64
|
- [BearMinion on GitHub](https://github.com/langehelp-software/bearminion)
|
|
62
|
-
- [Documentation](https://github.com/langehelp-software/bearminion/blob/main/docs/README.md)
|
|
63
65
|
|
|
64
66
|
## License
|
|
65
67
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bearminion",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Create a BearMinion site: an Astro website you edit by clicking on it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jakub Lange",
|
|
7
|
-
"homepage": "https://
|
|
7
|
+
"homepage": "https://bearminion.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/langehelp-software/bearminion.git",
|
package/template/README.md
CHANGED
|
@@ -200,7 +200,7 @@ A variant change, a new, duplicated, moved, or deleted section, and a sidebar va
|
|
|
200
200
|
|
|
201
201
|
Header links nest up to three levels (`MAX_NAV_DEPTH`). Desktop navigation uses hover and focus flyouts. The mobile menu uses an accordion. The link picker lists pages, `#anchor` on the current page, `/page#anchor` on other pages, and a custom URL.
|
|
202
202
|
|
|
203
|
-
The API is `/__bearminion/api` on localhost. Saving `content/*.json` asks the editor to reload. If the draft is still dirty, the editor asks to save, discard, or cancel first. Unsaved drafts are kept in `localStorage` until save or discard. An upload does not reload by itself, so the draft can preview the new file before save. Request and response shapes, with curl examples, are in the [Dev API docs](https://
|
|
203
|
+
The API is `/__bearminion/api` on localhost. Saving `content/*.json` asks the editor to reload. If the draft is still dirty, the editor asks to save, discard, or cancel first. Unsaved drafts are kept in `localStorage` until save or discard. An upload does not reload by itself, so the draft can preview the new file before save. Request and response shapes, with curl examples, are in the [Dev API docs](https://docs.bearminion.com/dev-api/), and every sidebar tab is described in [the editor docs](https://docs.bearminion.com/editor/).
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
206
|
pnpm dev
|
package/template/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { Image } from 'bearminion'
|
|
3
|
-
import { imageUrl } from '../../../lib/image-url'
|
|
3
|
+
import { imageSrcset, imageUrl } from '../../../lib/image-url'
|
|
4
4
|
import { cn } from '../../../lib/cn'
|
|
5
5
|
import MediaFrame from '../../molecules/MediaFrame.astro'
|
|
6
6
|
import type { MediaProps } from './definition'
|
|
@@ -15,10 +15,12 @@ interface Props {
|
|
|
15
15
|
label?: string
|
|
16
16
|
/** `data-field` path for the image, so a click on the frame opens the image picker. */
|
|
17
17
|
imageField: string
|
|
18
|
+
/** Near the top of the page: the cover loads at once instead of lazily, since it is likely the largest thing on screen. */
|
|
19
|
+
eager?: boolean
|
|
18
20
|
class?: string
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
const { kind, aspect, video, poster, image, label, imageField, class: className } = Astro.props
|
|
23
|
+
const { kind, aspect, video, poster, image, label, imageField, eager = false, class: className } = Astro.props
|
|
22
24
|
const ASPECT = { '16:9': 'aspect-video', '4:3': 'aspect-[4/3]', '21:9': 'aspect-[21/9]', '1:1': 'aspect-square' } as const
|
|
23
25
|
const frame = cn(
|
|
24
26
|
'relative w-full min-h-48 overflow-hidden rounded-[clamp(1rem,2cqi,1.5rem)] bg-surface shadow-[inset_0_0_0_1px_var(--line),0_40px_80px_-48px_oklch(0.2_0.02_255/0.35)]',
|
|
@@ -29,6 +31,9 @@ const source = kind === 'video' && video ? parseMediaSource(video) : null
|
|
|
29
31
|
const hasImage = kind === 'image' && Boolean(image?.src) && image?.src !== 'placeholder' && !image?.hidden
|
|
30
32
|
const empty = kind === 'video' ? !source : !hasImage
|
|
31
33
|
const cover = source ? ((await imageUrl(poster)) ?? (source.kind === 'youtube' ? source.thumbnail : undefined)) : undefined
|
|
34
|
+
const coverSrcset = source ? await imageSrcset(poster) : undefined
|
|
35
|
+
// The frame is at most 68rem wide and the full width of a phone.
|
|
36
|
+
const coverSizes = '(min-width: 72rem) 68rem, 100vw'
|
|
32
37
|
const name = label || 'Video'
|
|
33
38
|
---
|
|
34
39
|
|
|
@@ -68,7 +73,17 @@ const name = label || 'Video'
|
|
|
68
73
|
data-label={name}
|
|
69
74
|
data-poster={source.kind === 'file' ? cover : undefined}
|
|
70
75
|
>
|
|
71
|
-
{cover &&
|
|
76
|
+
{cover && (
|
|
77
|
+
<img
|
|
78
|
+
src={cover}
|
|
79
|
+
srcset={coverSrcset}
|
|
80
|
+
sizes={coverSrcset ? coverSizes : undefined}
|
|
81
|
+
alt=""
|
|
82
|
+
class="absolute inset-0 size-full object-cover"
|
|
83
|
+
loading={eager ? 'eager' : 'lazy'}
|
|
84
|
+
decoding="async"
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
72
87
|
<button
|
|
73
88
|
type="button"
|
|
74
89
|
class="group absolute inset-0 grid cursor-pointer place-items-center bg-[oklch(0.15_0.01_255/0.12)] transition-colors hover:bg-[oklch(0.15_0.01_255/0.22)] focus-visible:outline-2 focus-visible:outline-offset-[-4px] focus-visible:outline-accent"
|
|
@@ -7,6 +7,23 @@ import { isRemote, resolveLocalAsset } from './assets'
|
|
|
7
7
|
* optimized to WebP; a file uploaded in this dev session is served by the editor's dev route; a remote URL is used as
|
|
8
8
|
* it is. A missing file in `src/assets` fails the build, like a missing `<Image>`.
|
|
9
9
|
*/
|
|
10
|
+
/** Widths for a responsive `srcset`; none wider than the file itself. */
|
|
11
|
+
const SRCSET_WIDTHS = [480, 768, 1080, 1440, 1920]
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* An image as a WebP `srcset`, for a plain `<img>` that is not an Astro `<Image>`, such as a video's cover. Undefined
|
|
15
|
+
* when the image is not a file in `src/assets` (a remote URL or a dev upload): then `imageUrl` alone is used.
|
|
16
|
+
*/
|
|
17
|
+
export async function imageSrcset(image: Image | undefined): Promise<string | undefined> {
|
|
18
|
+
if (!image || image.hidden || !image.src || image.src === 'placeholder' || isRemote(image.src)) return undefined
|
|
19
|
+
const asset = resolveLocalAsset(image.src.replace(/^\//, ''))
|
|
20
|
+
if (!asset) return undefined
|
|
21
|
+
const widths = SRCSET_WIDTHS.filter((width) => width < asset.width)
|
|
22
|
+
widths.push(Math.min(asset.width, SRCSET_WIDTHS.at(-1) ?? asset.width))
|
|
23
|
+
const sources = await Promise.all(widths.map(async (width) => `${(await getImage({ src: asset, width, format: 'webp' })).src} ${width}w`))
|
|
24
|
+
return sources.join(', ')
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
export async function imageUrl(image: Image | undefined, maxWidth = 1920): Promise<string | undefined> {
|
|
11
28
|
if (!image || image.hidden || !image.src || image.src === 'placeholder') return undefined
|
|
12
29
|
if (isRemote(image.src)) return image.src
|