@openrails/contentkit-ui 0.0.0-stage → 0.68.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/README.md +470 -2
- package/dist/appearance.d.ts +38 -0
- package/dist/appearance.js +15 -0
- package/dist/appearance.js.map +1 -0
- package/dist/assets/hash.worker-BYfo-7MQ.js.map +1 -0
- package/dist/client/aspect.d.ts +6 -0
- package/dist/client/aspect.js +17 -0
- package/dist/client/aspect.js.map +1 -0
- package/dist/client/client.d.ts +53 -0
- package/dist/client/client.js +36 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/codes.d.ts +12 -0
- package/dist/client/codes.js +19 -0
- package/dist/client/codes.js.map +1 -0
- package/dist/client/content/bans.d.ts +15 -0
- package/dist/client/content/bans.js +40 -0
- package/dist/client/content/bans.js.map +1 -0
- package/dist/client/content/comments.d.ts +36 -0
- package/dist/client/content/comments.js +95 -0
- package/dist/client/content/comments.js.map +1 -0
- package/dist/client/content/favorites.d.ts +14 -0
- package/dist/client/content/favorites.js +42 -0
- package/dist/client/content/favorites.js.map +1 -0
- package/dist/client/content/moderation.d.ts +18 -0
- package/dist/client/content/moderation.js +37 -0
- package/dist/client/content/moderation.js.map +1 -0
- package/dist/client/content/polls.d.ts +47 -0
- package/dist/client/content/polls.js +151 -0
- package/dist/client/content/polls.js.map +1 -0
- package/dist/client/content/posts.d.ts +44 -0
- package/dist/client/content/posts.js +92 -0
- package/dist/client/content/posts.js.map +1 -0
- package/dist/client/content/reactions.d.ts +12 -0
- package/dist/client/content/reactions.js +36 -0
- package/dist/client/content/reactions.js.map +1 -0
- package/dist/client/content/types.d.ts +91 -0
- package/dist/client/content/types.js +8 -0
- package/dist/client/content/types.js.map +1 -0
- package/dist/client/crop.d.ts +32 -0
- package/dist/client/crop.js +103 -0
- package/dist/client/crop.js.map +1 -0
- package/dist/client/errors.d.ts +54 -0
- package/dist/client/errors.js +80 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/gallery.d.ts +43 -0
- package/dist/client/gallery.js +86 -0
- package/dist/client/gallery.js.map +1 -0
- package/dist/client/generated/error-codes.d.ts +5 -0
- package/dist/client/generated/error-codes.js +131 -0
- package/dist/client/generated/error-codes.js.map +1 -0
- package/dist/client/generated/routes.d.ts +1086 -0
- package/dist/client/generated/routes.js +1400 -0
- package/dist/client/generated/routes.js.map +1 -0
- package/dist/client/generated/wire.d.ts +630 -0
- package/dist/client/generated/wire.js +6 -0
- package/dist/client/generated/wire.js.map +1 -0
- package/dist/client/http.d.ts +83 -0
- package/dist/client/http.js +82 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/image.d.ts +24 -0
- package/dist/client/image.js +65 -0
- package/dist/client/image.js.map +1 -0
- package/dist/client/index.d.ts +33 -0
- package/dist/client/media/api.d.ts +36 -0
- package/dist/client/media/api.js +61 -0
- package/dist/client/media/api.js.map +1 -0
- package/dist/client/media/client.d.ts +247 -0
- package/dist/client/media/client.js +407 -0
- package/dist/client/media/client.js.map +1 -0
- package/dist/client/media/encode.d.ts +3 -0
- package/dist/client/media/encode.js +8 -0
- package/dist/client/media/encode.js.map +1 -0
- package/dist/client/media/hash-stream.d.ts +6 -0
- package/dist/client/media/hash-stream.js +16 -0
- package/dist/client/media/hash-stream.js.map +1 -0
- package/dist/client/media/hash.d.ts +12 -0
- package/dist/client/media/hash.js +48 -0
- package/dist/client/media/hash.js.map +1 -0
- package/dist/client/media/hash.worker-inline.js +18 -0
- package/dist/client/media/hash.worker-inline.js.map +1 -0
- package/dist/client/media/pacer.d.ts +21 -0
- package/dist/client/media/pacer.js +25 -0
- package/dist/client/media/pacer.js.map +1 -0
- package/dist/client/media/queue.d.ts +112 -0
- package/dist/client/media/queue.js +274 -0
- package/dist/client/media/queue.js.map +1 -0
- package/dist/client/media/ref.d.ts +6 -0
- package/dist/client/media/ref.js +10 -0
- package/dist/client/media/ref.js.map +1 -0
- package/dist/client/media/rules.d.ts +54 -0
- package/dist/client/media/rules.js +143 -0
- package/dist/client/media/rules.js.map +1 -0
- package/dist/client/media/transport.d.ts +13 -0
- package/dist/client/media/transport.js +33 -0
- package/dist/client/media/transport.js.map +1 -0
- package/dist/client/media/windows.d.ts +15 -0
- package/dist/client/media/windows.js +39 -0
- package/dist/client/media/windows.js.map +1 -0
- package/dist/client/playback.d.ts +200 -0
- package/dist/client/playback.js +142 -0
- package/dist/client/playback.js.map +1 -0
- package/dist/client/player.d.ts +102 -0
- package/dist/client/player.js +155 -0
- package/dist/client/player.js.map +1 -0
- package/dist/client/public.d.ts +25 -0
- package/dist/client/public.js +18 -0
- package/dist/client/public.js.map +1 -0
- package/dist/client/rendition.d.ts +19 -0
- package/dist/client/rendition.js +16 -0
- package/dist/client/rendition.js.map +1 -0
- package/dist/client/route.d.ts +39 -0
- package/dist/client/route.js +31 -0
- package/dist/client/route.js.map +1 -0
- package/dist/client/taxonomy.d.ts +30 -0
- package/dist/client/taxonomy.js +77 -0
- package/dist/client/taxonomy.js.map +1 -0
- package/dist/client.js +33 -0
- package/dist/components/encode-progress.d.ts +15 -0
- package/dist/components/encode-progress.js +49 -0
- package/dist/components/encode-progress.js.map +1 -0
- package/dist/components/hover-preview.d.ts +29 -0
- package/dist/components/hover-preview.js +49 -0
- package/dist/components/hover-preview.js.map +1 -0
- package/dist/components/image-crop-dialog.d.ts +48 -0
- package/dist/components/image-crop-dialog.js +359 -0
- package/dist/components/image-crop-dialog.js.map +1 -0
- package/dist/components/lazy-mount.d.ts +10 -0
- package/dist/components/lazy-mount.js +22 -0
- package/dist/components/lazy-mount.js.map +1 -0
- package/dist/components/media-folder-editor.d.ts +59 -0
- package/dist/components/media-folder-editor.js +611 -0
- package/dist/components/media-folder-editor.js.map +1 -0
- package/dist/components/media-gallery.d.ts +54 -0
- package/dist/components/media-gallery.js +597 -0
- package/dist/components/media-gallery.js.map +1 -0
- package/dist/components/media-readiness-notice.d.ts +24 -0
- package/dist/components/media-readiness-notice.js +61 -0
- package/dist/components/media-readiness-notice.js.map +1 -0
- package/dist/components/player-controls.d.ts +71 -0
- package/dist/components/player-controls.js +264 -0
- package/dist/components/player-controls.js.map +1 -0
- package/dist/components/progress.d.ts +5 -0
- package/dist/components/progress.js +14 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/rendition-img.d.ts +34 -0
- package/dist/components/rendition-img.js +78 -0
- package/dist/components/rendition-img.js.map +1 -0
- package/dist/components/slot-editor.d.ts +95 -0
- package/dist/components/slot-editor.js +182 -0
- package/dist/components/slot-editor.js.map +1 -0
- package/dist/components/slot-image.d.ts +23 -0
- package/dist/components/slot-image.js +48 -0
- package/dist/components/slot-image.js.map +1 -0
- package/dist/components/slot-upload.d.ts +41 -0
- package/dist/components/slot-upload.js +215 -0
- package/dist/components/slot-upload.js.map +1 -0
- package/dist/components/social/bans.d.ts +42 -0
- package/dist/components/social/bans.js +300 -0
- package/dist/components/social/bans.js.map +1 -0
- package/dist/components/social/comments.d.ts +42 -0
- package/dist/components/social/comments.js +517 -0
- package/dist/components/social/comments.js.map +1 -0
- package/dist/components/social/engagement.d.ts +45 -0
- package/dist/components/social/engagement.js +129 -0
- package/dist/components/social/engagement.js.map +1 -0
- package/dist/components/social/index.d.ts +6 -0
- package/dist/components/social/moderation.d.ts +32 -0
- package/dist/components/social/moderation.js +383 -0
- package/dist/components/social/moderation.js.map +1 -0
- package/dist/components/social/parts.d.ts +50 -0
- package/dist/components/social/parts.js +106 -0
- package/dist/components/social/parts.js.map +1 -0
- package/dist/components/social/poll-editor.d.ts +34 -0
- package/dist/components/social/poll-editor.js +567 -0
- package/dist/components/social/poll-editor.js.map +1 -0
- package/dist/components/social/poll.d.ts +30 -0
- package/dist/components/social/poll.js +252 -0
- package/dist/components/social/poll.js.map +1 -0
- package/dist/components/sortable-list.d.ts +29 -0
- package/dist/components/sortable-list.js +121 -0
- package/dist/components/sortable-list.js.map +1 -0
- package/dist/components/video-mini-player.d.ts +35 -0
- package/dist/components/video-mini-player.js +236 -0
- package/dist/components/video-mini-player.js.map +1 -0
- package/dist/components/video-player.d.ts +146 -0
- package/dist/components/video-player.js +630 -0
- package/dist/components/video-player.js.map +1 -0
- package/dist/components/video-poster-picker.d.ts +59 -0
- package/dist/components/video-poster-picker.js +335 -0
- package/dist/components/video-poster-picker.js.map +1 -0
- package/dist/components/video-poster.d.ts +24 -0
- package/dist/components/video-poster.js +46 -0
- package/dist/components/video-poster.js.map +1 -0
- package/dist/i18n/context.d.ts +4 -0
- package/dist/i18n/context.js +11 -0
- package/dist/i18n/context.js.map +1 -0
- package/dist/i18n/messages.d.ts +40 -0
- package/dist/i18n/messages.js +94 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +41 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/de.js +271 -0
- package/dist/locales/de.js.map +1 -0
- package/dist/locales/en.d.ts +457 -0
- package/dist/locales/en.js +271 -0
- package/dist/locales/en.js.map +1 -0
- package/dist/locales/es.d.ts +2 -0
- package/dist/locales/es.js +271 -0
- package/dist/locales/es.js.map +1 -0
- package/dist/locales/ja.d.ts +2 -0
- package/dist/locales/ja.js +271 -0
- package/dist/locales/ja.js.map +1 -0
- package/dist/locales/ko.d.ts +2 -0
- package/dist/locales/ko.js +271 -0
- package/dist/locales/ko.js.map +1 -0
- package/dist/locales/social/de.d.ts +2 -0
- package/dist/locales/social/de.js +198 -0
- package/dist/locales/social/de.js.map +1 -0
- package/dist/locales/social/en.d.ts +195 -0
- package/dist/locales/social/en.js +198 -0
- package/dist/locales/social/en.js.map +1 -0
- package/dist/locales/social/es.d.ts +2 -0
- package/dist/locales/social/es.js +198 -0
- package/dist/locales/social/es.js.map +1 -0
- package/dist/locales/social/ja.d.ts +2 -0
- package/dist/locales/social/ja.js +198 -0
- package/dist/locales/social/ja.js.map +1 -0
- package/dist/locales/social/ko.d.ts +2 -0
- package/dist/locales/social/ko.js +198 -0
- package/dist/locales/social/ko.js.map +1 -0
- package/dist/locales/social/zh.d.ts +2 -0
- package/dist/locales/social/zh.js +198 -0
- package/dist/locales/social/zh.js.map +1 -0
- package/dist/locales/zh.d.ts +2 -0
- package/dist/locales/zh.js +271 -0
- package/dist/locales/zh.js.map +1 -0
- package/dist/provider.d.ts +22 -0
- package/dist/provider.js +59 -0
- package/dist/provider.js.map +1 -0
- package/dist/react/canonical.d.ts +33 -0
- package/dist/react/canonical.js +78 -0
- package/dist/react/canonical.js.map +1 -0
- package/dist/react/comments.d.ts +82 -0
- package/dist/react/comments.js +155 -0
- package/dist/react/comments.js.map +1 -0
- package/dist/react/config.d.ts +13 -0
- package/dist/react/config.js +14 -0
- package/dist/react/config.js.map +1 -0
- package/dist/react/context.d.ts +55 -0
- package/dist/react/context.js +42 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/crop.d.ts +49 -0
- package/dist/react/crop.js +107 -0
- package/dist/react/crop.js.map +1 -0
- package/dist/react/engagement.d.ts +38 -0
- package/dist/react/engagement.js +88 -0
- package/dist/react/engagement.js.map +1 -0
- package/dist/react/folder.d.ts +103 -0
- package/dist/react/folder.js +240 -0
- package/dist/react/folder.js.map +1 -0
- package/dist/react/gallery.d.ts +140 -0
- package/dist/react/gallery.js +477 -0
- package/dist/react/gallery.js.map +1 -0
- package/dist/react/index.d.ts +21 -0
- package/dist/react/inline-preview.d.ts +24 -0
- package/dist/react/inline-preview.js +76 -0
- package/dist/react/inline-preview.js.map +1 -0
- package/dist/react/player.d.ts +70 -0
- package/dist/react/player.js +214 -0
- package/dist/react/player.js.map +1 -0
- package/dist/react/polls.d.ts +74 -0
- package/dist/react/polls.js +176 -0
- package/dist/react/polls.js.map +1 -0
- package/dist/react/posts.d.ts +59 -0
- package/dist/react/posts.js +149 -0
- package/dist/react/posts.js.map +1 -0
- package/dist/react/provider.d.ts +27 -0
- package/dist/react/provider.js +49 -0
- package/dist/react/provider.js.map +1 -0
- package/dist/react/public.d.ts +39 -0
- package/dist/react/public.js +70 -0
- package/dist/react/public.js.map +1 -0
- package/dist/react/read.d.ts +52 -0
- package/dist/react/read.js +144 -0
- package/dist/react/read.js.map +1 -0
- package/dist/react/resources.d.ts +73 -0
- package/dist/react/resources.js +247 -0
- package/dist/react/resources.js.map +1 -0
- package/dist/react/slot.d.ts +104 -0
- package/dist/react/slot.js +185 -0
- package/dist/react/slot.js.map +1 -0
- package/dist/react/store.d.ts +51 -0
- package/dist/react/store.js +165 -0
- package/dist/react/store.js.map +1 -0
- package/dist/react/upload.d.ts +86 -0
- package/dist/react/upload.js +155 -0
- package/dist/react/upload.js.map +1 -0
- package/dist/react/use-resource.d.ts +44 -0
- package/dist/react/use-resource.js +74 -0
- package/dist/react/use-resource.js.map +1 -0
- package/dist/react/video.d.ts +118 -0
- package/dist/react/video.js +184 -0
- package/dist/react/video.js.map +1 -0
- package/dist/react/viewport.d.ts +19 -0
- package/dist/react/viewport.js +30 -0
- package/dist/react/viewport.js.map +1 -0
- package/dist/react.js +21 -0
- package/dist/scope.d.ts +14 -0
- package/dist/scope.js +30 -0
- package/dist/scope.js.map +1 -0
- package/dist/styles.css +3 -0
- package/dist/ui/alert-dialog.d.ts +18 -0
- package/dist/ui/alert-dialog.js +86 -0
- package/dist/ui/alert-dialog.js.map +1 -0
- package/dist/ui/alert.d.ts +10 -0
- package/dist/ui/alert.js +38 -0
- package/dist/ui/alert.js.map +1 -0
- package/dist/ui/avatar.d.ts +11 -0
- package/dist/ui/avatar.js +31 -0
- package/dist/ui/avatar.js.map +1 -0
- package/dist/ui/badge.d.ts +7 -0
- package/dist/ui/badge.js +31 -0
- package/dist/ui/badge.js.map +1 -0
- package/dist/ui/button.d.ts +8 -0
- package/dist/ui/button.js +46 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/dialog.d.ts +17 -0
- package/dist/ui/dialog.js +91 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/dropdown-menu.d.ts +15 -0
- package/dist/ui/dropdown-menu.js +105 -0
- package/dist/ui/dropdown-menu.js.map +1 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.js +17 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/label.d.ts +3 -0
- package/dist/ui/label.js +15 -0
- package/dist/ui/label.js.map +1 -0
- package/dist/ui/progress.d.ts +7 -0
- package/dist/ui/progress.js +31 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/select.d.ts +15 -0
- package/dist/ui/select.js +98 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/skeleton.d.ts +2 -0
- package/dist/ui/skeleton.js +14 -0
- package/dist/ui/skeleton.js.map +1 -0
- package/dist/ui/slider.d.ts +3 -0
- package/dist/ui/slider.js +35 -0
- package/dist/ui/slider.js.map +1 -0
- package/dist/ui/switch.d.ts +5 -0
- package/dist/ui/switch.js +20 -0
- package/dist/ui/switch.js.map +1 -0
- package/dist/ui/tabs.d.ts +10 -0
- package/dist/ui/tabs.js +47 -0
- package/dist/ui/tabs.js.map +1 -0
- package/dist/ui/textarea.d.ts +3 -0
- package/dist/ui/textarea.js +15 -0
- package/dist/ui/textarea.js.map +1 -0
- package/dist/ui/toggle-group.d.ts +5 -0
- package/dist/ui/toggle-group.js +23 -0
- package/dist/ui/toggle-group.js.map +1 -0
- package/dist/urls/index.d.ts +100 -0
- package/dist/urls.js +118 -0
- package/dist/urls.js.map +1 -0
- package/package.json +120 -3
package/README.md
CHANGED
|
@@ -1,3 +1,471 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @openrails/contentkit-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ContentKit in the browser: a framework-free client, content URLs, React hooks
|
|
4
|
+
and styled components (uploads with cropping, video posters, galleries, HLS
|
|
5
|
+
playback, comments, reactions, favorites, polls and their staff screens).
|
|
6
|
+
`@openrails/contentkit-ui@X.Y.Z` speaks ContentKit `vX.Y.Z`.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add @openrails/contentkit-ui@X.Y.Z
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| Import | Contents | Needs |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `@openrails/contentkit-ui` | styled components, `ContentKitUiProvider`, i18n | `react`, `react-dom` |
|
|
15
|
+
| `@openrails/contentkit-ui/react` | `ContentKitProvider` and every hook | `react` |
|
|
16
|
+
| `@openrails/contentkit-ui/client` | `createContentKitClient`, `ContentKitError`, generated wire types, crop/aspect/rendition/ABR/gallery math | nothing |
|
|
17
|
+
| `@openrails/contentkit-ui/urls` | content codes, `[/{lang}]/{route}/{CODE}[/{slug}]` paths, `canonical` | nothing |
|
|
18
|
+
| `@openrails/contentkit-ui/locales/{de,es,ja,ko,zh}` | message bundles (English is built in) | |
|
|
19
|
+
| `@openrails/contentkit-ui/styles.css` | the stylesheet, scoped under `.ckui` (the root entry also installs it) | |
|
|
20
|
+
|
|
21
|
+
`react` and `react-dom` are optional peers, so `/client` and `/urls` work
|
|
22
|
+
without React. hls.js loads on first play.
|
|
23
|
+
|
|
24
|
+
## Client and providers
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { createContentKitClient } from "@openrails/contentkit-ui/client";
|
|
28
|
+
import { ContentKitProvider } from "@openrails/contentkit-ui/react";
|
|
29
|
+
import { ContentKitUiProvider } from "@openrails/contentkit-ui";
|
|
30
|
+
import { createContentURLs } from "@openrails/contentkit-ui/urls";
|
|
31
|
+
|
|
32
|
+
const contentkit = createContentKitClient({
|
|
33
|
+
baseUrl: "/api/v1/contentkit", // where the host mounts ContentKit
|
|
34
|
+
mounts: { upload: "/api/v1/user-uploads" }, // optional: modules mounted elsewhere (a string, or (ref) => string)
|
|
35
|
+
fetch: auth.authFetch, // an authenticating transport
|
|
36
|
+
token: () => auth.getAccessToken(), // HLS playlists on the API origin (hls.js uses XHR)
|
|
37
|
+
language: () => lang, // Accept-Language
|
|
38
|
+
});
|
|
39
|
+
const urls = createContentURLs({ routes: { video: "watch", gallery: "g" }, languages: ["en", "es"], origin: "https://example.com" });
|
|
40
|
+
|
|
41
|
+
<ContentKitProvider
|
|
42
|
+
client={contentkit}
|
|
43
|
+
viewer={user?.id ?? null} // the signed-in user as ContentKit sees it
|
|
44
|
+
language={lang} // same value as the client's language callback
|
|
45
|
+
accessRevision={accessRevision} // increment when permissions/entitlements change
|
|
46
|
+
onSignIn={() => openSignIn()} // asks signed-out visitors to sign in where the server refuses them
|
|
47
|
+
urls={urls}
|
|
48
|
+
navigate={(to, o) => navigate(to, o)}
|
|
49
|
+
onChange={(change) => queryClient.invalidateQueries({ queryKey: ["media", change.ref.id] })}
|
|
50
|
+
onError={(error, { operation }) => report(operation, error)}
|
|
51
|
+
>
|
|
52
|
+
<ContentKitUiProvider appearance={{ theme: "inherit" }} language={lang} density={[2, 3]}>
|
|
53
|
+
<App />
|
|
54
|
+
</ContentKitUiProvider>
|
|
55
|
+
</ContentKitProvider>;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Keep `viewer`, `language` and `accessRevision` current on the provider. A change
|
|
59
|
+
starts fresh media and social reads without remounting the children. Old requests
|
|
60
|
+
are abandoned when their last reader leaves; their results cannot fill the new
|
|
61
|
+
scope. `accessRevision` is optional when the host has no changing access state.
|
|
62
|
+
It is a cache signal, not an authorization decision. Without a provider, replace
|
|
63
|
+
the client when its session or request language changes. Host-supplied `read` and
|
|
64
|
+
`initial` data must also belong to the current viewer and language.
|
|
65
|
+
|
|
66
|
+
Writes retain the cache scope they started in. After a scope change, a late
|
|
67
|
+
notification refetches affected reads instead of copying its response into them;
|
|
68
|
+
`onChange` still runs. Use a separate client for each independent session or
|
|
69
|
+
request-language context, not one client across differently scoped providers.
|
|
70
|
+
The framework-free client exposes `setReadScope(key)` and passes that key as
|
|
71
|
+
the second `subscribe` / `onChange` argument, or `null` when the scope changed during the
|
|
72
|
+
operation. This does not change the operation's result or its authentication.
|
|
73
|
+
|
|
74
|
+
- **Mounts.** Without `mounts`, modules live under `baseUrl`: content at the
|
|
75
|
+
root, `/media` (reads, HLS), `/media/upload`, `/codes`, `/taxonomy`.
|
|
76
|
+
- **`client.media`**: `upload`, `put` (upload, commit, wait), `commit`, `read`,
|
|
77
|
+
`waitFor`, `editorView`, `getFrame`, `hlsBase(ref, dir)`, `discard`, and
|
|
78
|
+
`xhrSetup` for players (`<MediaGallery xhrSetup={contentkit.media.xhrSetup} />`).
|
|
79
|
+
Uploads hash in a Web Worker, send one checksum-bound PUT up to 64 MiB and
|
|
80
|
+
resumable multipart above (`onState`/`resume`). The bucket must allow CORS
|
|
81
|
+
`PUT` from the app's origin with `Content-Type` and `x-amz-checksum-sha256`.
|
|
82
|
+
- **Content modules**, one per resource, each method one route of the
|
|
83
|
+
generated route table with its generated types: `posts`, `comments`,
|
|
84
|
+
`reactions`, `favorites`, `polls`, `bans` (`"owner"` or `"global"`),
|
|
85
|
+
`moderation`, `taxonomy`, `codes`. Lists take `limit`/`offset` (the review
|
|
86
|
+
queue a `cursor`); staff lists are `posts.adminList` (`q` searches,
|
|
87
|
+
`deleted: true` lists deleted posts for `posts.restore`), `polls.adminList`,
|
|
88
|
+
`comments.adminList`.
|
|
89
|
+
- **Images of posts and polls** go to the item's server-named upload path:
|
|
90
|
+
`media.uploadNamed(ref, file)` resolves the name content routes take;
|
|
91
|
+
`media.uploadInline(postId, file)` resolves a body image's public URL;
|
|
92
|
+
`posts.uploadCover`, `polls.uploadImage` and `polls.uploadOptionImage` do
|
|
93
|
+
both steps. `folders` renames the post and poll media kinds. A post's
|
|
94
|
+
public image files exist only while it is published; `usePost` shows an
|
|
95
|
+
unpublished post's images to its editors (below).
|
|
96
|
+
- **`client.config()`** says what the content module allows: which
|
|
97
|
+
interactions signed-out visitors may make (`content.Options.Anonymous`,
|
|
98
|
+
none by default) and the longest comment (`CommentMaxLength`, 2,200 by
|
|
99
|
+
default; longer is `comment_too_long` with `details.max`).
|
|
100
|
+
- **`client.subscribe(listener)`** receives every successful mutation
|
|
101
|
+
(`media.committed`, `media.processed`, `comment.created`, `reaction.changed`,
|
|
102
|
+
`poll.updated`, `ban.saved`, …); `ContentKitProvider onChange` is the same
|
|
103
|
+
stream, for the host's cache.
|
|
104
|
+
- **`ContentKitProvider`** holds the client, URL config, `navigate`,
|
|
105
|
+
`onChange`, `onError` and the read store hooks share: one request per item
|
|
106
|
+
and options while any hook shows it; `set()` and processed uploads update
|
|
107
|
+
every reader in place.
|
|
108
|
+
- **`ContentKitUiProvider`**: `appearance` (`theme`
|
|
109
|
+
`light`/`dark`/`auto`/`inherit`, `variables` as `--ckui-*`), `language`
|
|
110
|
+
(loads its built-in bundle), `messages` (bundles over it), `t` (host
|
|
111
|
+
translate hook), `density` (default `[2, 3]`), `inlinePreview` (default true).
|
|
112
|
+
|
|
113
|
+
## Hooks
|
|
114
|
+
|
|
115
|
+
Hooks read the client from `ContentKitProvider`; a `client` option overrides it.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
const q = useUploadQueue({ ref, path: "originals/{name}" });
|
|
119
|
+
q.add(input.files!); q.move(id, 0); await q.commit();
|
|
120
|
+
|
|
121
|
+
const one = useUpload();
|
|
122
|
+
await one.upload(file, { ref, path: "inline/x.png", put: {} });
|
|
123
|
+
|
|
124
|
+
const img = useSlotImage({ ref, path: "avatar", image });
|
|
125
|
+
const crop = useSlotCrop({ ref, path: "avatar", file: img.file, aspect: "1:1", onSaved: img.set });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Content hooks share one store per client: one request per item while any
|
|
129
|
+
hook shows it, kept per `viewer`, with every change made through the client
|
|
130
|
+
applied in place. Reactions, favorites and votes apply at once and roll back
|
|
131
|
+
if the server refuses them.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
const thread = useComments(ref, { sort: "best" });
|
|
135
|
+
await thread.post("Nice!", { replyTo: parentId });
|
|
136
|
+
await thread.react(comment, 1);
|
|
137
|
+
const { counts, toggle } = useReaction(ref);
|
|
138
|
+
const { favorited, count, toggle: fav } = useFavorite(ref);
|
|
139
|
+
const { poll, vote } = usePoll(null, { language }); // the newest live poll
|
|
140
|
+
const editor = usePollEditor(pollId); // staff
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A post editor puts `editorBody` in its rich-text field and inserts what
|
|
144
|
+
`uploadImage(file)` resolves: URLs that show the images now (the file for a
|
|
145
|
+
fresh upload, signed editor views of an unpublished post's images, read and
|
|
146
|
+
refreshed as needed). `update({ body })` stores the images' public URLs
|
|
147
|
+
(`storedBody(html)` does the same for a host's own save); `imageSrc(url)` shows
|
|
148
|
+
the cover.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
const editor = usePost(postId);
|
|
152
|
+
<RichText value={editor.editorBody} onImage={editor.uploadImage} onSave={(html) => editor.update({ body: html })} />
|
|
153
|
+
<img src={editor.imageSrc(editor.post?.cover_url)} alt="" />
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Also `useContentConfig` (`client.config()`, read once), `useCommentReplies`,
|
|
157
|
+
`useCanComment` (may the caller comment, may signed-out visitors, the ban
|
|
158
|
+
that stops it, and what it may do to others' comments), `useLatestComments`,
|
|
159
|
+
`usePolls`, `usePosts` (staff: `admin`, `deleted`, `q`), `usePost` (with `restore`), and for staff `useAdminComments`,
|
|
160
|
+
`useModerationQueue`, `useCommentBans`.
|
|
161
|
+
|
|
162
|
+
Also: `useCrop`, `useVideoImages`, `useFrameStrip`, `useVideoFrame`,
|
|
163
|
+
`useVideoPoster`, `useEncodeProgress`, `useHlsPlayer`, `useCarousel`,
|
|
164
|
+
`useGalleryView`, `useInlinePreview`, `useRefreshBeforeExpiry`, `usePresets`,
|
|
165
|
+
`useContentKitClient`, `useContentURLs`, `useErrorReporter`.
|
|
166
|
+
|
|
167
|
+
### useMediaRead
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
const { read, loading, error, processing, reload, refresh, set } =
|
|
171
|
+
useMediaRead(ref, { prefix: "low-res/", editor: true, window: { start: 0, end: 120 } });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
One read per item and options, shared by every hook showing it. It reads
|
|
175
|
+
again shortly before the URLs expire, waits out `rate_limited`, polls while
|
|
176
|
+
uploads process (editor reads, every 2.5 s; `poll`), and reloads after any
|
|
177
|
+
commit through the same client. `window` splits a long item into reads of
|
|
178
|
+
`chunk` files (default 50) merged into one result; windows once read stay
|
|
179
|
+
loaded. `refresh()` joins a read in flight (a player's grant refresh);
|
|
180
|
+
`reload()` restarts it. `read` shows a host's read until `reload()`.
|
|
181
|
+
|
|
182
|
+
### useMediaFolder
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
const folder = useMediaFolder(ref, { paths: ["images/{name}", "videos/{name}"], commit: "manual" });
|
|
186
|
+
folder.add(files); // screened and queued; refusals in folder.refused
|
|
187
|
+
await folder.commit(); // the uploaded files, in queue order
|
|
188
|
+
await folder.move(path, 0); await folder.rename(path, "cover");
|
|
189
|
+
await folder.edit(path, edit); await folder.replace(path, file); await folder.remove([path]);
|
|
190
|
+
folder.discard(); // stop uploads and empty the queue
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
An item's folder for its editors: the editor read (all windows), an upload
|
|
194
|
+
queue, and the kind's upload rules from the editor read, which screen type,
|
|
195
|
+
size and file caps before anything uploads and name files around names
|
|
196
|
+
taken. `groups` lists each path's uploads; `commit: "auto"` commits each
|
|
197
|
+
upload once those before it have (a draft). Files added before the editor
|
|
198
|
+
read arrives wait in `waiting` and are screened when its rules do (or
|
|
199
|
+
refused if the read fails or states no rules). `ref` may be null until the item exists,
|
|
200
|
+
so a composer can create its draft on the first drop. `busy` is true while
|
|
201
|
+
work is in flight (files waiting, uploads queued or running, a commit or
|
|
202
|
+
update); `fileCount` counts the uploads and the files on their way in. A
|
|
203
|
+
committed file leaves the queue once the read lists it. Failures go to the provider's
|
|
204
|
+
`onError` with an operation (`upload`, `folder.commit`, `folder.update`,
|
|
205
|
+
`folder.process` for uploads the worker fails after the editor opened) and
|
|
206
|
+
the file's name.
|
|
207
|
+
|
|
208
|
+
### useEditorCrop
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
const crop = useEditorCrop(ref, cropping); // a path, or null
|
|
212
|
+
// crop.status: idle | loading | cropping (source, edit) | saving | done | error
|
|
213
|
+
await crop.save(edit);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Re-crops a kept upload: while the path is set, its editor view (the
|
|
217
|
+
original, oriented, unedited) and current edit load; `save` commits an edit
|
|
218
|
+
op. Nothing is uploaded.
|
|
219
|
+
|
|
220
|
+
### usePublicImage
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const { image, rule, isDefault } = usePublicImage("user", userId, "avatar");
|
|
224
|
+
<SlotImage image={image} fallback={<Initials name={name} />} />
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
An item's image for a public preset: the exact renditions from a read's
|
|
228
|
+
`public`, or from a host listing passed as `image` (no read), else the
|
|
229
|
+
kind's default image. Published files carry a generation, so a URL built
|
|
230
|
+
from a preset template only ever names the default; `rule` (from
|
|
231
|
+
`GET /media/presets`) gives the shape, widths and narrowest edit.
|
|
232
|
+
|
|
233
|
+
### useCanonicalContent
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
useCanonicalContent(video.link, { title: video.title, image: posterURL, location: pathname + search, defaultLanguage: "en" });
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Once a content page's link arrives, replaces the address with its canonical
|
|
240
|
+
path (code spelling, merged code, current slug) through the provider's
|
|
241
|
+
`navigate`, else `history.replaceState`, and keeps `<link rel=canonical>`,
|
|
242
|
+
`og:url`, `og:title`, `og:image` and hreflang alternates (each language's
|
|
243
|
+
own slug, plus `x-default`) in the head while mounted. Unmounting removes
|
|
244
|
+
them, server-rendered ones included, so the next page sets its own. Needs the provider's
|
|
245
|
+
`urls`; `/urls` also exports `hreflang()` and `canonicalURL()` (drops
|
|
246
|
+
tracking parameters) for server rendering.
|
|
247
|
+
|
|
248
|
+
### useNearViewport
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
const [ref, near] = useNearViewport({ margin: "800px 0px", leaveMargin: "2400px 0px" });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Whether an element is near the viewport, to mount media as it approaches;
|
|
255
|
+
with `leaveMargin`, false again far away.
|
|
256
|
+
|
|
257
|
+
## Components
|
|
258
|
+
|
|
259
|
+
`AvatarUpload`, `CoverUpload` (complete form fields); `SlotEditor` with
|
|
260
|
+
`SlotEditMenu`, `SlotEditError` and `useSlotEditor()` (the same pick, crop,
|
|
261
|
+
save and re-crop flow inside a host layout); `SlotImage`; `ImageCropDialog`;
|
|
262
|
+
`VideoPosterPicker`, `VideoPoster`; `EncodeProgress`; `RenditionImg`;
|
|
263
|
+
`MediaGallery`, `VideoPlayer` and `VideoMiniPlayer` (HLS with ABR, inline
|
|
264
|
+
muted previews, a grant refresh before `expires`, and a reason, Retry and
|
|
265
|
+
support code for every failure; see below). A `PublicPreset` (`{ preset, aspect, renditions }`)
|
|
266
|
+
is what a slot or poster shows; a read's `public` lists `PublicImage`s. A
|
|
267
|
+
slot crops at its image's aspect, else its public preset's
|
|
268
|
+
(`GET /media/presets`), so an item without an image yet still crops a 3:1
|
|
269
|
+
cover at 3:1.
|
|
270
|
+
|
|
271
|
+
### MediaFolderEditor
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
<MediaFolderEditor item={post} paths={["images/{name}", "videos/{name}"]} commit="auto" ref={handle}
|
|
275
|
+
toolbar={<ZipImport />} rowActions={(f) => <SetCover file={f} />} footer={note} />
|
|
276
|
+
handle.current.discard();
|
|
277
|
+
|
|
278
|
+
// Or the host owns the folder and renders with its state:
|
|
279
|
+
const folder = useMediaFolder(draft, { paths, commit: "auto" }); // draft: null until created
|
|
280
|
+
<MediaFolderEditor folder={folder} />
|
|
281
|
+
<Button disabled={folder.busy || (!title && !text && !folder.fileCount)}>Publish</Button>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`useMediaFolder` with its UI: a drop zone stating the rules, a queue with
|
|
285
|
+
progress, retry and remove, and each path's uploads as a sortable list with
|
|
286
|
+
thumbnail, name, edited badge, encode progress and failure; crop (with
|
|
287
|
+
`aspects` when the path has no preset shape), rename, replace and bulk
|
|
288
|
+
remove. `commit="manual"` adds the queue with "Add N"; `"auto"` commits as
|
|
289
|
+
files finish. `confirmRemove` replaces the browser's confirm.
|
|
290
|
+
|
|
291
|
+
### SortableList
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<SortableList items={rows} id={(r) => r.id} name={(r) => r.title} onMove={(from, to) => move(from, to)} layout="grid">
|
|
295
|
+
{(row, handle) => <>{handle}<Row row={row} /></>}
|
|
296
|
+
</SortableList>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Reorders by a drag handle with a mouse, a finger or the keyboard (Space or
|
|
300
|
+
Enter lifts, arrows move, Space or Enter drops, Escape cancels), with
|
|
301
|
+
localized screen reader announcements. `onMove` fires once, on drop.
|
|
302
|
+
|
|
303
|
+
### ImageCropDialog shapes
|
|
304
|
+
|
|
305
|
+
`aspects={["", "1:1", "4:5", "16:9"]}` adds a shape chooser (`""` is the
|
|
306
|
+
original's, turning with the image); a preview of the result and its size
|
|
307
|
+
sit under the controls (`preview={false}` hides them), and Reset returns to
|
|
308
|
+
the starting shape and edit.
|
|
309
|
+
|
|
310
|
+
### MediaGallery item
|
|
311
|
+
|
|
312
|
+
`<MediaGallery item={post} prefix="low-res/" renderLocked={unlock} />` reads
|
|
313
|
+
through the client: the read (refreshed, reloaded after commits), HLS bases,
|
|
314
|
+
playlist auth and the grant refresh. Passing `read`, `hlsBase`, `xhrSetup`
|
|
315
|
+
and `refresh` yourself still works. A locked item's public teaser carries
|
|
316
|
+
the locked count and `renderLocked`, so the unlock is on the first slide;
|
|
317
|
+
the stage is never taller than `maxHeight` (default `80svh`).
|
|
318
|
+
|
|
319
|
+
### VideoPosterPicker frames from another video
|
|
320
|
+
|
|
321
|
+
`frames={{ item: otherVideo, path: "source" }}` browses another item's video;
|
|
322
|
+
the chosen frame (cropped or whole) is uploaded to the poster path as an
|
|
323
|
+
image. Which video to offer is the host's choice.
|
|
324
|
+
|
|
325
|
+
### HoverPreview
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
<div className="card relative"><Thumbnail /><HoverPreview item={video} duration={video.duration} /></div>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
A card's muted inline preview over the client's HLS base, fading in on hover
|
|
332
|
+
(or while it is the most visible card on touch screens), one page-wide, off
|
|
333
|
+
with reduced motion or Save-Data.
|
|
334
|
+
|
|
335
|
+
### Images: fallback, skeleton, fit and LazyMount
|
|
336
|
+
|
|
337
|
+
`RenditionImg`, `SlotImage` and `VideoPoster` take `fallback` (shown without
|
|
338
|
+
renditions or after a failed load, e.g. a 404), and `RenditionImg` and
|
|
339
|
+
`SlotImage` take `skeleton` (a pulsing fill until loaded) and `fit`
|
|
340
|
+
(`cover` or `contain`). `<LazyMount placeholder={box}>` mounts its children
|
|
341
|
+
as it nears the viewport (`useNearViewport` options).
|
|
342
|
+
|
|
343
|
+
### MediaReadinessNotice
|
|
344
|
+
|
|
345
|
+
`<MediaReadinessNotice item={post} description="Only editors see it until then." />`
|
|
346
|
+
renders nothing once every upload is processed; while some process, a
|
|
347
|
+
notice with the first video's encode progress; when uploads failed, which
|
|
348
|
+
ones.
|
|
349
|
+
|
|
350
|
+
## Video player
|
|
351
|
+
|
|
352
|
+
`VideoPlayer` plays a ladder's HLS folder (`client.media.hlsBase(ref, dir)`)
|
|
353
|
+
with its own controls: seek bar with sprite preview, volume, subtitles,
|
|
354
|
+
audio, quality (Auto or a fixed rung), speed, picture in picture, theater,
|
|
355
|
+
fullscreen and a mini player.
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
<VideoPlayer
|
|
359
|
+
base={contentkit.media.hlsBase(ref, "hls/")}
|
|
360
|
+
xhrSetup={contentkit.media.xhrSetup}
|
|
361
|
+
refresh={reload} expires={read.expires} // re-grant before the token expires
|
|
362
|
+
width={video.w} height={video.h} duration={video.dur} poster={posterPreset}
|
|
363
|
+
autoPlay startAt={resumePoint} // resume; never a muted autoplay
|
|
364
|
+
keyboard="global" // a watch page; default "focus"
|
|
365
|
+
audioLanguage={lang} subtitleLanguage={null} // until the viewer chooses
|
|
366
|
+
onProgress={(p) => saveResume(p.time, p.played)} // interval, pause, seek, end, page hide, unload
|
|
367
|
+
onEvent={(e) => analytics(e)} // play, pause, seek, quality, audio, subtitles, fullscreen, error…
|
|
368
|
+
theater={theater} onTheaterChange={setTheater}
|
|
369
|
+
onMiniPlayer={setHandoff}
|
|
370
|
+
renderDownloads={({ className, container }) => <Downloads className={className} container={container} />}
|
|
371
|
+
menuItems={[{ label: "Version", value, options, onChange }, { label: "Report a problem", onSelect }]}
|
|
372
|
+
/>
|
|
373
|
+
{handoff && <VideoMiniPlayer handoff={handoff} xhrSetup={contentkit.media.xhrSetup} onExpand={openWatchPage} onClose={() => setHandoff(null)} />}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
- **Shortcuts:** Space/K play, ←/→ 5 s, J/L 10 s, ↑/↓ volume, M mute, C
|
|
377
|
+
subtitles, F fullscreen, T theater, I mini player, 0–9 a tenth of the way,
|
|
378
|
+
Home/End, `<`/`>` speed. Never while typing or in a menu.
|
|
379
|
+
- **Touch:** a tap shows or hides the controls; a double tap on either side
|
|
380
|
+
seeks 10 s, and further taps keep seeking. A double click is fullscreen.
|
|
381
|
+
- **Remembered per browser:** volume and mute (`ckui.player.volume`), the
|
|
382
|
+
audio and subtitle languages (`ckui.player.tracks`) and a fixed quality
|
|
383
|
+
(`ckui.player.quality`); each `*Key` option renames or disables one.
|
|
384
|
+
- **Mini player:** `onMiniPlayer` gets a `PlayerHandoff` (position, playing,
|
|
385
|
+
languages, size, poster). `VideoMiniPlayer` docks it in a corner and calls
|
|
386
|
+
`onExpand({ time, playing })` or `onClose({ time })`; where it lives and
|
|
387
|
+
what expand opens are the host's.
|
|
388
|
+
- **Theming:** `appearance.variables.playerAccent` colors the played range
|
|
389
|
+
and pressed toggles (default white). Menus are always dark.
|
|
390
|
+
|
|
391
|
+
## Comments, reactions and polls
|
|
392
|
+
|
|
393
|
+
`Comments` (threads with one-level replies, tombstones, held and rejected
|
|
394
|
+
states shown to their author, reactions, edit, delete, ban and rate-limit
|
|
395
|
+
notices, the server's longest comment), `ReactionButtons`, `FavoriteButton` (with the server's count)
|
|
396
|
+
and `Poll` (a final vote with results scaled to the leading option, or a
|
|
397
|
+
free-text answer and its groups; `results="always"` shows results before
|
|
398
|
+
voting). Signed-out visitors comment under a name, react and vote where the
|
|
399
|
+
server allows it (`content.Options.Anonymous`, read from the comment standing
|
|
400
|
+
and `client.config()`); elsewhere they are asked to sign in (`onSignIn`, the
|
|
401
|
+
provider's or their own) or, without one, told to.
|
|
402
|
+
|
|
403
|
+
Staff: `CommentModeration` (every comment, the review queue, the site's
|
|
404
|
+
bans), `CommentBans` with `CommentBanDialog`, and `PollEditor`. They show what
|
|
405
|
+
the caller's permissions allow and say so when a route refuses; the host
|
|
406
|
+
decides who sees them.
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
<Comments item={{ kind: "video", id }} count={video.comment_count} userHref={(u) => `/u/${u.username}`} />
|
|
410
|
+
<Poll language={lang} />
|
|
411
|
+
<CommentModeration contentKinds={["video", "post"]} itemHref={(i) => `/${i.kind}/${i.id}`} />
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Errors
|
|
415
|
+
|
|
416
|
+
Every call rejects with `ContentKitError`: `code` (the server's, or `network`,
|
|
417
|
+
`storage`, `aborted`, `resume_mismatch`, `decode`, `render_timeout`),
|
|
418
|
+
`status`, `retryAfter`, `details` (image and video limits), `blobs`
|
|
419
|
+
(`not_uploaded`), `ban` (`comment_banned`) and `action` (`rate_limited`),
|
|
420
|
+
plus `refusal`, `isLimit`, `isCeiling`, `blocksQueue` and `transient`. Every
|
|
421
|
+
code has a message in every bundle (`useMessages().error(e)`).
|
|
422
|
+
|
|
423
|
+
## Migrating from the two old packages
|
|
424
|
+
|
|
425
|
+
| Before | Now |
|
|
426
|
+
| --- | --- |
|
|
427
|
+
| `@openrails/contentkit-upload` | `@openrails/contentkit-ui/client` |
|
|
428
|
+
| `@openrails/contentkit-upload/react` | `@openrails/contentkit-ui/react` |
|
|
429
|
+
| `@openrails/contentkit-upload/ui` | `@openrails/contentkit-ui` |
|
|
430
|
+
| `@openrails/contentkit-upload/locales/*`, `/styles.css` | `@openrails/contentkit-ui/locales/*`, `/styles.css` |
|
|
431
|
+
| `@openrails/contentkit-urls` | `@openrails/contentkit-ui/urls` |
|
|
432
|
+
| `createUploadClient({ endpoint, readEndpoint })` | `createContentKitClient({ baseUrl, mounts: { upload, media } }).media` |
|
|
433
|
+
| `UploadClient`, `UploadApi` | `MediaClient`, `MediaApi` |
|
|
434
|
+
| `UploadUiProvider client onError …` | `ContentKitProvider client onError` + `ContentKitUiProvider appearance messages t density inlinePreview` |
|
|
435
|
+
| `UploadError`, `UploadErrorCode` | `ContentKitError`, `ContentKitErrorCode` |
|
|
436
|
+
| `useUploadQueue(client, o)`, `useUpload(client)` | `useUploadQueue(o)`, `useUpload()` |
|
|
437
|
+
| `useRead(client, ref, o)` | `useMediaRead(ref, o)` |
|
|
438
|
+
| `useSlotImage(client, o)`, `useSlotCrop(client, o)` | `useSlotImage(o)`, `useSlotCrop(o)` |
|
|
439
|
+
| `useVideoImages`, `useFrameStrip`, `useVideoFrame`, `useVideoPoster` `(client, o)` | the same `(o)` |
|
|
440
|
+
| `PublicImage` (preset at an item) | `PublicPreset` |
|
|
441
|
+
| `SlotImage`, `VideoPoster` `placeholder` | `fallback` |
|
|
442
|
+
| `UploadUi*` types, `UploadUiRoot` | `ContentKitUi*`, `ContentKitUiRoot` |
|
|
443
|
+
|
|
444
|
+
## Development
|
|
445
|
+
|
|
446
|
+
```sh
|
|
447
|
+
pnpm check # typecheck, lint, unit tests (pure math and client-free components), build
|
|
448
|
+
pnpm build
|
|
449
|
+
pnpm test:e2e # the integration project and the browser specs against a real ContentKit
|
|
450
|
+
pnpm test:integration # or either alone
|
|
451
|
+
pnpm test:browser # (demo/ in Chromium, light/dark × desktop/mobile screenshots)
|
|
452
|
+
pnpm demo # the stack with seeded demo pages, until Ctrl-C
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
The suites need Docker, Go and ffmpeg. `e2e/server` composes the real
|
|
456
|
+
`contentkit.Runtime` (content, taxonomy, codes, media upload and read) with
|
|
457
|
+
AuthKit for identity over PGroonga PostgreSQL 18, MinIO, and media-worker and
|
|
458
|
+
media-gateway built from this checkout (`e2e/compose.yaml`). Each run brings
|
|
459
|
+
the stack up under its own project name and takes it down; the server exits
|
|
460
|
+
with its runner. Pages sign in with auth-ui and pass `auth.authFetch`. Tests
|
|
461
|
+
set up through `/__test`: accounts (`staff`, `moderator`, `editor` roles),
|
|
462
|
+
items the host's resolver knows, stored-object checks, and faults (no CORS,
|
|
463
|
+
429 and expired tokens at the gateway, dropped or held bucket requests, API
|
|
464
|
+
error replies).
|
|
465
|
+
|
|
466
|
+
The wire types, route table and error codes in `src/client/generated/` are
|
|
467
|
+
generated from ContentKit's route catalog (`pnpm contract`; CI runs
|
|
468
|
+
`pnpm contract:check`). UI primitives come from
|
|
469
|
+
`pnpm dlx shadcn@4.21.0 add …` (`components.json`); local edits are marked
|
|
470
|
+
`// Local:`. The `release` workflow publishes the package to npm for every
|
|
471
|
+
published `v*` release.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `light`/`dark` force a palette, `auto` follows the OS, `inherit` reads the
|
|
4
|
+
* host's shadcn tokens (`--primary`, …) and its `.dark` class.
|
|
5
|
+
*/
|
|
6
|
+
export type ContentKitUiTheme = "light" | "dark" | "auto" | "inherit";
|
|
7
|
+
/** Raw CSS values applied as `--ckui-*` custom properties on every root. */
|
|
8
|
+
export interface ContentKitUiVariables {
|
|
9
|
+
background?: string;
|
|
10
|
+
foreground?: string;
|
|
11
|
+
card?: string;
|
|
12
|
+
cardForeground?: string;
|
|
13
|
+
popover?: string;
|
|
14
|
+
popoverForeground?: string;
|
|
15
|
+
primary?: string;
|
|
16
|
+
primaryForeground?: string;
|
|
17
|
+
secondary?: string;
|
|
18
|
+
secondaryForeground?: string;
|
|
19
|
+
muted?: string;
|
|
20
|
+
mutedForeground?: string;
|
|
21
|
+
accent?: string;
|
|
22
|
+
accentForeground?: string;
|
|
23
|
+
destructive?: string;
|
|
24
|
+
warning?: string;
|
|
25
|
+
border?: string;
|
|
26
|
+
input?: string;
|
|
27
|
+
ring?: string;
|
|
28
|
+
radius?: string;
|
|
29
|
+
fontFamily?: string;
|
|
30
|
+
/** The video player's played range, seek thumb and pressed toggles. Default white. */
|
|
31
|
+
playerAccent?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ContentKitUiAppearance {
|
|
34
|
+
theme?: ContentKitUiTheme;
|
|
35
|
+
variables?: ContentKitUiVariables;
|
|
36
|
+
}
|
|
37
|
+
export declare function appearanceStyle(a: ContentKitUiAppearance | undefined): CSSProperties | undefined;
|
|
38
|
+
export declare function appearanceTheme(a: ContentKitUiAppearance | undefined): ContentKitUiTheme;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/appearance.ts
|
|
2
|
+
var e = (e) => e.replace(/[A-Z]/g, (e) => "-" + e.toLowerCase());
|
|
3
|
+
function t(t) {
|
|
4
|
+
if (!t?.variables) return;
|
|
5
|
+
let n = {};
|
|
6
|
+
for (let [r, i] of Object.entries(t.variables)) i && (n[r === "fontFamily" ? "--ckui-font" : `--ckui-${e(r)}`] = i);
|
|
7
|
+
return n;
|
|
8
|
+
}
|
|
9
|
+
function n(e) {
|
|
10
|
+
return e?.theme ?? "auto";
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { t as appearanceStyle, n as appearanceTheme };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=appearance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appearance.js","names":[],"sources":["../src/appearance.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\n\n/**\n * `light`/`dark` force a palette, `auto` follows the OS, `inherit` reads the\n * host's shadcn tokens (`--primary`, …) and its `.dark` class.\n */\nexport type ContentKitUiTheme = \"light\" | \"dark\" | \"auto\" | \"inherit\";\n\n/** Raw CSS values applied as `--ckui-*` custom properties on every root. */\nexport interface ContentKitUiVariables {\n background?: string;\n foreground?: string;\n card?: string;\n cardForeground?: string;\n popover?: string;\n popoverForeground?: string;\n primary?: string;\n primaryForeground?: string;\n secondary?: string;\n secondaryForeground?: string;\n muted?: string;\n mutedForeground?: string;\n accent?: string;\n accentForeground?: string;\n destructive?: string;\n warning?: string;\n border?: string;\n input?: string;\n ring?: string;\n radius?: string;\n fontFamily?: string;\n /** The video player's played range, seek thumb and pressed toggles. Default white. */\n playerAccent?: string;\n}\n\nexport interface ContentKitUiAppearance {\n theme?: ContentKitUiTheme;\n variables?: ContentKitUiVariables;\n}\n\nconst kebab = (k: string) => k.replace(/[A-Z]/g, (c) => \"-\" + c.toLowerCase());\n\nexport function appearanceStyle(a: ContentKitUiAppearance | undefined): CSSProperties | undefined {\n if (!a?.variables) return undefined;\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(a.variables)) {\n if (value) style[key === \"fontFamily\" ? \"--ckui-font\" : `--ckui-${kebab(key)}`] = value;\n }\n return style as CSSProperties;\n}\n\nexport function appearanceTheme(a: ContentKitUiAppearance | undefined): ContentKitUiTheme {\n return a?.theme ?? \"auto\";\n}\n"],"mappings":";AAwCA,IAAM,KAAS,MAAc,EAAE,QAAQ,WAAW,MAAM,MAAM,EAAE,YAAY,CAAC;AAE7E,SAAgB,EAAgB,GAAkE;CAChG,IAAI,CAAC,GAAG,WAAW;CACnB,IAAM,IAAgC,CAAC;CACvC,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,EAAE,SAAS,GACnD,AAAI,MAAO,EAAM,MAAQ,eAAe,gBAAgB,UAAU,EAAM,CAAG,OAAO;CAEpF,OAAO;AACT;AAEA,SAAgB,EAAgB,GAA0D;CACxF,OAAO,GAAG,SAAS;AACrB"}
|