@openrails/contentkit-ui 0.0.0-stage → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -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 +51 -0
- package/dist/client/client.js +35 -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 +93 -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 +148 -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 +91 -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 +79 -0
- package/dist/client/http.js +74 -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 +52 -0
- package/dist/client/media/rules.js +139 -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 +45 -0
- package/dist/components/media-folder-editor.js +564 -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 +213 -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 +88 -0
- package/dist/react/canonical.js.map +1 -0
- package/dist/react/comments.d.ts +82 -0
- package/dist/react/comments.js +147 -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 +48 -0
- package/dist/react/context.js +37 -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 +83 -0
- package/dist/react/folder.js +183 -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 +23 -0
- package/dist/react/provider.js +35 -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 +143 -0
- package/dist/react/read.js.map +1 -0
- package/dist/react/resources.d.ts +70 -0
- package/dist/react/resources.js +247 -0
- package/dist/react/resources.js.map +1 -0
- package/dist/react/slot.d.ts +102 -0
- package/dist/react/slot.js +181 -0
- package/dist/react/slot.js.map +1 -0
- package/dist/react/store.d.ts +51 -0
- package/dist/react/store.js +164 -0
- package/dist/react/store.js.map +1 -0
- package/dist/react/upload.d.ts +85 -0
- package/dist/react/upload.js +150 -0
- package/dist/react/upload.js.map +1 -0
- package/dist/react/use-resource.d.ts +43 -0
- package/dist/react/use-resource.js +66 -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 +118 -3
package/README.md
CHANGED
|
@@ -1,3 +1,426 @@
|
|
|
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
|
+
onSignIn={() => openSignIn()} // asks signed-out visitors to sign in where the server refuses them
|
|
45
|
+
urls={urls}
|
|
46
|
+
navigate={(to, o) => navigate(to, o)}
|
|
47
|
+
onChange={(change) => queryClient.invalidateQueries({ queryKey: ["media", change.ref.id] })}
|
|
48
|
+
onError={(error, { operation }) => report(operation, error)}
|
|
49
|
+
>
|
|
50
|
+
<ContentKitUiProvider appearance={{ theme: "inherit" }} language={lang} density={[2, 3]}>
|
|
51
|
+
<App />
|
|
52
|
+
</ContentKitUiProvider>
|
|
53
|
+
</ContentKitProvider>;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **Mounts.** Without `mounts`, modules live under `baseUrl`: content at the
|
|
57
|
+
root, `/media` (reads, HLS), `/media/upload`, `/codes`, `/taxonomy`.
|
|
58
|
+
- **`client.media`**: `upload`, `put` (upload, commit, wait), `commit`, `read`,
|
|
59
|
+
`waitFor`, `editorView`, `getFrame`, `hlsBase(ref, dir)`, `discard`, and
|
|
60
|
+
`xhrSetup` for players (`<MediaGallery xhrSetup={contentkit.media.xhrSetup} />`).
|
|
61
|
+
Uploads hash in a Web Worker, send one checksum-bound PUT up to 64 MiB and
|
|
62
|
+
resumable multipart above (`onState`/`resume`). The bucket must allow CORS
|
|
63
|
+
`PUT` from the app's origin with `Content-Type` and `x-amz-checksum-sha256`.
|
|
64
|
+
- **Content modules**, one per resource, each method one route of the
|
|
65
|
+
generated route table with its generated types: `posts`, `comments`,
|
|
66
|
+
`reactions`, `favorites`, `polls`, `bans` (`"owner"` or `"global"`),
|
|
67
|
+
`moderation`, `taxonomy`, `codes`. Lists take `limit`/`offset` (the review
|
|
68
|
+
queue a `cursor`); staff lists are `posts.adminList` (`q` searches,
|
|
69
|
+
`deleted: true` lists deleted posts for `posts.restore`), `polls.adminList`,
|
|
70
|
+
`comments.adminList`.
|
|
71
|
+
- **Images of posts and polls** go to the item's server-named upload path:
|
|
72
|
+
`media.uploadNamed(ref, file)` resolves the name content routes take;
|
|
73
|
+
`media.uploadInline(postId, file)` resolves a body image's public URL;
|
|
74
|
+
`posts.uploadCover`, `polls.uploadImage` and `polls.uploadOptionImage` do
|
|
75
|
+
both steps. `folders` renames the post and poll media kinds. A post's
|
|
76
|
+
public image files exist only while it is published; `usePost` shows an
|
|
77
|
+
unpublished post's images to its editors (below).
|
|
78
|
+
- **`client.config()`** says what the content module allows: which
|
|
79
|
+
interactions signed-out visitors may make (`content.Options.Anonymous`,
|
|
80
|
+
none by default) and the longest comment (`CommentMaxLength`, 400 by
|
|
81
|
+
default; longer is `comment_too_long` with `details.max`).
|
|
82
|
+
- **`client.subscribe(listener)`** receives every successful mutation
|
|
83
|
+
(`media.committed`, `media.processed`, `comment.created`, `reaction.changed`,
|
|
84
|
+
`poll.updated`, `ban.saved`, …); `ContentKitProvider onChange` is the same
|
|
85
|
+
stream, for the host's cache.
|
|
86
|
+
- **`ContentKitProvider`** holds the client, URL config, `navigate`,
|
|
87
|
+
`onChange`, `onError` and the read store hooks share: one request per item
|
|
88
|
+
and options while any hook shows it; `set()` and processed uploads update
|
|
89
|
+
every reader in place.
|
|
90
|
+
- **`ContentKitUiProvider`**: `appearance` (`theme`
|
|
91
|
+
`light`/`dark`/`auto`/`inherit`, `variables` as `--ckui-*`), `language`
|
|
92
|
+
(loads its built-in bundle), `messages` (bundles over it), `t` (host
|
|
93
|
+
translate hook), `density` (default `[2, 3]`), `inlinePreview` (default true).
|
|
94
|
+
|
|
95
|
+
## Hooks
|
|
96
|
+
|
|
97
|
+
Hooks read the client from `ContentKitProvider`; a `client` option overrides it.
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const q = useUploadQueue({ ref, path: "originals/{name}" });
|
|
101
|
+
q.add(input.files!); q.move(id, 0); await q.commit();
|
|
102
|
+
|
|
103
|
+
const one = useUpload();
|
|
104
|
+
await one.upload(file, { ref, path: "inline/x.png", put: {} });
|
|
105
|
+
|
|
106
|
+
const img = useSlotImage({ ref, path: "avatar", image });
|
|
107
|
+
const crop = useSlotCrop({ ref, path: "avatar", file: img.file, aspect: "1:1", onSaved: img.set });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Content hooks share one store per client: one request per item while any
|
|
111
|
+
hook shows it, kept per `viewer`, with every change made through the client
|
|
112
|
+
applied in place. Reactions, favorites and votes apply at once and roll back
|
|
113
|
+
if the server refuses them.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
const thread = useComments(ref, { sort: "best" });
|
|
117
|
+
await thread.post("Nice!", { replyTo: parentId });
|
|
118
|
+
await thread.react(comment, 1);
|
|
119
|
+
const { counts, toggle } = useReaction(ref);
|
|
120
|
+
const { favorited, count, toggle: fav } = useFavorite(ref);
|
|
121
|
+
const { poll, vote } = usePoll(null, { language }); // the newest live poll
|
|
122
|
+
const editor = usePollEditor(pollId); // staff
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A post editor puts `editorBody` in its rich-text field and inserts what
|
|
126
|
+
`uploadImage(file)` resolves: URLs that show the images now (the file for a
|
|
127
|
+
fresh upload, signed editor views of an unpublished post's images, read and
|
|
128
|
+
refreshed as needed). `update({ body })` stores the images' public URLs
|
|
129
|
+
(`storedBody(html)` does the same for a host's own save); `imageSrc(url)` shows
|
|
130
|
+
the cover.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
const editor = usePost(postId);
|
|
134
|
+
<RichText value={editor.editorBody} onImage={editor.uploadImage} onSave={(html) => editor.update({ body: html })} />
|
|
135
|
+
<img src={editor.imageSrc(editor.post?.cover_url)} alt="" />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Also `useContentConfig` (`client.config()`, read once), `useCommentReplies`,
|
|
139
|
+
`useCanComment` (may the caller comment, may signed-out visitors, the ban
|
|
140
|
+
that stops it, and what it may do to others' comments), `useLatestComments`,
|
|
141
|
+
`usePolls`, `usePosts` (staff: `admin`, `deleted`, `q`), `usePost` (with `restore`), and for staff `useAdminComments`,
|
|
142
|
+
`useModerationQueue`, `useCommentBans`.
|
|
143
|
+
|
|
144
|
+
Also: `useCrop`, `useVideoImages`, `useFrameStrip`, `useVideoFrame`,
|
|
145
|
+
`useVideoPoster`, `useEncodeProgress`, `useHlsPlayer`, `useCarousel`,
|
|
146
|
+
`useGalleryView`, `useInlinePreview`, `useRefreshBeforeExpiry`, `usePresets`,
|
|
147
|
+
`useContentKitClient`, `useContentURLs`, `useErrorReporter`.
|
|
148
|
+
|
|
149
|
+
### useMediaRead
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
const { read, loading, error, processing, reload, refresh, set } =
|
|
153
|
+
useMediaRead(ref, { prefix: "low-res/", editor: true, window: { start: 0, end: 120 } });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
One read per item and options, shared by every hook showing it. It reads
|
|
157
|
+
again shortly before the URLs expire, waits out `rate_limited`, polls while
|
|
158
|
+
uploads process (editor reads, every 2.5 s; `poll`), and reloads after any
|
|
159
|
+
commit through the same client. `window` splits a long item into reads of
|
|
160
|
+
`chunk` files (default 50) merged into one result; windows once read stay
|
|
161
|
+
loaded. `refresh()` joins a read in flight (a player's grant refresh);
|
|
162
|
+
`reload()` restarts it. `read` shows a host's read until `reload()`.
|
|
163
|
+
|
|
164
|
+
### useMediaFolder
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
const folder = useMediaFolder(ref, { paths: ["images/{name}", "videos/{name}"], commit: "manual" });
|
|
168
|
+
folder.add(files); // screened, queued; returns the refusals
|
|
169
|
+
await folder.commit(); // the uploaded files, in queue order
|
|
170
|
+
await folder.move(path, 0); await folder.rename(path, "cover");
|
|
171
|
+
await folder.edit(path, edit); await folder.replace(path, file); await folder.remove([path]);
|
|
172
|
+
folder.discard(); // stop uploads and empty the queue
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
An item's folder for its editors: the editor read (all windows), an upload
|
|
176
|
+
queue, and the kind's upload rules from the editor read, which screen type,
|
|
177
|
+
size and file caps before anything uploads and name files around names
|
|
178
|
+
taken. `groups` lists each path's uploads; `commit: "auto"` commits each
|
|
179
|
+
upload once those before it have (a draft). Failures go to the provider's
|
|
180
|
+
`onError` with an operation (`upload`, `folder.commit`, `folder.update`,
|
|
181
|
+
`folder.process` for uploads the worker fails after the editor opened) and
|
|
182
|
+
the file's name.
|
|
183
|
+
|
|
184
|
+
### useEditorCrop
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
const crop = useEditorCrop(ref, cropping); // a path, or null
|
|
188
|
+
// crop.status: idle | loading | cropping (source, edit) | saving | done | error
|
|
189
|
+
await crop.save(edit);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Re-crops a kept upload: while the path is set, its editor view (the
|
|
193
|
+
original, oriented, unedited) and current edit load; `save` commits an edit
|
|
194
|
+
op. Nothing is uploaded.
|
|
195
|
+
|
|
196
|
+
### usePublicImage
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
const { image, rule, isDefault } = usePublicImage("user", userId, "avatar");
|
|
200
|
+
<SlotImage image={image} fallback={<Initials name={name} />} />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
An item's image for a public preset: the exact renditions from a read's
|
|
204
|
+
`public`, or from a host listing passed as `image` (no read), else the
|
|
205
|
+
kind's default image. Published files carry a generation, so a URL built
|
|
206
|
+
from a preset template only ever names the default; `rule` (from
|
|
207
|
+
`GET /media/presets`) gives the shape, widths and narrowest edit.
|
|
208
|
+
|
|
209
|
+
### useCanonicalContent
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
useCanonicalContent(video.link, { title: video.title, image: posterURL, location: pathname + search, defaultLanguage: "en" });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Once a content page's link arrives, replaces the address with its canonical
|
|
216
|
+
path (code spelling, merged code, current slug) through the provider's
|
|
217
|
+
`navigate`, else `history.replaceState`, and keeps `<link rel=canonical>`,
|
|
218
|
+
`og:url`, `og:title`, `og:image` and hreflang alternates (each language's
|
|
219
|
+
own slug, plus `x-default`) in the head while mounted. Needs the provider's
|
|
220
|
+
`urls`; `/urls` also exports `hreflang()` and `canonicalURL()` (drops
|
|
221
|
+
tracking parameters) for server rendering.
|
|
222
|
+
|
|
223
|
+
### useNearViewport
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
const [ref, near] = useNearViewport({ margin: "800px 0px", leaveMargin: "2400px 0px" });
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Whether an element is near the viewport, to mount media as it approaches;
|
|
230
|
+
with `leaveMargin`, false again far away.
|
|
231
|
+
|
|
232
|
+
## Components
|
|
233
|
+
|
|
234
|
+
`AvatarUpload`, `CoverUpload` (complete form fields); `SlotEditor` with
|
|
235
|
+
`SlotEditMenu`, `SlotEditError` and `useSlotEditor()` (the same pick, crop,
|
|
236
|
+
save and re-crop flow inside a host layout); `SlotImage`; `ImageCropDialog`;
|
|
237
|
+
`VideoPosterPicker`, `VideoPoster`; `EncodeProgress`; `RenditionImg`;
|
|
238
|
+
`MediaGallery`, `VideoPlayer` and `VideoMiniPlayer` (HLS with ABR, inline
|
|
239
|
+
muted previews, a grant refresh before `expires`, and a reason, Retry and
|
|
240
|
+
support code for every failure; see below). A `PublicPreset` (`{ preset, aspect, renditions }`)
|
|
241
|
+
is what a slot or poster shows; a read's `public` lists `PublicImage`s.
|
|
242
|
+
|
|
243
|
+
### MediaFolderEditor
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<MediaFolderEditor item={post} paths={["images/{name}", "videos/{name}"]} commit="auto" ref={handle}
|
|
247
|
+
toolbar={<ZipImport />} rowActions={(f) => <SetCover file={f} />} footer={note} />
|
|
248
|
+
handle.current.discard();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`useMediaFolder` with its UI: a drop zone stating the rules, a queue with
|
|
252
|
+
progress, retry and remove, and each path's uploads as a sortable list with
|
|
253
|
+
thumbnail, name, edited badge, encode progress and failure; crop (with
|
|
254
|
+
`aspects` when the path has no preset shape), rename, replace and bulk
|
|
255
|
+
remove. `commit="manual"` adds the queue with "Add N"; `"auto"` commits as
|
|
256
|
+
files finish. `confirmRemove` replaces the browser's confirm.
|
|
257
|
+
|
|
258
|
+
### SortableList
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
<SortableList items={rows} id={(r) => r.id} name={(r) => r.title} onMove={(from, to) => move(from, to)} layout="grid">
|
|
262
|
+
{(row, handle) => <>{handle}<Row row={row} /></>}
|
|
263
|
+
</SortableList>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Reorders by a drag handle with a mouse, a finger or the keyboard (Space or
|
|
267
|
+
Enter lifts, arrows move, Space or Enter drops, Escape cancels), with
|
|
268
|
+
localized screen reader announcements. `onMove` fires once, on drop.
|
|
269
|
+
|
|
270
|
+
### ImageCropDialog shapes
|
|
271
|
+
|
|
272
|
+
`aspects={["", "1:1", "4:5", "16:9"]}` adds a shape chooser (`""` is the
|
|
273
|
+
original's, turning with the image); a preview of the result and its size
|
|
274
|
+
sit under the controls (`preview={false}` hides them), and Reset returns to
|
|
275
|
+
the starting shape and edit.
|
|
276
|
+
|
|
277
|
+
### MediaGallery item
|
|
278
|
+
|
|
279
|
+
`<MediaGallery item={post} prefix="low-res/" renderLocked={unlock} />` reads
|
|
280
|
+
through the client: the read (refreshed, reloaded after commits), HLS bases,
|
|
281
|
+
playlist auth and the grant refresh. Passing `read`, `hlsBase`, `xhrSetup`
|
|
282
|
+
and `refresh` yourself still works. A locked item's public teaser carries
|
|
283
|
+
the locked count and `renderLocked`, so the unlock is on the first slide;
|
|
284
|
+
the stage is never taller than `maxHeight` (default `80svh`).
|
|
285
|
+
|
|
286
|
+
### VideoPosterPicker frames from another video
|
|
287
|
+
|
|
288
|
+
`frames={{ item: otherVideo, path: "source" }}` browses another item's video;
|
|
289
|
+
the chosen frame (cropped or whole) is uploaded to the poster path as an
|
|
290
|
+
image. Which video to offer is the host's choice.
|
|
291
|
+
|
|
292
|
+
### HoverPreview
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
<div className="card relative"><Thumbnail /><HoverPreview item={video} duration={video.duration} /></div>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
A card's muted inline preview over the client's HLS base, fading in on hover
|
|
299
|
+
(or while it is the most visible card on touch screens), one page-wide, off
|
|
300
|
+
with reduced motion or Save-Data.
|
|
301
|
+
|
|
302
|
+
### Images: fallback, skeleton, fit and LazyMount
|
|
303
|
+
|
|
304
|
+
`RenditionImg`, `SlotImage` and `VideoPoster` take `fallback` (shown without
|
|
305
|
+
renditions or after a failed load, e.g. a 404), and `RenditionImg` and
|
|
306
|
+
`SlotImage` take `skeleton` (a pulsing fill until loaded) and `fit`
|
|
307
|
+
(`cover` or `contain`). `<LazyMount placeholder={box}>` mounts its children
|
|
308
|
+
as it nears the viewport (`useNearViewport` options).
|
|
309
|
+
|
|
310
|
+
### MediaReadinessNotice
|
|
311
|
+
|
|
312
|
+
`<MediaReadinessNotice item={post} description="Only editors see it until then." />`
|
|
313
|
+
renders nothing once every upload is processed; while some process, a
|
|
314
|
+
notice with the first video's encode progress; when uploads failed, which
|
|
315
|
+
ones.
|
|
316
|
+
|
|
317
|
+
## Video player
|
|
318
|
+
|
|
319
|
+
`VideoPlayer` plays a ladder's HLS folder (`client.media.hlsBase(ref, dir)`)
|
|
320
|
+
with its own controls: seek bar with sprite preview, volume, subtitles,
|
|
321
|
+
audio, quality (Auto or a fixed rung), speed, picture in picture, theater,
|
|
322
|
+
fullscreen and a mini player.
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
<VideoPlayer
|
|
326
|
+
base={contentkit.media.hlsBase(ref, "hls/")}
|
|
327
|
+
xhrSetup={contentkit.media.xhrSetup}
|
|
328
|
+
refresh={reload} expires={read.expires} // re-grant before the token expires
|
|
329
|
+
width={video.w} height={video.h} duration={video.dur} poster={posterPreset}
|
|
330
|
+
autoPlay startAt={resumePoint} // resume; never a muted autoplay
|
|
331
|
+
keyboard="global" // a watch page; default "focus"
|
|
332
|
+
audioLanguage={lang} subtitleLanguage={null} // until the viewer chooses
|
|
333
|
+
onProgress={(p) => saveResume(p.time, p.played)} // interval, pause, seek, end, page hide, unload
|
|
334
|
+
onEvent={(e) => analytics(e)} // play, pause, seek, quality, audio, subtitles, fullscreen, error…
|
|
335
|
+
theater={theater} onTheaterChange={setTheater}
|
|
336
|
+
onMiniPlayer={setHandoff}
|
|
337
|
+
renderDownloads={({ className, container }) => <Downloads className={className} container={container} />}
|
|
338
|
+
menuItems={[{ label: "Version", value, options, onChange }, { label: "Report a problem", onSelect }]}
|
|
339
|
+
/>
|
|
340
|
+
{handoff && <VideoMiniPlayer handoff={handoff} xhrSetup={contentkit.media.xhrSetup} onExpand={openWatchPage} onClose={() => setHandoff(null)} />}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
- **Shortcuts:** Space/K play, ←/→ 5 s, J/L 10 s, ↑/↓ volume, M mute, C
|
|
344
|
+
subtitles, F fullscreen, T theater, I mini player, 0–9 a tenth of the way,
|
|
345
|
+
Home/End, `<`/`>` speed. Never while typing or in a menu.
|
|
346
|
+
- **Touch:** a tap shows or hides the controls; a double tap on either side
|
|
347
|
+
seeks 10 s, and further taps keep seeking. A double click is fullscreen.
|
|
348
|
+
- **Remembered per browser:** volume and mute (`ckui.player.volume`), the
|
|
349
|
+
audio and subtitle languages (`ckui.player.tracks`) and a fixed quality
|
|
350
|
+
(`ckui.player.quality`); each `*Key` option renames or disables one.
|
|
351
|
+
- **Mini player:** `onMiniPlayer` gets a `PlayerHandoff` (position, playing,
|
|
352
|
+
languages, size, poster). `VideoMiniPlayer` docks it in a corner and calls
|
|
353
|
+
`onExpand({ time, playing })` or `onClose({ time })`; where it lives and
|
|
354
|
+
what expand opens are the host's.
|
|
355
|
+
- **Theming:** `appearance.variables.playerAccent` colors the played range
|
|
356
|
+
and pressed toggles (default white). Menus are always dark.
|
|
357
|
+
|
|
358
|
+
## Comments, reactions and polls
|
|
359
|
+
|
|
360
|
+
`Comments` (threads with one-level replies, tombstones, held and rejected
|
|
361
|
+
states shown to their author, reactions, edit, delete, ban and rate-limit
|
|
362
|
+
notices, the server's longest comment), `ReactionButtons`, `FavoriteButton` (with the server's count)
|
|
363
|
+
and `Poll` (a final vote with results scaled to the leading option, or a
|
|
364
|
+
free-text answer and its groups; `results="always"` shows results before
|
|
365
|
+
voting). Signed-out visitors comment under a name, react and vote where the
|
|
366
|
+
server allows it (`content.Options.Anonymous`, read from the comment standing
|
|
367
|
+
and `client.config()`); elsewhere they are asked to sign in (`onSignIn`, the
|
|
368
|
+
provider's or their own) or, without one, told to.
|
|
369
|
+
|
|
370
|
+
Staff: `CommentModeration` (every comment, the review queue, the site's
|
|
371
|
+
bans), `CommentBans` with `CommentBanDialog`, and `PollEditor`. They show what
|
|
372
|
+
the caller's permissions allow and say so when a route refuses; the host
|
|
373
|
+
decides who sees them.
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
<Comments item={{ kind: "video", id }} count={video.comment_count} userHref={(u) => `/u/${u.username}`} />
|
|
377
|
+
<Poll language={lang} />
|
|
378
|
+
<CommentModeration contentKinds={["video", "post"]} itemHref={(i) => `/${i.kind}/${i.id}`} />
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Errors
|
|
382
|
+
|
|
383
|
+
Every call rejects with `ContentKitError`: `code` (the server's, or `network`,
|
|
384
|
+
`storage`, `aborted`, `resume_mismatch`, `decode`, `render_timeout`),
|
|
385
|
+
`status`, `retryAfter`, `details` (image and video limits), `blobs`
|
|
386
|
+
(`not_uploaded`), `ban` (`comment_banned`) and `action` (`rate_limited`),
|
|
387
|
+
plus `refusal`, `isLimit`, `isCeiling`, `blocksQueue` and `transient`. Every
|
|
388
|
+
code has a message in every bundle (`useMessages().error(e)`).
|
|
389
|
+
|
|
390
|
+
## Migrating from the two old packages
|
|
391
|
+
|
|
392
|
+
| Before | Now |
|
|
393
|
+
| --- | --- |
|
|
394
|
+
| `@openrails/contentkit-upload` | `@openrails/contentkit-ui/client` |
|
|
395
|
+
| `@openrails/contentkit-upload/react` | `@openrails/contentkit-ui/react` |
|
|
396
|
+
| `@openrails/contentkit-upload/ui` | `@openrails/contentkit-ui` |
|
|
397
|
+
| `@openrails/contentkit-upload/locales/*`, `/styles.css` | `@openrails/contentkit-ui/locales/*`, `/styles.css` |
|
|
398
|
+
| `@openrails/contentkit-urls` | `@openrails/contentkit-ui/urls` |
|
|
399
|
+
| `createUploadClient({ endpoint, readEndpoint })` | `createContentKitClient({ baseUrl, mounts: { upload, media } }).media` |
|
|
400
|
+
| `UploadClient`, `UploadApi` | `MediaClient`, `MediaApi` |
|
|
401
|
+
| `UploadUiProvider client onError …` | `ContentKitProvider client onError` + `ContentKitUiProvider appearance messages t density inlinePreview` |
|
|
402
|
+
| `UploadError`, `UploadErrorCode` | `ContentKitError`, `ContentKitErrorCode` |
|
|
403
|
+
| `useUploadQueue(client, o)`, `useUpload(client)` | `useUploadQueue(o)`, `useUpload()` |
|
|
404
|
+
| `useRead(client, ref, o)` | `useMediaRead(ref, o)` |
|
|
405
|
+
| `useSlotImage(client, o)`, `useSlotCrop(client, o)` | `useSlotImage(o)`, `useSlotCrop(o)` |
|
|
406
|
+
| `useVideoImages`, `useFrameStrip`, `useVideoFrame`, `useVideoPoster` `(client, o)` | the same `(o)` |
|
|
407
|
+
| `PublicImage` (preset at an item) | `PublicPreset` |
|
|
408
|
+
| `SlotImage`, `VideoPoster` `placeholder` | `fallback` |
|
|
409
|
+
| `UploadUi*` types, `UploadUiRoot` | `ContentKitUi*`, `ContentKitUiRoot` |
|
|
410
|
+
|
|
411
|
+
## Development
|
|
412
|
+
|
|
413
|
+
```sh
|
|
414
|
+
pnpm check # typecheck, lint, unit + jsdom tests, build
|
|
415
|
+
CONTENTKIT_TEST_URL=… CONTENTKIT_TEST_S3_ENDPOINT=… CONTENTKIT_TEST_S3_ACCESS_KEY=… CONTENTKIT_TEST_S3_SECRET_KEY=… \
|
|
416
|
+
pnpm test:integration # the real handlers (contentkit.Runtime.Handler) over PostgreSQL and MinIO
|
|
417
|
+
pnpm build && pnpm screenshots # demo/ in Chromium, light/dark × desktop/mobile
|
|
418
|
+
# (social.html needs the integration variables)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The wire types, route table and error codes in `src/client/generated/` are
|
|
422
|
+
generated from ContentKit's route catalog (`pnpm contract`; CI runs
|
|
423
|
+
`pnpm contract:check`). UI primitives come from
|
|
424
|
+
`pnpm dlx shadcn@4.21.0 add …` (`components.json`); local edits are marked
|
|
425
|
+
`// Local:`. The `release` workflow publishes the package to npm for every
|
|
426
|
+
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"}
|