kempo-media 0.2.3
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/.env.example +1 -0
- package/.gitattributes +5 -0
- package/.github/workflows/publish.yml +85 -0
- package/.github/workflows/test.yml +123 -0
- package/CHANGELOG.md +25 -0
- package/README.md +114 -0
- package/admin/asset-picker.global.html +63 -0
- package/admin/components/MediaLibrary.js +304 -0
- package/admin/icons/archive.svg +1 -0
- package/admin/icons/audio.svg +1 -0
- package/admin/icons/image.svg +1 -0
- package/admin/icons/model3d.svg +1 -0
- package/admin/icons/video.svg +1 -0
- package/admin/index.page.html +35 -0
- package/admin/nav.global.html +3 -0
- package/docker-compose.yml +22 -0
- package/drizzle.config.js +26 -0
- package/install.js +18 -0
- package/kempo-config.json +18 -0
- package/package.json +39 -0
- package/public/api/media/DELETE.js +21 -0
- package/public/api/media/GET.js +38 -0
- package/public/api/media/PATCH.js +18 -0
- package/public/api/media/POST.js +57 -0
- package/public/sdk.js +62 -0
- package/scripts/build.js +0 -0
- package/sdk.js +15 -0
- package/server/db/schema.js +18 -0
- package/server/utils/media/createMediaAsset.js +19 -0
- package/server/utils/media/deleteMediaAsset.js +37 -0
- package/server/utils/media/getMediaAsset.js +14 -0
- package/server/utils/media/getMediaAssets.js +39 -0
- package/server/utils/media/updateMediaAsset.js +32 -0
- package/server/utils/uploads/fileTypes.js +50 -0
- package/server/utils/uploads/generateThumbnail.js +63 -0
- package/server/utils/uploads/parseMultipart.js +99 -0
- package/server/utils/uploads/storeUpload.js +89 -0
- package/tests/browser-fixtures.node-test.js +61 -0
- package/tests/browser-modules-parse.node-test.js +62 -0
- package/tests/components/MediaLibrary.browser-test.js +412 -0
- package/tests/fixtures/kempo-media-sdk-mock/sdk.js +16 -0
- package/tests/media-api-contract.node-test.js +363 -0
- package/tests/media-data-layer.node-test.js +268 -0
- package/tests/module-graph.node-test.js +86 -0
- package/tests/package-invariants.node-test.js +247 -0
- package/tests/permission-name-consistency.node-test.js +102 -0
- package/tests/uploads.node-test.js +255 -0
- package/update.js +3 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DATABASE_URL=postgresql://kempo:kempo_media_test_password@localhost:5435/kempo_media_test
|
package/.gitattributes
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Normalise every text file to LF in the repository, and check it out with the
|
|
2
|
+
# platform's native endings. Without this, a Windows build writes CRLF into files that
|
|
3
|
+
# checkout wrote as LF, so git reports them as modified even though the content is
|
|
4
|
+
# identical — `git diff` shows nothing while `git status` shows a change.
|
|
5
|
+
* text=auto
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: Publish to npmjs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
version-type:
|
|
10
|
+
description: 'Version bump type'
|
|
11
|
+
required: true
|
|
12
|
+
type: choice
|
|
13
|
+
options:
|
|
14
|
+
- patch
|
|
15
|
+
- minor
|
|
16
|
+
- major
|
|
17
|
+
default: patch
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
publish:
|
|
21
|
+
# [skip publish] lands a commit on master without releasing while still running the tests,
|
|
22
|
+
# which a shared [skip ci] marker cannot do since that skips the test workflow too.
|
|
23
|
+
# A manual run always publishes.
|
|
24
|
+
if: "github.event_name == 'workflow_dispatch' || !(contains(github.event.head_commit.message, '[skip ci]') || contains(github.event.head_commit.message, '[skip publish]'))"
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
contents: write
|
|
28
|
+
id-token: write
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-node@v4
|
|
35
|
+
with:
|
|
36
|
+
node-version: '20.x'
|
|
37
|
+
|
|
38
|
+
- run: npm ci
|
|
39
|
+
|
|
40
|
+
- name: Build project and documentation
|
|
41
|
+
run: |
|
|
42
|
+
npm run build
|
|
43
|
+
|
|
44
|
+
# kempo/kempo-ui/kempo-server are peer dependencies and are not installed by npm ci above.
|
|
45
|
+
# The published release is the right one to test against: it is what a site installing this
|
|
46
|
+
# extension will actually pair it with.
|
|
47
|
+
- name: Install the host for the test gate
|
|
48
|
+
run: npm install --no-save kempo@latest kempo-ui@latest kempo-server@latest
|
|
49
|
+
|
|
50
|
+
- name: Run Unit Tests
|
|
51
|
+
run: npm test
|
|
52
|
+
|
|
53
|
+
- name: Set version type
|
|
54
|
+
id: version
|
|
55
|
+
run: |
|
|
56
|
+
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
|
57
|
+
echo "type=${{ inputs.version-type }}" >> $GITHUB_OUTPUT
|
|
58
|
+
else
|
|
59
|
+
echo "type=patch" >> $GITHUB_OUTPUT
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
- name: Bump ${{ steps.version.outputs.type }} version
|
|
63
|
+
run: |
|
|
64
|
+
git config --global user.name "github-actions"
|
|
65
|
+
git config --global user.email "github-actions@github.com"
|
|
66
|
+
npm version ${{ steps.version.outputs.type }} --no-git-tag-version
|
|
67
|
+
git add -A || true
|
|
68
|
+
git commit -m "ci: bump ${{ steps.version.outputs.type }} version [skip ci]" || echo "No changes to commit"
|
|
69
|
+
# Must be the branch that triggered this run — hardcoding a branch name silently drifts
|
|
70
|
+
# git and npm apart the day the default branch changes.
|
|
71
|
+
git push origin HEAD:${{ github.ref_name }}
|
|
72
|
+
env:
|
|
73
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
74
|
+
|
|
75
|
+
- name: Setup .npmrc for OIDC
|
|
76
|
+
run: |
|
|
77
|
+
cat << EOF > ~/.npmrc
|
|
78
|
+
//registry.npmjs.org/:_authToken=\${NPM_TOKEN}
|
|
79
|
+
registry=https://registry.npmjs.org/
|
|
80
|
+
always-auth=true
|
|
81
|
+
EOF
|
|
82
|
+
|
|
83
|
+
- run: npm publish --access public
|
|
84
|
+
env:
|
|
85
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
name: Run Unit Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
pull_request:
|
|
9
|
+
branches:
|
|
10
|
+
- main
|
|
11
|
+
- master
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
# kempo/kempo-ui/kempo-server resolve from the registry, which is what a site installing this
|
|
15
|
+
# extension gets. Peer dependencies are installed automatically by npm ci.
|
|
16
|
+
published:
|
|
17
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
services:
|
|
20
|
+
# The API-contract and data-layer suites need a real database; they skip themselves when
|
|
21
|
+
# none is reachable, so this is what makes them actually run rather than silently pass.
|
|
22
|
+
postgres:
|
|
23
|
+
image: postgres:16-alpine
|
|
24
|
+
env:
|
|
25
|
+
POSTGRES_USER: kempo
|
|
26
|
+
POSTGRES_PASSWORD: kempo
|
|
27
|
+
POSTGRES_DB: kempo_media_test
|
|
28
|
+
ports:
|
|
29
|
+
- 5432:5432
|
|
30
|
+
options: >-
|
|
31
|
+
--health-cmd pg_isready
|
|
32
|
+
--health-interval 10s
|
|
33
|
+
--health-timeout 5s
|
|
34
|
+
--health-retries 5
|
|
35
|
+
env:
|
|
36
|
+
DATABASE_URL: postgresql://kempo:kempo@localhost:5432/kempo_media_test
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- uses: actions/setup-node@v4
|
|
41
|
+
with:
|
|
42
|
+
node-version: '22.x'
|
|
43
|
+
|
|
44
|
+
- run: npm ci
|
|
45
|
+
|
|
46
|
+
# kempo/kempo-ui/kempo-server are peer dependencies, which npm ci does not install.
|
|
47
|
+
- name: Install the host for the test gate
|
|
48
|
+
run: npm install --no-save kempo@latest kempo-ui@latest kempo-server@latest
|
|
49
|
+
|
|
50
|
+
- name: Apply database schema
|
|
51
|
+
run: npx drizzle-kit push --force
|
|
52
|
+
|
|
53
|
+
- name: Run Unit Tests
|
|
54
|
+
run: npm test
|
|
55
|
+
|
|
56
|
+
# Same suite against the sibling checkout of the host. This extension resolves imports out of
|
|
57
|
+
# kempo, so it breaks whenever the host moves something it reaches into — this job is what
|
|
58
|
+
# surfaces that before a release, and it is advisory rather than blocking.
|
|
59
|
+
siblings:
|
|
60
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
continue-on-error: true
|
|
63
|
+
services:
|
|
64
|
+
# Without this the database-backed suites skip themselves, which reads as a pass.
|
|
65
|
+
postgres:
|
|
66
|
+
image: postgres:16-alpine
|
|
67
|
+
env:
|
|
68
|
+
POSTGRES_USER: kempo
|
|
69
|
+
POSTGRES_PASSWORD: kempo
|
|
70
|
+
POSTGRES_DB: kempo_media_test
|
|
71
|
+
ports:
|
|
72
|
+
- 5432:5432
|
|
73
|
+
options: >-
|
|
74
|
+
--health-cmd pg_isready
|
|
75
|
+
--health-interval 10s
|
|
76
|
+
--health-timeout 5s
|
|
77
|
+
--health-retries 5
|
|
78
|
+
env:
|
|
79
|
+
DATABASE_URL: postgresql://kempo:kempo@localhost:5432/kempo_media_test
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
with:
|
|
83
|
+
path: kempo-media
|
|
84
|
+
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
with:
|
|
87
|
+
repository: dustinpoissant/kempo
|
|
88
|
+
path: kempo
|
|
89
|
+
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
with:
|
|
92
|
+
repository: dustinpoissant/kempo-server
|
|
93
|
+
path: kempo-server
|
|
94
|
+
|
|
95
|
+
# kempo's own link:local script links both kempo-server and kempo-ui as siblings, so this
|
|
96
|
+
# checkout needs to exist even though kempo-media itself doesn't import kempo-ui server-side.
|
|
97
|
+
- uses: actions/checkout@v4
|
|
98
|
+
with:
|
|
99
|
+
repository: dustinpoissant/kempo-ui
|
|
100
|
+
path: kempo-ui
|
|
101
|
+
|
|
102
|
+
- uses: actions/setup-node@v4
|
|
103
|
+
with:
|
|
104
|
+
node-version: '22.x'
|
|
105
|
+
|
|
106
|
+
- name: Install host
|
|
107
|
+
run: npm ci && npm run link:local
|
|
108
|
+
working-directory: kempo
|
|
109
|
+
|
|
110
|
+
- run: npm ci
|
|
111
|
+
working-directory: kempo-media
|
|
112
|
+
|
|
113
|
+
- name: Link sibling checkouts
|
|
114
|
+
run: npm run link:local
|
|
115
|
+
working-directory: kempo-media
|
|
116
|
+
|
|
117
|
+
- name: Apply database schema
|
|
118
|
+
run: npx drizzle-kit push --force
|
|
119
|
+
working-directory: kempo-media
|
|
120
|
+
|
|
121
|
+
- name: Run Unit Tests
|
|
122
|
+
run: npm test
|
|
123
|
+
working-directory: kempo-media
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2026-08-13
|
|
8
|
+
### Added
|
|
9
|
+
- **Uploads.** `POST /kempo-media/api/media` accepts a `multipart/form-data` upload, validates its type, stores it under a generated name in the site's own `public/media/uploads/`, and records it. The multipart parser is written here rather than pulled in: the usual libraries stream a body that is still arriving, and kempo-server has already buffered the whole thing by the time a route runs. It works entirely on Buffers, so file content is never decoded — decoding it as UTF-8 is exactly the corruption kempo-server 3.2.0 fixed.
|
|
10
|
+
- **Thumbnails for images**, via `sharp`, imported lazily so a failed native build degrades to "no preview" rather than an extension that will not load. Other kinds get a type icon.
|
|
11
|
+
- **Browse, rename, edit alt text and delete**, over `GET`/`PATCH`/`DELETE` on the same route, each behind its own permission. Deleting removes the files as well as the row, and tolerates a file that has already gone from disk rather than leaving an undeletable entry.
|
|
12
|
+
- **The media library admin screen** at *Media*: a thumbnail grid with search, type filtering, drag-and-drop upload with per-file progress, and delete. Uploads run one at a time — bodies are buffered in memory server-side, so sending ten large files at once is the surest way to exhaust it.
|
|
13
|
+
- **An editor picker.** `admin/asset-picker.global.html` installs `window.kempo.openAssetPicker`, so kempo-ui's image control shows a **Browse…** button that opens the same library. Nothing in kempo core needed changing: it already scans every enabled extension's `admin/` directory for `*.global.html` on each render.
|
|
14
|
+
- **A parse check over every browser module.** Those files import absolute `/kempo-ui/…` URLs, so nothing else in the node suite loads them, and a backtick inside a comment in an `html``/`css`` literal terminates the literal and stops the module parsing — the element then silently never upgrades. `node --check` catches it without a browser.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
- **SVG is not an accepted upload type.** kempo-server serves it as inline unsanitised markup on the site's own origin, making an uploaded SVG with a `<script>` stored XSS against the admin who opens it.
|
|
18
|
+
- **Supplied filenames never reach the filesystem.** Names on disk are generated; the original is kept for display only, which makes `../../../../etc/passwd.png` a harmless label rather than a traversal.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Requires `kempo-server` >= 3.2.0 and, for the editor picker only, `kempo-ui` >= 0.4.43.
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - 2026-08-12
|
|
24
|
+
### Added
|
|
25
|
+
- Initial extension scaffold: `kempo-config.json` (permissions, settings, groups), the `kempoMediaAsset` table, `install.js`/`update.js`, admin nav registration, and the baseline test suite (module graph, package invariants, permission-name consistency).
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# kempo-media
|
|
2
|
+
|
|
3
|
+
Media library extension for [kempo](https://github.com/dustinpoissant/kempo) — upload and manage images, video, audio, 3D models and archives, with a picker wired into the editor's image control.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **A media library in the admin**, at *Media* in the sidebar: a grid of thumbnails with search, filtering by type, drag-and-drop upload with per-file progress, and delete.
|
|
8
|
+
- **A picker in the editor.** kempo-ui's image control grows a **Browse…** button that opens the library, so an author inserts an image by looking at it instead of remembering its URL.
|
|
9
|
+
- **Thumbnails for images**, generated on upload. Other kinds show a type icon.
|
|
10
|
+
- **Range-request support for free.** Uploads are stored in your site's own `public/`, so kempo-server serves them directly and video and audio seek properly.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
| Package | Version | Why |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `kempo` | >= 4.2.26 | Extension host |
|
|
17
|
+
| `kempo-server` | >= 3.2.0 | First release with binary-safe request bodies. On anything earlier, uploaded files arrive corrupted. |
|
|
18
|
+
| `kempo-ui` | >= 0.4.43 | First release with the `window.kempo.openAssetPicker` hook the editor picker uses. The library itself works without it. |
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
npm install kempo-media
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then install it from kempo's admin under **Extensions**. On install it creates `public/media/uploads/` and `public/media/thumbnails/` in your site.
|
|
27
|
+
|
|
28
|
+
## Permissions
|
|
29
|
+
|
|
30
|
+
| Name | Grants |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `media:browse` | List and view library assets |
|
|
33
|
+
| `media:upload` | Upload new assets |
|
|
34
|
+
| `media:update` | Edit alt text and rename |
|
|
35
|
+
| `media:delete` | Delete assets |
|
|
36
|
+
|
|
37
|
+
A `kempo-media:editor` group is created granting browse, upload and update — but not delete.
|
|
38
|
+
|
|
39
|
+
## Settings
|
|
40
|
+
|
|
41
|
+
| Name | Default | Description |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `max_upload_size_mb` | `250` | Largest single file accepted |
|
|
44
|
+
| `thumbnail_max_dimension` | `480` | Longest edge of generated thumbnails, in pixels |
|
|
45
|
+
|
|
46
|
+
`max_upload_size_mb` sits **below** kempo-server's own `maxBodySize` (500 MB by default), which is the real ceiling. Raising this setting above that has no effect; the request is rejected before this extension sees it.
|
|
47
|
+
|
|
48
|
+
## Accepted file types
|
|
49
|
+
|
|
50
|
+
| Kind | Extensions |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Image | `png` `jpg` `jpeg` `gif` `webp` `avif` |
|
|
53
|
+
| Video | `mp4` `m4v` `webm` `ogv` |
|
|
54
|
+
| Audio | `mp3` `ogg` `wav` |
|
|
55
|
+
| 3D model | `glb` `gltf` `obj` `fbx` |
|
|
56
|
+
| Archive | `zip` |
|
|
57
|
+
|
|
58
|
+
**SVG is deliberately not accepted.** kempo-server serves SVG as inline, unsanitised markup on your site's own origin, so an uploaded SVG carrying a `<script>` would be stored XSS against the admin who opens it. Supporting it needs a sanitiser first, not just an entry in the list.
|
|
59
|
+
|
|
60
|
+
## Using assets from your own code
|
|
61
|
+
|
|
62
|
+
Uploaded files are served from your site root, so an asset's `path` is its URL:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { getMediaAssets } from 'kempo-media/sdk';
|
|
66
|
+
|
|
67
|
+
const [error, data] = await getMediaAssets({ kind: 'image', limit: 10 });
|
|
68
|
+
// data.assets[0].path === 'media/uploads/a1b2c3d4e5f60718.png' -> /media/uploads/...
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The same functions are available in the browser from `/kempo-media/sdk.js`, plus `uploadMedia(file, { alt }, onProgress)`.
|
|
72
|
+
|
|
73
|
+
## Providing your own picker
|
|
74
|
+
|
|
75
|
+
The editor hook is just a global. Any extension can install one, and the last one to load wins:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
window.kempo.openAssetPicker = async ({ alt }) => ({ url: '/some/image.png', alt });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Resolve `null` to insert nothing. See kempo-ui's markdown-editor documentation for the full contract.
|
|
82
|
+
|
|
83
|
+
## Local development
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
npm run link:local # links sibling ../kempo and ../kempo-ui checkouts
|
|
87
|
+
npm test
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Note that `npm install <anything>` in a consuming project drops packages linked with `--no-save`, so re-run `link:local` after one.
|
|
91
|
+
|
|
92
|
+
`npm test` runs both node and headless-browser tests (Puppeteer downloads its own Chromium on `npm install`, nothing extra to set up). The browser suites cover `admin/components/MediaLibrary.js` — a real Lit component that imports `/kempo-ui/...` and `/kempo-media/sdk.js` by absolute URL, the same way it does on a live site. `tests/browser-fixtures.node-test.js` runs first (node tests always finish before any browser test starts) and links `kempo-ui` and `kempo-media` directories at the repo root so those imports resolve against the test server; `kempo-media` there points at a fake `sdk.js` (`tests/fixtures/kempo-media-sdk-mock/`) that each test controls via `window.__mediaSdkMock`, not the real one, since these tests don't boot a server.
|
|
93
|
+
|
|
94
|
+
### Running the database-backed tests
|
|
95
|
+
|
|
96
|
+
Two suites (`tests/media-api-contract.node-test.js`, `tests/media-data-layer.node-test.js`) drive this extension against a real Postgres — one installs it and hits its actual HTTP routes, the other exercises the CRUD layer directly. Both skip themselves with a clear message when no database is reachable, so `npm test` is safe to run without one. To actually run them:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
docker compose up -d # starts kempo-media-test-db on localhost:5435
|
|
100
|
+
export DATABASE_URL=$(grep DATABASE_URL .env.example | cut -d= -f2-)
|
|
101
|
+
npx drizzle-kit push --force # creates kempo's core tables + kempoMediaAsset
|
|
102
|
+
npm test
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`DATABASE_URL` needs to be a real environment variable for both `drizzle-kit` and `npm test` — this repo has no dotenv step of its own to load a `.env` file, so `.env.example` is a copy-paste reference, not something read automatically.
|
|
106
|
+
|
|
107
|
+
`drizzle.config.js` here exists only for this — the table itself is created for real via `createTablesFromSchema` at install time, the same as it would be on a live site.
|
|
108
|
+
|
|
109
|
+
## Known limits
|
|
110
|
+
|
|
111
|
+
- **Uploads are buffered in memory, not streamed to disk.** kempo-server reads the whole request body before a route runs, so a very large upload is bounded by available RAM rather than disk. `maxBodySize` is the ceiling.
|
|
112
|
+
- **Thumbnails are images only.** No video frame extraction or waveforms — that needs ffmpeg and a job queue, neither of which exists here.
|
|
113
|
+
- **`sharp` is a native module.** It is the one dependency that can fail to install on an unusual host. It is imported lazily, so if it is missing you get uploads without thumbnails rather than a broken extension.
|
|
114
|
+
- **No usage tracking.** Deleting an asset does not check whether a page links to it.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<content id="kempo-media-asset-picker" name="Media asset picker" owner="kempo-media" enabled="true" locked="true" location="scripts" priority="0">
|
|
2
|
+
<script type="module">
|
|
3
|
+
/*
|
|
4
|
+
Installs the picker kempo-ui's image control looks for. kempo core scans every enabled
|
|
5
|
+
extension's admin/ directory for *.global.html on each admin render, so this lands in the
|
|
6
|
+
scripts location of every admin page with no changes to core and no registration step.
|
|
7
|
+
|
|
8
|
+
The hook is read when the control's dropdown opens rather than at page load, so setting it here
|
|
9
|
+
is early enough regardless of which loads first.
|
|
10
|
+
*/
|
|
11
|
+
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
12
|
+
import '/admin/extension/kempo-media/components/MediaLibrary.js';
|
|
13
|
+
|
|
14
|
+
window.kempo = window.kempo || {};
|
|
15
|
+
|
|
16
|
+
window.kempo.openAssetPicker = ({ alt = '' } = {}) => new Promise(resolve => {
|
|
17
|
+
const library = document.createElement('kempo-media-library');
|
|
18
|
+
library.selectable = true;
|
|
19
|
+
library.canUpload = true; // uploading and then immediately using it is the common flow
|
|
20
|
+
library.kind = 'image'; // the caller is the image control; other kinds are a click away
|
|
21
|
+
library.style.minWidth = 'min(48rem, 80vw)';
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
resolve() is idempotent by contract but the paths overlap — a double-click confirms and then
|
|
25
|
+
the dialog closes, firing closeCallback too. Guarding here keeps the first answer.
|
|
26
|
+
*/
|
|
27
|
+
let settled = false;
|
|
28
|
+
const settle = value => {
|
|
29
|
+
if(settled) return;
|
|
30
|
+
settled = true;
|
|
31
|
+
resolve(value);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const chosen = asset => ({
|
|
35
|
+
url: `/${asset.path}`,
|
|
36
|
+
// The asset's own alt text describes the file; fall back to whatever was already typed
|
|
37
|
+
alt: asset.altText || alt
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
library.addEventListener('asset-confirmed', e => {
|
|
41
|
+
settle(chosen(e.detail.asset));
|
|
42
|
+
dialog?.close();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const dialog = Dialog.create(library, {
|
|
46
|
+
title: 'Select media',
|
|
47
|
+
confirmText: 'Insert',
|
|
48
|
+
cancelText: 'Cancel',
|
|
49
|
+
confirmAction: event => {
|
|
50
|
+
const asset = library.selectedAsset;
|
|
51
|
+
if(!asset){
|
|
52
|
+
// Nothing picked yet — keep the dialog up rather than silently inserting nothing
|
|
53
|
+
event.keepDialogOpen = true;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
settle(chosen(asset));
|
|
57
|
+
},
|
|
58
|
+
// Fires on every dismissal route: cancel, the close button, the overlay, and Escape
|
|
59
|
+
closeCallback: () => settle(null)
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
</script>
|
|
63
|
+
</content>
|