@openrails/contentkit-ui 0.85.1 → 0.87.0-rc.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 +28 -7
- package/dist/client/beacons.d.ts +13 -4
- package/dist/client/beacons.js +14 -7
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +29 -27
- package/dist/client/client.js.map +1 -1
- package/dist/client/codes.d.ts +6 -1
- package/dist/client/codes.js +4 -1
- package/dist/client/codes.js.map +1 -1
- package/dist/client/content/types.d.ts +4 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/discovery.js +3 -0
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/errors.d.ts +6 -0
- package/dist/client/errors.js +6 -2
- package/dist/client/errors.js.map +1 -1
- package/dist/client/gallery.d.ts +3 -5
- package/dist/client/gallery.js +16 -20
- package/dist/client/gallery.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +9 -1
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +29 -3
- package/dist/client/generated/routes.js +44 -3
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +60 -10
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/media/queue.d.ts +4 -2
- package/dist/client/media/queue.js +18 -12
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client/media/rules.d.ts +7 -1
- package/dist/client/media/rules.js +27 -21
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/media/windows.js +1 -1
- package/dist/client/media/windows.js.map +1 -1
- package/dist/client/stats.d.ts +32 -0
- package/dist/client/stats.js +35 -0
- package/dist/client/stats.js.map +1 -0
- package/dist/client/versions.d.ts +6 -4
- package/dist/client/versions.js +24 -26
- package/dist/client/versions.js.map +1 -1
- package/dist/client.js +30 -29
- package/dist/components/admin/content-manager.js +206 -206
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/admin/group-manager.js +71 -70
- package/dist/components/admin/group-manager.js.map +1 -1
- package/dist/components/admin/media-item-panel.d.ts +24 -7
- package/dist/components/admin/media-item-panel.js +209 -197
- package/dist/components/admin/media-item-panel.js.map +1 -1
- package/dist/components/admin/poll-manager.js +25 -25
- package/dist/components/admin/stats-dashboard.d.ts +22 -0
- package/dist/components/admin/stats-dashboard.js +257 -0
- package/dist/components/admin/stats-dashboard.js.map +1 -0
- package/dist/components/content/card.d.ts +5 -3
- package/dist/components/content/card.js +155 -133
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/download-button.d.ts +26 -0
- package/dist/components/content/download-button.js +51 -0
- package/dist/components/content/download-button.js.map +1 -0
- package/dist/components/content/editors.js +455 -427
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/grid.d.ts +8 -1
- package/dist/components/content/grid.js +153 -104
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/index.d.ts +4 -2
- package/dist/components/content/listing.d.ts +3 -1
- package/dist/components/content/listing.js +34 -34
- package/dist/components/content/listing.js.map +1 -1
- package/dist/components/content/paywall.d.ts +17 -5
- package/dist/components/content/paywall.js +115 -100
- package/dist/components/content/paywall.js.map +1 -1
- package/dist/components/content/reader-stats.d.ts +12 -0
- package/dist/components/content/reader-stats.js +47 -0
- package/dist/components/content/reader-stats.js.map +1 -0
- package/dist/components/groups/banner.js +33 -33
- package/dist/components/groups/directory.js +11 -11
- package/dist/components/groups/editor.js +293 -293
- package/dist/components/groups/editor.js.map +1 -1
- package/dist/components/groups/header.js +27 -27
- package/dist/components/groups/header.js.map +1 -1
- package/dist/components/groups/memberships.js +180 -180
- package/dist/components/groups/parts.d.ts +0 -2
- package/dist/components/groups/parts.js +9 -27
- package/dist/components/groups/parts.js.map +1 -1
- package/dist/components/media-folder-editor.js +239 -189
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/media-gallery.d.ts +1 -4
- package/dist/components/media-gallery.js +19 -34
- package/dist/components/media-gallery.js.map +1 -1
- package/dist/components/notifications.js +17 -17
- package/dist/components/slot-upload.d.ts +2 -2
- package/dist/components/slot-upload.js +84 -79
- package/dist/components/slot-upload.js.map +1 -1
- package/dist/components/social/bans.js +34 -34
- package/dist/components/social/comments.js +1 -1
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +2 -1
- package/dist/components/social/engagement.js +5 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/components/social/moderation.js +52 -48
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/components/video-player.d.ts +2 -0
- package/dist/components/video-player.js +318 -314
- package/dist/components/video-player.js.map +1 -1
- package/dist/i18n/context.d.ts +2 -0
- package/dist/i18n/context.js +23 -5
- package/dist/i18n/context.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -10
- package/dist/locales/admin/de.js +27 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +27 -0
- package/dist/locales/admin/en.js +27 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +27 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +27 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +27 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +27 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/content/de.js +6 -0
- package/dist/locales/content/de.js.map +1 -1
- package/dist/locales/content/en.d.ts +6 -0
- package/dist/locales/content/en.js +6 -0
- package/dist/locales/content/en.js.map +1 -1
- package/dist/locales/content/es.js +6 -0
- package/dist/locales/content/es.js.map +1 -1
- package/dist/locales/content/ja.js +6 -0
- package/dist/locales/content/ja.js.map +1 -1
- package/dist/locales/content/ko.js +6 -0
- package/dist/locales/content/ko.js.map +1 -1
- package/dist/locales/content/zh.js +6 -0
- package/dist/locales/content/zh.js.map +1 -1
- package/dist/locales/de.js +14 -2
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +3 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +3 -0
- package/dist/locales/discovery/en.js +3 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +3 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +3 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +3 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +3 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +50 -2
- package/dist/locales/en.js +14 -2
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +14 -2
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +14 -2
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +14 -2
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +14 -2
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/admin.js +22 -23
- package/dist/react/admin.js.map +1 -1
- package/dist/react/canonical.d.ts +4 -3
- package/dist/react/canonical.js +34 -33
- package/dist/react/canonical.js.map +1 -1
- package/dist/react/comments.d.ts +1 -1
- package/dist/react/comments.js +3 -2
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.js +144 -144
- package/dist/react/contents.js.map +1 -1
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/detail.d.ts +12 -6
- package/dist/react/detail.js +31 -24
- package/dist/react/detail.js.map +1 -1
- package/dist/react/discovery.d.ts +11 -0
- package/dist/react/discovery.js +49 -30
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/engagement.d.ts +6 -2
- package/dist/react/engagement.js +2 -6
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.d.ts +7 -2
- package/dist/react/folder.js +165 -147
- package/dist/react/folder.js.map +1 -1
- package/dist/react/groups.d.ts +0 -1
- package/dist/react/groups.js +13 -12
- package/dist/react/groups.js.map +1 -1
- package/dist/react/index.d.ts +6 -4
- package/dist/react/location.d.ts +11 -0
- package/dist/react/location.js +18 -0
- package/dist/react/location.js.map +1 -0
- package/dist/react/public.d.ts +8 -0
- package/dist/react/public.js +32 -27
- package/dist/react/public.js.map +1 -1
- package/dist/react/stats.d.ts +24 -0
- package/dist/react/stats.js +28 -0
- package/dist/react/stats.js.map +1 -0
- package/dist/react/use-resource.js +8 -7
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react/versions.d.ts +25 -5
- package/dist/react/versions.js +61 -40
- package/dist/react/versions.js.map +1 -1
- package/dist/react.js +26 -24
- package/dist/styles.css +1 -1
- package/dist/urls/index.d.ts +35 -10
- package/dist/urls.js +100 -70
- package/dist/urls.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-manager.js","names":[],"sources":["../../../src/components/admin/content-manager.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport { imageRefs } from \"../../client/contents.js\";\nimport type { Content } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContent } from \"../../react/contents.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button, buttonVariants } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"../social/bans.js\";\nimport { contentError, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { ContentSections } from \"../content/editors.js\";\nimport type { Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Pager, Rows, pageOf, useConfirm, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\n/** What ContentManager hands its body editor. */\nexport interface BodyEditorProps {\n /** The body as HTML, its images at URLs that show them now. */\n value: string;\n onChange: (html: string) => void;\n /** Uploads an image to the item and resolves with a URL that shows it; absent until the item exists. */\n uploadImage?: (file: File) => Promise<string>;\n disabled?: boolean;\n placeholder?: string;\n \"aria-label\"?: string;\n}\n\nexport interface ContentManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The content kind managed. */\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /**\n * The body editor: `richTextEditor` from `@openrails/contentkit-ui/editor`,\n * or the host's own. Without it, the body is a plain HTML field.\n */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n /** Display names of kinds, traits, tiers, attributes and values by identifier. */\n labels?: Labels;\n pageSize?: number;\n}\n\n/** Whether the caller manages content: root:content:write holders, creators and owner-group editors. */\nexport function useContentManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"contents\"> {\n return useStaffAccess([\"contents\"] as const, o);\n}\n\nexport type ContentStatus = \"draft\" | \"scheduled\" | \"published\" | \"held\" | \"rejected\";\n\n/** An item's status as the manager names it. */\nexport function contentStatus(c: Content): ContentStatus {\n if (c.moderation === \"rejected\") return \"rejected\";\n if (c.moderation === \"held\") return \"held\";\n if (c.state === \"draft\") return \"draft\";\n if (!c.live) return \"scheduled\";\n return \"published\";\n}\n\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst badge: Record<ContentStatus, \"default\" | \"secondary\" | \"outline\" | \"destructive\"> = { published: \"default\", scheduled: \"secondary\", draft: \"outline\", held: \"secondary\", rejected: \"destructive\" };\nconst states = [\"draft\", \"scheduled\", \"published\"] as const;\n\n/**\n * Content items of one kind for their creators and staff: every one the\n * caller manages, by state and text, deleted ones on their own to restore;\n * create and edit title, slug, description, schedule, cover and body with\n * inline images per language, publish or keep a draft, and delete; then its\n * listing and attributes, access rule and price, versions (order, languages,\n * traits, schedule, access override, price, segments, media) and groups. The\n * list is at basePath, a new item at basePath/new and each item at\n * basePath/{id}, filters in the query.\n */\nexport function ContentManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"content-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Contents {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<ContentManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Contents({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useContentManagerAccess({ client: p.client });\n if (access.loading) return <Rows />;\n if (!access.allowed.contents) return <NoAccess>{t(\"contentManager.noAccess\")}</NoAccess>;\n if (!loc.view) return <List {...p} loc={loc} />;\n return (\n <>\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(\"\")}>\n {t(\"contentManager.back\")}\n </Button>\n <ContentEditor\n key={loc.view}\n {...p}\n id={loc.view === \"new\" ? null : loc.view}\n onCreated={(c) => loc.go(c.id, undefined, { replace: true })}\n onDeleted={() => loc.go(\"\", undefined, { replace: true })}\n />\n </>\n );\n}\n\nfunction List({ loc, client, kind, languages: offered, contentHref, pageSize = 20, onError }: Inner & { loc: AdminLocation }) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const language = choices(offered)[0]?.value;\n const query = loc.query.get(\"q\") ?? \"\";\n const status = states.find((s) => s === loc.query.get(\"status\")) ?? \"\";\n const deleted = loc.query.get(\"deleted\") === \"1\";\n const page = pageOf(loc.query);\n const filters = { q: query, status, deleted: deleted ? \"1\" : undefined };\n const filter = (next: Partial<typeof filters>) => loc.go(\"\", { ...filters, ...next });\n const [text, setText] = useState(query);\n useEffect(() => setText(query), [query]);\n const { store, scope } = useContentScope(client);\n const state = deleted ? \"deleted\" : status || undefined;\n const list = usePaged(store, keyOf(\"contents-page\", scope, kind, query, state, pageSize), { type: \"contents\", scope: deleted ? \"deleted\" : undefined }, page, pageSize, (q, signal) =>\n client.contents.manage({ ...q, kind, q: query || undefined, state, language }, signal),\n );\n const del = useAction(\"content.save\", onError);\n const { confirm, dialog } = useConfirm();\n const remove = async (c: Content) => {\n if (await confirm({ title: t(\"contentManager.deleteTitle\", { title: c.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") })) {\n await del.run(() => client.contents.remove(c.id));\n }\n };\n return (\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"contentManager.title\")}</h2>\n <Button size=\"sm\" onClick={() => loc.go(\"new\")}>\n {t(\"contentManager.new\")}\n </Button>\n </div>\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"contentManager.title\")}>\n <Button size=\"sm\" variant={deleted ? \"outline\" : \"default\"} aria-pressed={!deleted} onClick={() => loc.go(\"\", { q: query })}>\n {t(\"contentManager.items\")}\n </Button>\n <Button size=\"sm\" variant={deleted ? \"default\" : \"outline\"} aria-pressed={deleted} onClick={() => loc.go(\"\", { q: query, deleted: \"1\" })}>\n {t(\"contentManager.deleted\")}\n </Button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto]\">\n <form\n className=\"flex gap-2\"\n role=\"search\"\n onSubmit={(e) => {\n e.preventDefault();\n filter({ q: text.trim() });\n }}\n >\n <Label htmlFor={`${ids}-q`} className=\"sr-only\">\n {t(\"contentManager.search\")}\n </Label>\n <Input id={`${ids}-q`} className=\"h-8\" type=\"search\" value={text} placeholder={t(\"contentManager.search\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n {!deleted && (\n <Select value={status} onValueChange={(v) => filter({ status: String(v ?? \"\") as typeof status })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentManager.status\")}>\n <SelectValue>{(v: string) => t(v ? `contentManager.${v as (typeof states)[number]}` : \"contentManager.allStatuses\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"contentManager.allStatuses\")}</SelectItem>\n {states.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`contentManager.${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <ErrorLine error={del.error} />\n {list.loading && !list.items.length ? (\n <Rows />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"contentManager.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(deleted ? \"contentManager.noDeleted\" : \"contentManager.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={list.loading}>\n {list.items.map((c) => {\n const s = contentStatus(c);\n const href = s === \"published\" ? contentHref?.(c) : undefined;\n return (\n <li key={c.id} className=\"flex flex-wrap items-start gap-3 p-3\" data-ckui=\"content-row\" data-status={deleted ? \"deleted\" : s}>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n {deleted ? (\n <span className=\"font-medium break-words\">{c.title}</span>\n ) : (\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(c.id)}>\n {c.title}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {!deleted && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n <span>{c.language}</span>\n <RelativeTime at={deleted && c.deleted_at ? c.deleted_at : c.updated_at} />\n {s === \"scheduled\" && c.publish_at && <span>{t(\"pollManager.goesLive\", { date: formatDate(m, c.publish_at) })}</span>}\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={del.pending} onClick={() => void del.run(() => client.contents.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <>\n {href && (\n <a className={buttonVariants({ size: \"sm\", variant: \"ghost\" })} href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n <Button size=\"sm\" variant=\"outline\" onClick={() => loc.go(c.id)}>\n {t(\"comments.edit\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void remove(c)}>\n {t(\"comments.delete\")}\n </Button>\n </>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pager page={page} hasNext={list.hasNext} disabled={list.loading} onPage={(n) => loc.go(\"\", { ...filters, page: n > 1 ? n : undefined })} />\n {dialog}\n </div>\n );\n}\n\ninterface Form {\n title: string;\n slug: string;\n description: string;\n publishAt: string;\n body: string;\n}\n\nconst empty: Form = { title: \"\", slug: \"\", description: \"\", publishAt: \"\", body: \"\" };\n\nexport interface ContentEditorProps {\n /** The item edited; null creates one of kind. */\n id: string | null;\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /** The body editor; without it, a plain HTML field. */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n labels?: Labels;\n /** A new item's owner group: a channel its editors write for. */\n ownerGroup?: string;\n /** After create: show the new item (the editor goes on editing it). */\n onCreated?: (content: Content) => void;\n onDeleted?: (content: Content) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n}\n\n/**\n * One content item for its creators and staff: title, slug, description,\n * schedule, cover and body per language, publish, unpublish and delete;\n * then listing and attributes, access rule and price, versions and groups.\n */\nexport function ContentEditor({ id, client: own, kind, languages: offered, renderEditor, contentHref, labels, ownerGroup, onCreated, onDeleted, onError }: ContentEditorProps) {\n const client = useContentKitClient(own);\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const [language, setLanguage] = useState(languages[0]?.value ?? \"en\");\n const ed = useContent(id, kind, language, { client });\n const report = useErrorReporter(onError);\n // The body is kept as stored (image references); the editor shows it with URLs.\n const [form, setForm] = useState<Form | null>(id ? null : empty);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const { confirm, dialog } = useConfirm();\n const item = ed.content;\n useEffect(() => {\n if (!item) return;\n const stored = item.text?.[language];\n setForm((f) => f ?? { title: stored?.title ?? \"\", slug: stored?.slug ?? \"\", description: stored?.description ?? \"\", body: stored?.body ?? \"\",\n publishAt: item.publish_at && !item.live ? localInput(new Date(item.publish_at)) : \"\" });\n }, [item, language]);\n const { imageSrc } = ed;\n const shown = useMemo(\n () => (form?.body ?? \"\").replace(imageRefs(), (ref, name: string) => {\n const src = imageSrc(name);\n return src && src !== name ? src.replaceAll(\"&\", \"&\") : ref;\n }),\n [form?.body, imageSrc],\n );\n if (id && !form) {\n if (ed.error) return <LoadFailed error={ed.error} message={t(\"contentManager.loadFailed\")} retry={ed.reload} />;\n return (\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-48 w-full\" />\n </div>\n );\n }\n const f = form!;\n const set = (patch: Partial<Form>) => setForm((cur) => ({ ...cur!, ...patch }));\n const fail = (e: unknown) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"content.save\");\n };\n const text = () => ({ title: f.title.trim(), slug: f.slug.trim() || undefined, description: f.description.trim(), body: f.body });\n const save = async () => {\n setError(null);\n setNotice(null);\n if (!f.title.trim()) return setError(t(\"contentManager.titleRequired\"));\n try {\n if (!item) {\n const created = await ed.create({ kind, text: { [language]: text() }, owner_group_id: ownerGroup });\n onCreated?.(created);\n return;\n }\n const saved = await ed.putText(text());\n setNotice(t(\"pollEditor.saved\"));\n setForm((cur) => ({ ...cur!, slug: saved.text?.[language]?.slug ?? cur!.slug }));\n } catch (e) {\n fail(e);\n }\n };\n const publish = async (live: boolean) => {\n setError(null);\n setNotice(null);\n try {\n if (live) await ed.publish(f.publishAt ? new Date(f.publishAt).toISOString() : undefined);\n else await ed.unpublish();\n setNotice(t(live ? \"contentManager.publishedNotice\" : \"contentManager.unpublishedNotice\"));\n } catch (e) {\n fail(e);\n }\n };\n const remove = async () => {\n if (!item || !(await confirm({ title: t(\"contentManager.deleteTitle\", { title: item.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") }))) return;\n try {\n await ed.remove();\n onDeleted?.(item);\n } catch (e) {\n fail(e);\n }\n };\n const s = item ? contentStatus(item) : \"draft\";\n const href = item && s === \"published\" ? contentHref?.(item) : undefined;\n const editor: BodyEditorProps = {\n value: shown,\n onChange: (html) => set({ body: ed.storedBody(html) }),\n uploadImage: item ? (file) => ed.uploadImage(file) : undefined,\n disabled: ed.saving,\n placeholder: t(\"contentManager.bodyPlaceholder\"),\n \"aria-label\": t(\"contentManager.body\"),\n };\n return (\n <>\n <form\n className=\"grid gap-4\"\n data-ckui=\"content-form\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(item ? \"contentManager.editTitle\" : \"contentManager.newTitle\")}</h2>\n {item && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n {href && (\n <a className=\"text-xs text-muted-foreground hover:underline\" href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n </div>\n {item?.moderation && (\n <Alert variant={item.moderation === \"rejected\" ? \"destructive\" : \"default\"}>\n <AlertDescription>\n {item.moderation_reason\n ? t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedBecause\" : \"heldBecause\"}`, { reason: item.moderation_reason })\n : t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedNote\" : \"heldNote\"}`)}\n </AlertDescription>\n </Alert>\n )}\n {languages.length > 1 && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"contentManager.language\")}</Label>\n <Select\n value={language}\n onValueChange={(v) => {\n setLanguage(String(v ?? \"\"));\n setForm(item ? null : empty);\n }}\n >\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => languages.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-title`}>{t(\"contentManager.itemTitle\")}</Label>\n <Input id={`${ids}-title`} value={f.title} required maxLength={300} onChange={(e) => set({ title: e.target.value })} />\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-slug`}>{t(\"contentManager.slug\")}</Label>\n <Input id={`${ids}-slug`} value={f.slug} placeholder={t(\"contentManager.slugAuto\")} onChange={(e) => set({ slug: e.target.value })} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-publish`}>{t(\"contentManager.publishAt\")}</Label>\n <Input id={`${ids}-publish`} type=\"datetime-local\" value={f.publishAt} onChange={(e) => set({ publishAt: e.target.value })} />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-description`}>{t(\"contentManager.description\")}</Label>\n <Textarea id={`${ids}-description`} rows={2} value={f.description} onChange={(e) => set({ description: e.target.value })} />\n </div>\n <Cover item={item} src={ed.coverSrc} saving={ed.saving} onFile={(file) => void ed.setCover(file).catch(fail)} />\n <div className=\"grid gap-1.5\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.body\")}</span>\n {renderEditor ? renderEditor(editor) : <Textarea aria-label={editor[\"aria-label\"]} rows={12} value={f.body} placeholder={editor.placeholder} onChange={(e) => set({ body: e.target.value })} />}\n {!item && <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>}\n </div>\n {error && <ErrorLine>{typeof error === \"string\" ? error : contentError(m, error)}</ErrorLine>}\n {notice && !error && (\n <p role=\"status\" className=\"text-sm text-muted-foreground\">\n {notice}\n </p>\n )}\n <div className=\"flex flex-wrap gap-2 border-t border-border pt-4\">\n <Button type=\"submit\" disabled={ed.saving}>\n {ed.saving ? t(\"common.saving\") : t(item ? \"common.save\" : \"contentManager.createDraft\")}\n </Button>\n {item && !item.live && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(true)}>\n {t(\"contentManager.publish\")}\n </Button>\n )}\n {item?.state === \"published\" && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(false)}>\n {t(\"contentManager.unpublish\")}\n </Button>\n )}\n {item && (\n <Button type=\"button\" variant=\"destructive\" className=\"ms-auto\" disabled={ed.saving} onClick={() => void remove()}>\n {t(\"contentManager.delete\")}\n </Button>\n )}\n </div>\n {dialog}\n </form>\n {item && <ContentSections content={item} client={client} onError={onError} labels={labels} />}\n </>\n );\n}\n\nfunction Cover({ item, src, saving, onFile }: { item: Content | null; src?: string; saving: boolean; onFile: (file: File | null) => void }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"content-cover\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.cover\")}</span>\n {item ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {src ? <img src={src} alt=\"\" className=\"h-20 w-36 rounded-md border border-border object-cover\" /> : <span className=\"text-muted-foreground\">{t(\"contentManager.noCover\")}</span>}\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={saving} onClick={() => file.current?.click()}>\n {t(src ? \"pollEditor.replaceImage\" : \"contentManager.addCover\")}\n </Button>\n {src && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={() => onFile(null)}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n <input\n ref={file}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(\"contentManager.cover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) onFile(f);\n }}\n />\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"content-manager.js","names":[],"sources":["../../../src/components/admin/content-manager.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport { imageRefs } from \"../../client/contents.js\";\nimport type { Content } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContent } from \"../../react/contents.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button, buttonVariants } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"../social/bans.js\";\nimport { contentError, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { ContentSections } from \"../content/editors.js\";\nimport type { Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Pager, Rows, pageOf, useConfirm, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\n/** What ContentManager hands its body editor. */\nexport interface BodyEditorProps {\n /** The body as HTML, its images at URLs that show them now. */\n value: string;\n onChange: (html: string) => void;\n /** Uploads an image to the item and resolves with a URL that shows it; absent until the item exists. */\n uploadImage?: (file: File) => Promise<string>;\n disabled?: boolean;\n placeholder?: string;\n \"aria-label\"?: string;\n}\n\nexport interface ContentManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The content kind managed. */\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /**\n * The body editor: `richTextEditor` from `@openrails/contentkit-ui/editor`,\n * or the host's own. Without it, the body is a plain HTML field.\n */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n /** Display names of kinds, traits, tiers, attributes and values by identifier. */\n labels?: Labels;\n pageSize?: number;\n}\n\n/** Whether the caller manages content: root:content:write holders, creators and owner-group editors. */\nexport function useContentManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"contents\"> {\n return useStaffAccess([\"contents\"] as const, o);\n}\n\nexport type ContentStatus = \"draft\" | \"scheduled\" | \"published\" | \"held\" | \"rejected\";\n\n/** An item's status as the manager names it. */\nexport function contentStatus(c: Content): ContentStatus {\n if (c.moderation === \"rejected\") return \"rejected\";\n if (c.moderation === \"held\") return \"held\";\n if (c.state === \"draft\") return \"draft\";\n if (!c.live) return \"scheduled\";\n return \"published\";\n}\n\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst badge: Record<ContentStatus, \"default\" | \"secondary\" | \"outline\" | \"destructive\"> = { published: \"default\", scheduled: \"secondary\", draft: \"outline\", held: \"secondary\", rejected: \"destructive\" };\nconst states = [\"draft\", \"scheduled\", \"published\"] as const;\n\n/**\n * Content items of one kind for their creators and staff: every one the\n * caller manages, by state and text, deleted ones on their own to restore;\n * create and edit title, slug, description, schedule, cover and body with\n * inline images per language, publish or keep a draft, and delete; then its\n * listing and attributes, access rule and price, versions (order, languages,\n * traits, schedule, access override, price, segments, media) and groups. The\n * list is at basePath, a new item at basePath/new and each item at\n * basePath/{id}, filters in the query.\n */\nexport function ContentManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"content-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Contents {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<ContentManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Contents({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useContentManagerAccess({ client: p.client });\n if (access.loading) return <Rows />;\n if (!access.allowed.contents) return <NoAccess>{t(\"contentManager.noAccess\")}</NoAccess>;\n if (!loc.view) return <List {...p} loc={loc} />;\n return (\n <>\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(\"\")}>\n {t(\"contentManager.back\")}\n </Button>\n <ContentEditor\n key={loc.view}\n {...p}\n id={loc.view === \"new\" ? null : loc.view}\n onCreated={(c) => loc.go(c.id, undefined, { replace: true })}\n onDeleted={() => loc.go(\"\", undefined, { replace: true })}\n />\n </>\n );\n}\n\nfunction List({ loc, client, kind, languages: offered, contentHref, pageSize = 20, onError }: Inner & { loc: AdminLocation }) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const language = choices(offered)[0]?.value;\n const query = loc.query.get(\"q\") ?? \"\";\n const status = states.find((s) => s === loc.query.get(\"status\")) ?? \"\";\n const deleted = loc.query.get(\"deleted\") === \"1\";\n const page = pageOf(loc.query);\n const filters = { q: query, status, deleted: deleted ? \"1\" : undefined };\n const filter = (next: Partial<typeof filters>) => loc.go(\"\", { ...filters, ...next });\n const [text, setText] = useState(query);\n useEffect(() => setText(query), [query]);\n const { store, scope } = useContentScope(client);\n const state = deleted ? \"deleted\" : status || undefined;\n const list = usePaged(store, keyOf(\"contents-page\", scope, kind, query, state, pageSize), { type: \"contents\", scope: deleted ? \"deleted\" : undefined }, page, pageSize, (q, signal) =>\n client.contents.manage({ ...q, kind, q: query || undefined, state, language }, signal),\n );\n const del = useAction(\"content.save\", onError);\n const { confirm, dialog } = useConfirm();\n const remove = async (c: Content) => {\n if (await confirm({ title: t(\"contentManager.deleteTitle\", { title: c.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") })) {\n await del.run(() => client.contents.remove(c.id));\n }\n };\n return (\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"contentManager.title\")}</h2>\n <Button size=\"sm\" onClick={() => loc.go(\"new\")}>\n {t(\"contentManager.new\")}\n </Button>\n </div>\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"contentManager.title\")}>\n <Button size=\"sm\" variant={deleted ? \"outline\" : \"default\"} aria-pressed={!deleted} onClick={() => loc.go(\"\", { q: query })}>\n {t(\"contentManager.items\")}\n </Button>\n <Button size=\"sm\" variant={deleted ? \"default\" : \"outline\"} aria-pressed={deleted} onClick={() => loc.go(\"\", { q: query, deleted: \"1\" })}>\n {t(\"contentManager.deleted\")}\n </Button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto]\">\n <form\n className=\"flex gap-2\"\n role=\"search\"\n onSubmit={(e) => {\n e.preventDefault();\n filter({ q: text.trim() });\n }}\n >\n <Label htmlFor={`${ids}-q`} className=\"sr-only\">\n {t(\"contentManager.search\")}\n </Label>\n <Input id={`${ids}-q`} className=\"h-8\" type=\"search\" value={text} placeholder={t(\"contentManager.search\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n {!deleted && (\n <Select value={status} onValueChange={(v) => filter({ status: String(v ?? \"\") as typeof status })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentManager.status\")}>\n <SelectValue>{(v: string) => t(v ? `contentManager.${v as (typeof states)[number]}` : \"contentManager.allStatuses\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"contentManager.allStatuses\")}</SelectItem>\n {states.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`contentManager.${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <ErrorLine error={del.error} />\n {list.loading && !list.items.length ? (\n <Rows />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"contentManager.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(deleted ? \"contentManager.noDeleted\" : \"contentManager.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={list.loading}>\n {list.items.map((c) => {\n const s = contentStatus(c);\n const href = s === \"published\" ? contentHref?.(c) : undefined;\n return (\n <li key={c.id} className=\"flex flex-wrap items-start gap-3 p-3\" data-ckui=\"content-row\" data-status={deleted ? \"deleted\" : s}>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n {deleted ? (\n <span className=\"font-medium break-words\">{c.title}</span>\n ) : (\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(c.id)}>\n {c.title}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {!deleted && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n <span>{c.language}</span>\n <RelativeTime at={deleted && c.deleted_at ? c.deleted_at : c.updated_at} />\n {s === \"scheduled\" && c.publish_at && <span>{t(\"pollManager.goesLive\", { date: formatDate(m, c.publish_at) })}</span>}\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={del.pending} onClick={() => void del.run(() => client.contents.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <>\n {href && (\n <a className={buttonVariants({ size: \"sm\", variant: \"ghost\" })} href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n <Button size=\"sm\" variant=\"outline\" onClick={() => loc.go(c.id)}>\n {t(\"comments.edit\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void remove(c)}>\n {t(\"comments.delete\")}\n </Button>\n </>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pager page={page} hasNext={list.hasNext} disabled={list.loading} onPage={(n) => loc.go(\"\", { ...filters, page: n > 1 ? n : undefined })} />\n {dialog}\n </div>\n );\n}\n\ninterface Form {\n title: string;\n slug: string;\n description: string;\n publishAt: string;\n body: string;\n}\n\nconst empty: Form = { title: \"\", slug: \"\", description: \"\", publishAt: \"\", body: \"\" };\n\nexport interface ContentEditorProps {\n /** The item edited; null creates one of kind. */\n id: string | null;\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /** The body editor; without it, a plain HTML field. */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n labels?: Labels;\n /** A new item's owner group: a channel its editors write for. */\n ownerGroup?: string;\n /** After create: show the new item (the editor goes on editing it). */\n onCreated?: (content: Content) => void;\n onDeleted?: (content: Content) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n}\n\n/**\n * One content item for its creators and staff: title, slug, description,\n * schedule, cover and body per language, publish, unpublish and delete;\n * then listing and attributes, access rule and price, versions and groups.\n */\nexport function ContentEditor({ id, client: own, kind, languages: offered, renderEditor, contentHref, labels, ownerGroup, onCreated, onDeleted, onError }: ContentEditorProps) {\n const client = useContentKitClient(own);\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const [language, setLanguage] = useState(languages[0]?.value ?? \"en\");\n const ed = useContent(id, kind, language, { client });\n const report = useErrorReporter(onError);\n // The body is kept as stored (image references); the editor shows it with URLs.\n const [form, setForm] = useState<Form | null>(id ? null : empty);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const { confirm, dialog } = useConfirm();\n const item = ed.content;\n useEffect(() => {\n if (!item) return;\n const stored = item.text?.[language];\n setForm((f) => f ?? { title: stored?.title ?? \"\", slug: stored?.slug ?? \"\", description: stored?.description ?? \"\", body: stored?.body ?? \"\",\n publishAt: item.publish_at && !item.live ? localInput(new Date(item.publish_at)) : \"\" });\n }, [item, language]);\n const { imageSrc } = ed;\n const shown = useMemo(\n () => (form?.body ?? \"\").replace(imageRefs(), (ref, name: string) => {\n const src = imageSrc(name);\n return src && src !== name ? src.replaceAll(\"&\", \"&\") : ref;\n }),\n [form?.body, imageSrc],\n );\n if (id && !form) {\n if (ed.error) return <LoadFailed error={ed.error} message={t(\"contentManager.loadFailed\")} retry={ed.reload} />;\n return (\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-48 w-full\" />\n </div>\n );\n }\n const f = form!;\n const set = (patch: Partial<Form>) => setForm((cur) => ({ ...cur!, ...patch }));\n const fail = (e: unknown) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"content.save\");\n };\n const text = () => ({ title: f.title.trim(), slug: f.slug.trim() || undefined, description: f.description.trim(), body: f.body });\n const save = async () => {\n setError(null);\n setNotice(null);\n if (!f.title.trim()) return setError(t(\"contentManager.titleRequired\"));\n try {\n if (!item) {\n const created = await ed.create({ kind, text: { [language]: text() }, owner_group_id: ownerGroup });\n onCreated?.(created);\n return;\n }\n const saved = await ed.putText(text());\n setNotice(t(\"pollEditor.saved\"));\n setForm((cur) => ({ ...cur!, slug: saved.text?.[language]?.slug ?? cur!.slug }));\n } catch (e) {\n fail(e);\n }\n };\n const publish = async (live: boolean) => {\n setError(null);\n setNotice(null);\n try {\n if (live) await ed.publish(f.publishAt ? new Date(f.publishAt).toISOString() : undefined);\n else await ed.unpublish();\n setNotice(t(live ? \"contentManager.publishedNotice\" : \"contentManager.unpublishedNotice\"));\n } catch (e) {\n fail(e);\n }\n };\n const remove = async () => {\n if (!item || !(await confirm({ title: t(\"contentManager.deleteTitle\", { title: item.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") }))) return;\n try {\n await ed.remove();\n onDeleted?.(item);\n } catch (e) {\n fail(e);\n }\n };\n const s = item ? contentStatus(item) : \"draft\";\n const href = item && s === \"published\" ? contentHref?.(item) : undefined;\n const editor: BodyEditorProps = {\n value: shown,\n onChange: (html) => set({ body: ed.storedBody(html) }),\n uploadImage: item ? (file) => ed.uploadImage(file) : undefined,\n disabled: ed.saving,\n placeholder: t(\"contentManager.bodyPlaceholder\"),\n \"aria-label\": t(\"contentManager.body\"),\n };\n return (\n <>\n <form\n className=\"grid gap-4\"\n data-ckui=\"content-form\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(item ? \"contentManager.editTitle\" : \"contentManager.newTitle\")}</h2>\n {item && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n {href && (\n <a className=\"text-xs text-muted-foreground hover:underline\" href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n </div>\n {item?.moderation && (\n <Alert variant={item.moderation === \"rejected\" ? \"destructive\" : \"default\"}>\n <AlertDescription>\n {item.moderation_reason\n ? t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedBecause\" : \"heldBecause\"}`, { reason: item.moderation_reason })\n : t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedNote\" : \"heldNote\"}`)}\n </AlertDescription>\n </Alert>\n )}\n {languages.length > 1 && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"contentManager.language\")}</Label>\n <Select\n value={language}\n onValueChange={(v) => {\n setLanguage(String(v ?? \"\"));\n setForm(item ? null : empty);\n }}\n >\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => languages.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-title`}>{t(\"contentManager.itemTitle\")}</Label>\n <Input id={`${ids}-title`} value={f.title} required maxLength={300} onChange={(e) => set({ title: e.target.value })} />\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-slug`}>{t(\"contentManager.slug\")}</Label>\n <Input id={`${ids}-slug`} value={f.slug} placeholder={t(\"contentManager.slugAuto\")} onChange={(e) => set({ slug: e.target.value })} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-publish`}>{t(\"contentManager.publishAt\")}</Label>\n <Input id={`${ids}-publish`} type=\"datetime-local\" value={f.publishAt} onChange={(e) => set({ publishAt: e.target.value })} />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-description`}>{t(\"contentManager.description\")}</Label>\n <Textarea id={`${ids}-description`} rows={2} value={f.description} onChange={(e) => set({ description: e.target.value })} />\n </div>\n <Cover item={item} src={ed.coverSrc} saving={ed.saving} onFile={(file) => void ed.setCover(file).catch(fail)} />\n <div className=\"grid gap-1.5\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.body\")}</span>\n {renderEditor ? renderEditor(editor) : <Textarea aria-label={editor[\"aria-label\"]} rows={12} value={f.body} placeholder={editor.placeholder} onChange={(e) => set({ body: e.target.value })} />}\n {!item && <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>}\n </div>\n {error && <ErrorLine>{typeof error === \"string\" ? error : contentError(m, error)}</ErrorLine>}\n {notice && !error && (\n <p role=\"status\" className=\"text-sm text-muted-foreground\">\n {notice}\n </p>\n )}\n <div className=\"flex flex-wrap gap-2 border-t border-border pt-4\">\n <Button type=\"submit\" disabled={ed.saving}>\n {ed.saving ? t(\"common.saving\") : t(item ? \"common.save\" : \"contentManager.createDraft\")}\n </Button>\n {item && !item.live && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(true)}>\n {t(\"contentManager.publish\")}\n </Button>\n )}\n {item?.state === \"published\" && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(false)}>\n {t(\"contentManager.unpublish\")}\n </Button>\n )}\n {item && (\n <Button type=\"button\" variant=\"destructive\" className=\"ms-auto\" disabled={ed.saving} onClick={() => void remove()}>\n {t(\"contentManager.delete\")}\n </Button>\n )}\n </div>\n {dialog}\n </form>\n {item && <ContentSections content={item} client={client} onError={onError} labels={labels} />}\n </>\n );\n}\n\nfunction Cover({ item, src, saving, onFile }: { item: Content | null; src?: string; saving: boolean; onFile: (file: File | null) => void }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"content-cover\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.cover\")}</span>\n {item ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {src ? <img src={src} alt=\"\" className=\"h-20 w-36 rounded-md border border-border object-cover\" /> : <span className=\"text-muted-foreground\">{t(\"contentManager.noCover\")}</span>}\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={saving} onClick={() => file.current?.click()}>\n {t(src ? \"pollEditor.replaceImage\" : \"contentManager.addCover\")}\n </Button>\n {src && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={() => onFile(null)}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n <input\n ref={file}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(\"contentManager.cover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) onFile(f);\n }}\n />\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,141 +1,142 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { labelOf as
|
|
1
|
+
import { useLanguageName as e, useMessages as t } from "../../i18n/context.js";
|
|
2
|
+
import { useGroups as n, useKinds as r } from "../../react/groups.js";
|
|
3
|
+
import { Button as i } from "../../ui/button.js";
|
|
4
|
+
import { Select as a, SelectContent as o, SelectItem as s, SelectTrigger as c, SelectValue as l } from "../../ui/select.js";
|
|
5
|
+
import { AdminRoot as u, LoadFailed as d, NoAccess as f, Rows as p } from "./shell.js";
|
|
6
|
+
import { useAdminLocation as m, useFlowClient as h, useStaffAccess as g } from "../../react/admin.js";
|
|
7
|
+
import { Tabs as _, TabsList as v, TabsTrigger as y } from "../../ui/tabs.js";
|
|
8
|
+
import { LETTERS as b } from "../../client/group-fields.js";
|
|
9
|
+
import { labelOf as x } from "../groups/parts.js";
|
|
10
10
|
import { GroupEditor as S } from "../groups/editor.js";
|
|
11
11
|
import { GroupMembers as C } from "../groups/memberships.js";
|
|
12
12
|
import { useMemo as w } from "react";
|
|
13
13
|
import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
14
14
|
//#region src/components/admin/group-manager.tsx
|
|
15
15
|
function O(e = {}) {
|
|
16
|
-
return
|
|
16
|
+
return g(["groups"], e);
|
|
17
17
|
}
|
|
18
18
|
var k = [
|
|
19
19
|
"name",
|
|
20
20
|
"count",
|
|
21
21
|
"newest"
|
|
22
22
|
];
|
|
23
|
-
function A({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages:
|
|
24
|
-
let
|
|
23
|
+
function A({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages: s, ...c }) {
|
|
24
|
+
let l = h({
|
|
25
25
|
client: e,
|
|
26
26
|
baseUrl: t,
|
|
27
27
|
auth: n
|
|
28
28
|
});
|
|
29
|
-
return /* @__PURE__ */ E(
|
|
29
|
+
return /* @__PURE__ */ E(u, {
|
|
30
30
|
flow: "group-manager",
|
|
31
31
|
appearance: i,
|
|
32
32
|
className: a,
|
|
33
33
|
language: o,
|
|
34
|
-
messages:
|
|
34
|
+
messages: s,
|
|
35
35
|
guard: r,
|
|
36
36
|
children: /* @__PURE__ */ E(j, {
|
|
37
|
-
...
|
|
38
|
-
client:
|
|
37
|
+
...c,
|
|
38
|
+
client: l
|
|
39
39
|
})
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
function j({ basePath:
|
|
43
|
-
let { t:
|
|
44
|
-
if (
|
|
45
|
-
if (!
|
|
46
|
-
if (!
|
|
47
|
-
error:
|
|
48
|
-
message:
|
|
49
|
-
retry:
|
|
42
|
+
function j({ basePath: e, navigation: n, ...i }) {
|
|
43
|
+
let { t: a } = t(), o = m(e, n), s = O({ client: i.client }), c = r({ client: i.client }), l = c.kinds, u = w(() => i.groupKinds ?? Object.entries(l?.group_kinds ?? {}).filter(([, e]) => e.owner !== "user").map(([e]) => e).sort(), [i.groupKinds, l]);
|
|
44
|
+
if (s.loading || c.loading) return /* @__PURE__ */ E(p, {});
|
|
45
|
+
if (!s.allowed.groups) return /* @__PURE__ */ E(f, { children: a("groupManager.noAccess") });
|
|
46
|
+
if (!l) return /* @__PURE__ */ E(d, {
|
|
47
|
+
error: c.error,
|
|
48
|
+
message: a("groupManager.kindsFailed"),
|
|
49
|
+
retry: c.reload
|
|
50
50
|
});
|
|
51
|
-
if (!
|
|
51
|
+
if (!u.length) return /* @__PURE__ */ E("p", {
|
|
52
52
|
className: "text-muted-foreground",
|
|
53
|
-
children:
|
|
53
|
+
children: a("groupManager.noKinds")
|
|
54
54
|
});
|
|
55
|
-
let [
|
|
56
|
-
...
|
|
57
|
-
loc:
|
|
58
|
-
kind:
|
|
59
|
-
kinds:
|
|
55
|
+
let [h, g] = o.view.split("/"), b = u.includes(h ?? "") ? h : u[0], S = {
|
|
56
|
+
...i,
|
|
57
|
+
loc: o,
|
|
58
|
+
kind: b,
|
|
59
|
+
kinds: l
|
|
60
60
|
};
|
|
61
|
-
return /* @__PURE__ */ D(T, { children: [
|
|
62
|
-
value:
|
|
63
|
-
onValueChange: (e) =>
|
|
64
|
-
children: /* @__PURE__ */ E(
|
|
65
|
-
className: "flex-wrap",
|
|
66
|
-
children:
|
|
61
|
+
return /* @__PURE__ */ D(T, { children: [u.length > 1 && /* @__PURE__ */ E(_, {
|
|
62
|
+
value: b,
|
|
63
|
+
onValueChange: (e) => o.go(String(e)),
|
|
64
|
+
children: /* @__PURE__ */ E(v, {
|
|
65
|
+
className: "flex-wrap group-data-horizontal/tabs:h-auto",
|
|
66
|
+
children: u.map((e) => /* @__PURE__ */ E(y, {
|
|
67
67
|
value: e,
|
|
68
|
-
|
|
68
|
+
className: "h-7.5",
|
|
69
|
+
children: x(i.labels, e)
|
|
69
70
|
}, e))
|
|
70
71
|
})
|
|
71
|
-
}),
|
|
72
|
+
}), g ? g === "new" ? /* @__PURE__ */ E(P, { ...S }, b) : /* @__PURE__ */ E(F, {
|
|
72
73
|
...S,
|
|
73
|
-
id:
|
|
74
|
-
},
|
|
74
|
+
id: g
|
|
75
|
+
}, g) : /* @__PURE__ */ E(N, { ...S }, b)] });
|
|
75
76
|
}
|
|
76
|
-
function M({ loc:
|
|
77
|
-
let { t:
|
|
78
|
-
return /* @__PURE__ */ E(
|
|
77
|
+
function M({ loc: e, kind: n }) {
|
|
78
|
+
let { t: r } = t();
|
|
79
|
+
return /* @__PURE__ */ E(i, {
|
|
79
80
|
variant: "ghost",
|
|
80
81
|
size: "sm",
|
|
81
82
|
className: "justify-self-start",
|
|
82
|
-
onClick: () =>
|
|
83
|
-
children:
|
|
83
|
+
onClick: () => e.go(n),
|
|
84
|
+
children: r("groupManager.back")
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
|
-
function N({ loc:
|
|
87
|
-
let
|
|
87
|
+
function N({ loc: r, kind: u, kinds: f, client: m, contentKind: h, labels: g, pageSize: _ = 50 }) {
|
|
88
|
+
let v = t(), { t: y } = v, S = e(), C = f.languages, w = r.query.get("letter") ?? "", T = b.includes(w) ? w : void 0, O = r.query.get("sort") ?? "", A = k.includes(O) ? O : "name", j = r.query.get("language") ?? "", M = C.includes(j) ? j : C[0], N = {
|
|
88
89
|
letter: T,
|
|
89
90
|
sort: A === "name" ? void 0 : A,
|
|
90
91
|
language: M === C[0] ? void 0 : M
|
|
91
|
-
}, P = (e) =>
|
|
92
|
+
}, P = (e) => r.go(u, {
|
|
92
93
|
...N,
|
|
93
94
|
...e
|
|
94
|
-
}), F =
|
|
95
|
-
kind:
|
|
95
|
+
}), F = n({
|
|
96
|
+
kind: u,
|
|
96
97
|
letter: T,
|
|
97
98
|
sort: A,
|
|
98
99
|
language: M,
|
|
99
|
-
contentKind:
|
|
100
|
-
pageSize:
|
|
101
|
-
}, { client:
|
|
100
|
+
contentKind: h,
|
|
101
|
+
pageSize: _
|
|
102
|
+
}, { client: m }), I = x(g, u);
|
|
102
103
|
return /* @__PURE__ */ D("div", {
|
|
103
104
|
className: "grid gap-4",
|
|
104
105
|
"data-ckui": "group-list",
|
|
105
|
-
"data-kind":
|
|
106
|
+
"data-kind": u,
|
|
106
107
|
children: [
|
|
107
108
|
/* @__PURE__ */ D("div", {
|
|
108
109
|
className: "flex flex-wrap items-center justify-between gap-2",
|
|
109
110
|
children: [/* @__PURE__ */ E("h2", {
|
|
110
111
|
className: "text-base font-semibold",
|
|
111
112
|
children: I
|
|
112
|
-
}), /* @__PURE__ */ E(
|
|
113
|
+
}), /* @__PURE__ */ E(i, {
|
|
113
114
|
size: "sm",
|
|
114
|
-
onClick: () =>
|
|
115
|
+
onClick: () => r.go(`${u}/new`),
|
|
115
116
|
children: y("groupManager.new", { kind: I })
|
|
116
117
|
})]
|
|
117
118
|
}),
|
|
118
119
|
/* @__PURE__ */ D("div", {
|
|
119
120
|
className: "flex flex-wrap items-center gap-2",
|
|
120
|
-
children: [/* @__PURE__ */ D(
|
|
121
|
+
children: [/* @__PURE__ */ D(a, {
|
|
121
122
|
value: A,
|
|
122
123
|
onValueChange: (e) => P({ sort: e === "name" ? void 0 : String(e) }),
|
|
123
|
-
children: [/* @__PURE__ */ E(
|
|
124
|
+
children: [/* @__PURE__ */ E(c, {
|
|
124
125
|
size: "sm",
|
|
125
126
|
"aria-label": y("groupManager.sort"),
|
|
126
|
-
children: /* @__PURE__ */ E(
|
|
127
|
-
}), /* @__PURE__ */ E(
|
|
127
|
+
children: /* @__PURE__ */ E(l, { children: (e) => y(`groupManager.sort_${e}`) })
|
|
128
|
+
}), /* @__PURE__ */ E(o, { children: k.map((e) => /* @__PURE__ */ E(s, {
|
|
128
129
|
value: e,
|
|
129
130
|
children: y(`groupManager.sort_${e}`)
|
|
130
131
|
}, e)) })]
|
|
131
|
-
}), C.length > 1 && /* @__PURE__ */ D(
|
|
132
|
+
}), C.length > 1 && /* @__PURE__ */ D(a, {
|
|
132
133
|
value: M,
|
|
133
134
|
onValueChange: (e) => P({ language: e === C[0] ? void 0 : String(e) }),
|
|
134
|
-
children: [/* @__PURE__ */ E(
|
|
135
|
+
children: [/* @__PURE__ */ E(c, {
|
|
135
136
|
size: "sm",
|
|
136
137
|
"aria-label": y("groupManager.language"),
|
|
137
|
-
children: /* @__PURE__ */ E(
|
|
138
|
-
}), /* @__PURE__ */ E(
|
|
138
|
+
children: /* @__PURE__ */ E(l, { children: (e) => S(e) })
|
|
139
|
+
}), /* @__PURE__ */ E(o, { children: C.map((e) => /* @__PURE__ */ E(s, {
|
|
139
140
|
value: e,
|
|
140
141
|
children: S(e)
|
|
141
142
|
}, e)) })]
|
|
@@ -145,7 +146,7 @@ function N({ loc: s, kind: l, kinds: d, client: f, contentKind: p, labels: m, pa
|
|
|
145
146
|
"aria-label": y("groups.byLetter"),
|
|
146
147
|
className: "flex flex-wrap gap-1",
|
|
147
148
|
"data-ckui": "letters",
|
|
148
|
-
children: [void 0, ...
|
|
149
|
+
children: [void 0, ...b].map((e) => /* @__PURE__ */ E(i, {
|
|
149
150
|
size: "xs",
|
|
150
151
|
variant: T === e ? "default" : "ghost",
|
|
151
152
|
"aria-pressed": T === e,
|
|
@@ -155,7 +156,7 @@ function N({ loc: s, kind: l, kinds: d, client: f, contentKind: p, labels: m, pa
|
|
|
155
156
|
children: e === void 0 ? y("groups.all") : e.toUpperCase()
|
|
156
157
|
}, e ?? "all"))
|
|
157
158
|
}),
|
|
158
|
-
F.loading && !F.items.length ? /* @__PURE__ */ E(
|
|
159
|
+
F.loading && !F.items.length ? /* @__PURE__ */ E(p, {}) : F.error && !F.items.length ? /* @__PURE__ */ E(d, {
|
|
159
160
|
error: F.error,
|
|
160
161
|
message: y("groupManager.loadFailed"),
|
|
161
162
|
retry: F.reload
|
|
@@ -171,7 +172,7 @@ function N({ loc: s, kind: l, kinds: d, client: f, contentKind: p, labels: m, pa
|
|
|
171
172
|
children: [/* @__PURE__ */ E("button", {
|
|
172
173
|
type: "button",
|
|
173
174
|
className: "text-left font-medium break-words hover:underline",
|
|
174
|
-
onClick: () =>
|
|
175
|
+
onClick: () => r.go(`${u}/${e.id}`),
|
|
175
176
|
children: e.name || e.slug
|
|
176
177
|
}), /* @__PURE__ */ E("span", {
|
|
177
178
|
className: "text-xs text-muted-foreground",
|
|
@@ -180,14 +181,14 @@ function N({ loc: s, kind: l, kinds: d, client: f, contentKind: p, labels: m, pa
|
|
|
180
181
|
}), /* @__PURE__ */ E("span", {
|
|
181
182
|
className: "text-sm text-muted-foreground tabular-nums",
|
|
182
183
|
title: y("groupManager.count"),
|
|
183
|
-
children: new Intl.NumberFormat(
|
|
184
|
+
children: new Intl.NumberFormat(v.language).format(e.count)
|
|
184
185
|
})]
|
|
185
186
|
}, e.id))
|
|
186
187
|
}) : /* @__PURE__ */ E("p", {
|
|
187
188
|
className: "text-muted-foreground",
|
|
188
189
|
children: y("groups.empty")
|
|
189
190
|
}),
|
|
190
|
-
F.hasMore && /* @__PURE__ */ E(
|
|
191
|
+
F.hasMore && /* @__PURE__ */ E(i, {
|
|
191
192
|
variant: "outline",
|
|
192
193
|
size: "sm",
|
|
193
194
|
className: "justify-self-center",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-manager.js","names":[],"sources":["../../../src/components/admin/group-manager.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { LETTERS } from \"../../client/group-fields.js\";\nimport type { ContentModel, Membership } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useGroups, useKinds, type GroupSort } from \"../../react/groups.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Tabs, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { GroupEditor } from \"../groups/editor.js\";\nimport { GroupMembers } from \"../groups/memberships.js\";\nimport { labelOf, useLanguageName, type Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\nexport interface GroupManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The group kinds managed, in tab order; default the content model's site-owned kinds. */\n groupKinds?: readonly string[];\n /** Display names of kinds, attributes, roles and values by identifier. */\n labels?: Labels;\n /** Counts of this content kind; default every kind. */\n contentKind?: string;\n /** One member, e.g. a link with its title; default its content id. */\n renderContent?: (member: Membership) => ReactNode;\n pageSize?: number;\n}\n\n/** Whether the caller may manage groups (root:groups:write). */\nexport function useGroupManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"groups\"> {\n return useStaffAccess([\"groups\"] as const, o);\n}\n\nconst SORTS: readonly GroupSort[] = [\"name\", \"count\", \"newest\"];\n\n/**\n * Staff: groups per kind. A kind's groups by letter, order and language; a\n * new group; a group's editor (GroupEditor) and its members (GroupMembers).\n * Kinds are at basePath/{kind}, a group at basePath/{kind}/{id}, filters in the query.\n */\nexport function GroupManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: GroupManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"group-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Manager {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<GroupManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\ntype Ctx = Inner & { loc: AdminLocation; kind: string; kinds: ContentModel };\n\nfunction Manager({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useGroupManagerAccess({ client: p.client });\n const model = useKinds({ client: p.client });\n const kinds = model.kinds;\n const list = useMemo(\n () =>\n p.groupKinds ??\n Object.entries(kinds?.group_kinds ?? {})\n .filter(([, k]) => k.owner !== \"user\")\n .map(([name]) => name)\n .sort(),\n [p.groupKinds, kinds],\n );\n if (access.loading || model.loading) return <Rows />;\n if (!access.allowed.groups) return <NoAccess>{t(\"groupManager.noAccess\")}</NoAccess>;\n if (!kinds) return <LoadFailed error={model.error} message={t(\"groupManager.kindsFailed\")} retry={model.reload} />;\n if (!list.length) return <p className=\"text-muted-foreground\">{t(\"groupManager.noKinds\")}</p>;\n const [k, sub] = loc.view.split(\"/\");\n const kind = list.includes(k ?? \"\") ? k! : list[0]!;\n const ctx: Ctx = { ...p, loc, kind, kinds };\n return (\n <>\n {list.length > 1 && (\n <Tabs value={kind} onValueChange={(v) => loc.go(String(v))}>\n <TabsList className=\"flex-wrap\">\n {list.map((x) => (\n <TabsTrigger key={x} value={x}>\n {labelOf(p.labels, x)}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )}\n {!sub ? <List key={kind} {...ctx} /> : sub === \"new\" ? <Create key={kind} {...ctx} /> : <Edit key={sub} {...ctx} id={sub} />}\n </>\n );\n}\n\nfunction Back({ loc, kind }: { loc: AdminLocation; kind: string }) {\n const { t } = useMessages();\n return (\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind)}>\n {t(\"groupManager.back\")}\n </Button>\n );\n}\n\nfunction List({ loc, kind, kinds, client, contentKind, labels, pageSize = 50 }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const languages = kinds.languages;\n const letterParam = loc.query.get(\"letter\") ?? \"\";\n const letter = LETTERS.includes(letterParam) ? letterParam : undefined;\n const sortParam = loc.query.get(\"sort\") ?? \"\";\n const sort = (SORTS as readonly string[]).includes(sortParam) ? (sortParam as GroupSort) : \"name\";\n const languageParam = loc.query.get(\"language\") ?? \"\";\n const language = languages.includes(languageParam) ? languageParam : languages[0];\n const filters: { letter?: string; sort?: GroupSort; language?: string } = { letter, sort: sort === \"name\" ? undefined : sort, language: language === languages[0] ? undefined : language };\n const filter = (next: Partial<typeof filters>) => loc.go(kind, { ...filters, ...next });\n const groups = useGroups({ kind, letter, sort, language, contentKind, pageSize }, { client });\n const label = labelOf(labels, kind);\n return (\n <div className=\"grid gap-4\" data-ckui=\"group-list\" data-kind={kind}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{label}</h2>\n <Button size=\"sm\" onClick={() => loc.go(`${kind}/new`)}>\n {t(\"groupManager.new\", { kind: label })}\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Select value={sort} onValueChange={(v) => filter({ sort: v === \"name\" ? undefined : (String(v) as GroupSort) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.sort\")}>\n <SelectValue>{(v: string) => t(`groupManager.sort_${v as GroupSort}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {SORTS.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`groupManager.sort_${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languages.length > 1 && (\n <Select value={language} onValueChange={(v) => filter({ language: v === languages[0] ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.language\")}>\n <SelectValue>{(v: string) => languageName(v)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <nav aria-label={t(\"groups.byLetter\")} className=\"flex flex-wrap gap-1\" data-ckui=\"letters\">\n {[undefined, ...LETTERS].map((l) => (\n <Button\n key={l ?? \"all\"}\n size=\"xs\"\n variant={letter === l ? \"default\" : \"ghost\"}\n aria-pressed={letter === l}\n aria-label={l === undefined ? undefined : l === \"#\" ? t(\"groups.otherLetters\") : l.toUpperCase()}\n className=\"min-w-7\"\n onClick={() => filter({ letter: l })}\n >\n {l === undefined ? t(\"groups.all\") : l.toUpperCase()}\n </Button>\n ))}\n </nav>\n {groups.loading && !groups.items.length ? (\n <Rows />\n ) : groups.error && !groups.items.length ? (\n <LoadFailed error={groups.error} message={t(\"groupManager.loadFailed\")} retry={groups.reload} />\n ) : !groups.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"groups.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={groups.loading}>\n {groups.items.map((g) => (\n <li key={g.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"group-row\" data-slug={g.slug}>\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(`${kind}/${g.id}`)}>\n {g.name || g.slug}\n </button>\n <span className=\"text-xs text-muted-foreground\">{g.slug}</span>\n </div>\n <span className=\"text-sm text-muted-foreground tabular-nums\" title={t(\"groupManager.count\")}>\n {new Intl.NumberFormat(m.language).format(g.count)}\n </span>\n </li>\n ))}\n </ul>\n )}\n {groups.hasMore && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={groups.loadingMore} onClick={() => void groups.loadMore()}>\n {groups.loadingMore ? t(\"groups.loading\") : t(\"groups.loadMore\")}\n </Button>\n )}\n </div>\n );\n}\n\nfunction Create({ loc, kind, client, labels, onError }: Ctx) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n id={null}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onCreated={(g) => loc.go(`${kind}/${g.id}`, undefined, { replace: true })}\n />\n </div>\n );\n}\n\nfunction Edit({ loc, kind, client, labels, onError, renderContent, id }: Ctx & { id: string }) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n id={id}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onMerged={(r) => loc.go(`${kind}/${r.into}`, undefined, { replace: true })}\n >\n {(group) => (\n <GroupMembers\n group={group}\n labels={labels}\n client={client}\n onError={onError}\n renderContent={renderContent}\n onUseCover={(content) => client.groups.update(group.id, { cover: { content_id: content } })}\n />\n )}\n </GroupEditor>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"group-manager.js","names":[],"sources":["../../../src/components/admin/group-manager.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { LETTERS } from \"../../client/group-fields.js\";\nimport type { ContentModel, Membership } from \"../../client/generated/wire.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useGroups, useKinds, type GroupSort } from \"../../react/groups.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Tabs, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { GroupEditor } from \"../groups/editor.js\";\nimport { GroupMembers } from \"../groups/memberships.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\nexport interface GroupManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The group kinds managed, in tab order; default the content model's site-owned kinds. */\n groupKinds?: readonly string[];\n /** Display names of kinds, attributes, roles and values by identifier. */\n labels?: Labels;\n /** Counts of this content kind; default every kind. */\n contentKind?: string;\n /** One member, e.g. a link with its title; default its content id. */\n renderContent?: (member: Membership) => ReactNode;\n pageSize?: number;\n}\n\n/** Whether the caller may manage groups (root:groups:write). */\nexport function useGroupManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"groups\"> {\n return useStaffAccess([\"groups\"] as const, o);\n}\n\nconst SORTS: readonly GroupSort[] = [\"name\", \"count\", \"newest\"];\n\n/**\n * Staff: groups per kind. A kind's groups by letter, order and language; a\n * new group; a group's editor (GroupEditor) and its members (GroupMembers).\n * Kinds are at basePath/{kind}, a group at basePath/{kind}/{id}, filters in the query.\n */\nexport function GroupManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: GroupManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"group-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Manager {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<GroupManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\ntype Ctx = Inner & { loc: AdminLocation; kind: string; kinds: ContentModel };\n\nfunction Manager({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useGroupManagerAccess({ client: p.client });\n const model = useKinds({ client: p.client });\n const kinds = model.kinds;\n const list = useMemo(\n () =>\n p.groupKinds ??\n Object.entries(kinds?.group_kinds ?? {})\n .filter(([, k]) => k.owner !== \"user\")\n .map(([name]) => name)\n .sort(),\n [p.groupKinds, kinds],\n );\n if (access.loading || model.loading) return <Rows />;\n if (!access.allowed.groups) return <NoAccess>{t(\"groupManager.noAccess\")}</NoAccess>;\n if (!kinds) return <LoadFailed error={model.error} message={t(\"groupManager.kindsFailed\")} retry={model.reload} />;\n if (!list.length) return <p className=\"text-muted-foreground\">{t(\"groupManager.noKinds\")}</p>;\n const [k, sub] = loc.view.split(\"/\");\n const kind = list.includes(k ?? \"\") ? k! : list[0]!;\n const ctx: Ctx = { ...p, loc, kind, kinds };\n return (\n <>\n {list.length > 1 && (\n <Tabs value={kind} onValueChange={(v) => loc.go(String(v))}>\n <TabsList className=\"flex-wrap group-data-horizontal/tabs:h-auto\">\n {list.map((x) => (\n <TabsTrigger key={x} value={x} className=\"h-7.5\">\n {labelOf(p.labels, x)}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )}\n {!sub ? <List key={kind} {...ctx} /> : sub === \"new\" ? <Create key={kind} {...ctx} /> : <Edit key={sub} {...ctx} id={sub} />}\n </>\n );\n}\n\nfunction Back({ loc, kind }: { loc: AdminLocation; kind: string }) {\n const { t } = useMessages();\n return (\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind)}>\n {t(\"groupManager.back\")}\n </Button>\n );\n}\n\nfunction List({ loc, kind, kinds, client, contentKind, labels, pageSize = 50 }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const languages = kinds.languages;\n const letterParam = loc.query.get(\"letter\") ?? \"\";\n const letter = LETTERS.includes(letterParam) ? letterParam : undefined;\n const sortParam = loc.query.get(\"sort\") ?? \"\";\n const sort = (SORTS as readonly string[]).includes(sortParam) ? (sortParam as GroupSort) : \"name\";\n const languageParam = loc.query.get(\"language\") ?? \"\";\n const language = languages.includes(languageParam) ? languageParam : languages[0];\n const filters: { letter?: string; sort?: GroupSort; language?: string } = { letter, sort: sort === \"name\" ? undefined : sort, language: language === languages[0] ? undefined : language };\n const filter = (next: Partial<typeof filters>) => loc.go(kind, { ...filters, ...next });\n const groups = useGroups({ kind, letter, sort, language, contentKind, pageSize }, { client });\n const label = labelOf(labels, kind);\n return (\n <div className=\"grid gap-4\" data-ckui=\"group-list\" data-kind={kind}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{label}</h2>\n <Button size=\"sm\" onClick={() => loc.go(`${kind}/new`)}>\n {t(\"groupManager.new\", { kind: label })}\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Select value={sort} onValueChange={(v) => filter({ sort: v === \"name\" ? undefined : (String(v) as GroupSort) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.sort\")}>\n <SelectValue>{(v: string) => t(`groupManager.sort_${v as GroupSort}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {SORTS.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`groupManager.sort_${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languages.length > 1 && (\n <Select value={language} onValueChange={(v) => filter({ language: v === languages[0] ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.language\")}>\n <SelectValue>{(v: string) => languageName(v)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <nav aria-label={t(\"groups.byLetter\")} className=\"flex flex-wrap gap-1\" data-ckui=\"letters\">\n {[undefined, ...LETTERS].map((l) => (\n <Button\n key={l ?? \"all\"}\n size=\"xs\"\n variant={letter === l ? \"default\" : \"ghost\"}\n aria-pressed={letter === l}\n aria-label={l === undefined ? undefined : l === \"#\" ? t(\"groups.otherLetters\") : l.toUpperCase()}\n className=\"min-w-7\"\n onClick={() => filter({ letter: l })}\n >\n {l === undefined ? t(\"groups.all\") : l.toUpperCase()}\n </Button>\n ))}\n </nav>\n {groups.loading && !groups.items.length ? (\n <Rows />\n ) : groups.error && !groups.items.length ? (\n <LoadFailed error={groups.error} message={t(\"groupManager.loadFailed\")} retry={groups.reload} />\n ) : !groups.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"groups.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={groups.loading}>\n {groups.items.map((g) => (\n <li key={g.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"group-row\" data-slug={g.slug}>\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(`${kind}/${g.id}`)}>\n {g.name || g.slug}\n </button>\n <span className=\"text-xs text-muted-foreground\">{g.slug}</span>\n </div>\n <span className=\"text-sm text-muted-foreground tabular-nums\" title={t(\"groupManager.count\")}>\n {new Intl.NumberFormat(m.language).format(g.count)}\n </span>\n </li>\n ))}\n </ul>\n )}\n {groups.hasMore && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={groups.loadingMore} onClick={() => void groups.loadMore()}>\n {groups.loadingMore ? t(\"groups.loading\") : t(\"groups.loadMore\")}\n </Button>\n )}\n </div>\n );\n}\n\nfunction Create({ loc, kind, client, labels, onError }: Ctx) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n id={null}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onCreated={(g) => loc.go(`${kind}/${g.id}`, undefined, { replace: true })}\n />\n </div>\n );\n}\n\nfunction Edit({ loc, kind, client, labels, onError, renderContent, id }: Ctx & { id: string }) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n id={id}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onMerged={(r) => loc.go(`${kind}/${r.into}`, undefined, { replace: true })}\n >\n {(group) => (\n <GroupMembers\n group={group}\n labels={labels}\n client={client}\n onError={onError}\n renderContent={renderContent}\n onUseCover={(content) => client.groups.update(group.id, { cover: { content_id: content } })}\n />\n )}\n </GroupEditor>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,27 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContentKitClient } from '../../client/client.js';
|
|
2
|
+
import { RefBody, UploadRule } from '../../client/generated/wire.js';
|
|
3
|
+
import { ContentKitErrorHandler } from '../../react/context.js';
|
|
2
4
|
import { AdminFlowProps } from './shell.js';
|
|
3
5
|
type Choice = string | {
|
|
4
6
|
value: string;
|
|
5
7
|
label: string;
|
|
6
8
|
};
|
|
7
|
-
|
|
8
|
-
/** The video
|
|
9
|
-
item: RefBody;
|
|
10
|
-
/** The video's upload path. Default "source". */
|
|
9
|
+
interface VideoPaths {
|
|
10
|
+
/** The video's upload path. Default the kind's literal video upload. */
|
|
11
11
|
source?: string;
|
|
12
|
-
/** The subtitle uploads' path pattern; null hides them. Default
|
|
12
|
+
/** The subtitle uploads' path pattern; null hides them. Default the kind's subtitle pattern. */
|
|
13
13
|
subtitles?: string | null;
|
|
14
|
-
/** The poster's upload path (its frames come from the video); null hides it. Default
|
|
14
|
+
/** The poster's upload path (its frames come from the video); null hides it. Default the kind's upload framed from the video. */
|
|
15
15
|
poster?: string | null;
|
|
16
16
|
/** The poster's public preset. Default "poster". */
|
|
17
17
|
posterPreset?: string;
|
|
18
18
|
/** Subtitle languages offered: codes, or codes with labels. Without them the language is typed. */
|
|
19
19
|
languages?: readonly Choice[];
|
|
20
20
|
}
|
|
21
|
+
export interface MediaItemPanelProps extends AdminFlowProps, VideoPaths {
|
|
22
|
+
/** The video item. */
|
|
23
|
+
item: RefBody;
|
|
24
|
+
}
|
|
25
|
+
/** A video item's upload paths from its kind's rules: the literal video upload, the image upload framed from it, a subtitle pattern. */
|
|
26
|
+
export declare function videoPaths(rules: readonly UploadRule[]): {
|
|
27
|
+
source?: string;
|
|
28
|
+
poster?: string;
|
|
29
|
+
subtitles?: string;
|
|
30
|
+
};
|
|
21
31
|
/**
|
|
22
32
|
* One video item for its editors: its encode state and progress, the source
|
|
23
33
|
* (replace it), subtitle sidecars (language, label, forced), the poster from
|
|
24
34
|
* a frame or an image, and regenerate, which retries failed uploads.
|
|
25
35
|
*/
|
|
26
36
|
export declare function MediaItemPanel({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: MediaItemPanelProps): import('react').JSX.Element;
|
|
37
|
+
export interface VideoMediaEditorProps extends VideoPaths {
|
|
38
|
+
item: RefBody;
|
|
39
|
+
client?: ContentKitClient;
|
|
40
|
+
onError?: ContentKitErrorHandler;
|
|
41
|
+
}
|
|
42
|
+
/** MediaItemPanel's body, for a host's own editor (the content manager's version media). */
|
|
43
|
+
export declare function VideoMediaEditor({ item, client: own, source: givenSource, subtitles: givenSubtitles, poster: givenPoster, posterPreset, languages, onError }: VideoMediaEditorProps): import('react').JSX.Element;
|
|
27
44
|
export {};
|