@openrails/contentkit-ui 0.103.0 → 0.103.2

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.
Files changed (65) hide show
  1. package/README.md +6 -4
  2. package/dist/components/admin/content-manager.d.ts +1 -1
  3. package/dist/components/admin/content-manager.js +346 -304
  4. package/dist/components/admin/content-manager.js.map +1 -1
  5. package/dist/components/admin/page-review.js +106 -97
  6. package/dist/components/admin/page-review.js.map +1 -1
  7. package/dist/components/admin/reader-content-editor.d.ts +6 -0
  8. package/dist/components/admin/reader-content-editor.js +709 -0
  9. package/dist/components/admin/reader-content-editor.js.map +1 -0
  10. package/dist/components/admin/reader-manifest.js +120 -105
  11. package/dist/components/admin/reader-manifest.js.map +1 -1
  12. package/dist/components/content/editor-save.d.ts +32 -0
  13. package/dist/components/content/editor-save.js +51 -0
  14. package/dist/components/content/editor-save.js.map +1 -0
  15. package/dist/components/content/editors.d.ts +17 -2
  16. package/dist/components/content/editors.js +359 -328
  17. package/dist/components/content/editors.js.map +1 -1
  18. package/dist/components/content/offer-fields.d.ts +1 -1
  19. package/dist/components/content/offer-fields.js +86 -64
  20. package/dist/components/content/offer-fields.js.map +1 -1
  21. package/dist/components/content/release-date-picker.d.ts +4 -4
  22. package/dist/components/content/release-date-picker.js +106 -48
  23. package/dist/components/content/release-date-picker.js.map +1 -1
  24. package/dist/components/groups/memberships.d.ts +11 -1
  25. package/dist/components/groups/memberships.js +1 -1
  26. package/dist/components/groups/memberships.js.map +1 -1
  27. package/dist/components/media-folder-editor.d.ts +4 -0
  28. package/dist/components/media-folder-editor.js +177 -167
  29. package/dist/components/media-folder-editor.js.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/locales/admin/de.js +25 -0
  32. package/dist/locales/admin/de.js.map +1 -1
  33. package/dist/locales/admin/en.d.ts +25 -0
  34. package/dist/locales/admin/en.js +25 -0
  35. package/dist/locales/admin/en.js.map +1 -1
  36. package/dist/locales/admin/es.js +25 -0
  37. package/dist/locales/admin/es.js.map +1 -1
  38. package/dist/locales/admin/ja.js +25 -0
  39. package/dist/locales/admin/ja.js.map +1 -1
  40. package/dist/locales/admin/ko.js +25 -0
  41. package/dist/locales/admin/ko.js.map +1 -1
  42. package/dist/locales/admin/zh.js +25 -0
  43. package/dist/locales/admin/zh.js.map +1 -1
  44. package/dist/locales/discovery/de.js +1 -2
  45. package/dist/locales/discovery/de.js.map +1 -1
  46. package/dist/locales/discovery/en.d.ts +1 -2
  47. package/dist/locales/discovery/en.js +1 -2
  48. package/dist/locales/discovery/en.js.map +1 -1
  49. package/dist/locales/discovery/es.js +1 -2
  50. package/dist/locales/discovery/es.js.map +1 -1
  51. package/dist/locales/discovery/ja.js +1 -2
  52. package/dist/locales/discovery/ja.js.map +1 -1
  53. package/dist/locales/discovery/ko.js +1 -2
  54. package/dist/locales/discovery/ko.js.map +1 -1
  55. package/dist/locales/discovery/zh.js +1 -2
  56. package/dist/locales/discovery/zh.js.map +1 -1
  57. package/dist/locales/en.d.ts +26 -2
  58. package/dist/react/page-review.d.ts +1 -1
  59. package/dist/react/page-review.js +6 -5
  60. package/dist/react/page-review.js.map +1 -1
  61. package/dist/react/use-draft.d.ts +8 -0
  62. package/dist/react/use-draft.js +35 -0
  63. package/dist/react/use-draft.js.map +1 -0
  64. package/dist/styles.css +1 -1
  65. package/package.json +2 -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 { useKinds } from \"../../react/groups.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 management\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 /** Staff entity lookup, including internal classifications. */\n management?: boolean;\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, management }: 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 // A hidden version's cover is edited here; a visible version's in its own form.\n const hidden = !!useKinds({ client }).kinds?.kinds[kind]?.versions.hidden;\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(\"&\", \"&amp;\") : 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 {(hidden || !item) && <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 management={management} 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 { useKinds } from \"../../react/groups.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 { ReaderContentEditor } from \"./reader-content-editor.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 management\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 /** Staff entity lookup, including internal classifications. */\n management?: boolean;\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(p:ContentEditorProps){\n const {client,scope}=useContentScope(p.client);\n const [session,setSession]=useState({id:p.id,kind:p.kind,client,scope,key:0,allocated:null as string|null});\n let boundary=session;\n if(session.id!==p.id||session.kind!==p.kind||session.client!==client||session.scope!==scope){\n const continues=session.id==null&&p.id===session.allocated&&session.kind===p.kind&&session.client===client&&session.scope===scope;\n boundary={id:p.id,kind:p.kind,client,scope,key:continues?session.key:session.key+1,allocated:null};\n setSession(boundary);\n }\n const {kinds,error,reload}=useKinds({client});\n const {t}=useMessages();\n if(!kinds)return error?<LoadFailed error={error} message={t(\"contentManager.loadFailed\")} retry={reload}/>:<Rows/>;\n const props={...p,client,onCreated:(content:Content)=>{setSession((old)=>({...old,allocated:content.id}));p.onCreated?.(content);}};\n if(kinds.kinds[p.kind]?.presentation===\"reader\")return <ReaderContentEditor key={boundary.key} {...props} model={kinds}/>;\n return <StandardContentEditor key={boundary.key} {...props}/>;\n}\n\nfunction StandardContentEditor({ id, client: own, kind, languages: offered, renderEditor, contentHref, labels, ownerGroup, onCreated, onDeleted, onError, management }: 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 // A hidden version's cover is edited here; a visible version's in its own form.\n const hidden = !!useKinds({ client }).kinds?.kinds[kind]?.versions.hidden;\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(\"&\", \"&amp;\") : 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 {(hidden || !item) && <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 management={management} 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":";;;;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgB,EAAwB,IAAuB,CAAC,GAA4B;CAC1F,OAAO,EAAe,CAAC,UAAU,GAAY,CAAC;AAChD;AAKA,SAAgB,EAAc,GAA2B;CAKvD,OAJI,EAAE,eAAe,aAAmB,aACpC,EAAE,eAAe,SAAe,SAChC,EAAE,UAAU,UAAgB,UAC3B,EAAE,OACA,cADa;AAEtB;AAEA,IAAM,MAAW,OAA8B,KAAQ,CAAC,EAAA,CAAG,KAAK,MAAO,OAAO,KAAM,WAAW;CAAE,OAAO;CAAG,OAAO;AAAE,IAAI,CAAE,GACpH,KAAoF;CAAE,WAAW;CAAW,WAAW;CAAa,OAAO;CAAW,MAAM;CAAa,UAAU;AAAc,GACjM,IAAS;CAAC;CAAS;CAAa;AAAW;AAYjD,SAAgB,EAAe,EAAE,QAAQ,GAAK,YAAS,SAAM,UAAO,eAAY,cAAW,aAAU,aAAU,GAAG,KAA0B;CAC1I,IAAM,IAAS,EAAc;EAAE,QAAQ;EAAK;EAAS;CAAK,CAAC;CAC3D,OACE,kBAAC,GAAD;EAAW,MAAK;EAA8B;EAAuB;EAAqB;EAAoB;EAAiB;EAC7H,UAAA,kBAAC,GAAD;GAAU,GAAI;GAAW;EAAS,CAAA;CACzB,CAAA;AAEf;AAIA,SAAS,EAAS,EAAE,aAAU,eAAY,GAAG,KAAY;CACvD,IAAM,EAAE,SAAM,EAAY,GACpB,IAAM,EAAiB,GAAU,CAAU,GAC3C,IAAS,EAAwB,EAAE,QAAQ,EAAE,OAAO,CAAC;CAI3D,OAHI,EAAO,UAAgB,kBAAC,GAAD,CAAO,CAAA,IAC7B,EAAO,QAAQ,WACf,EAAI,OAEP,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAQ,SAAQ;EAAQ,MAAK;EAAK,WAAU;EAAqB,eAAe,EAAI,GAAG,EAAE;EACtF,UAAA,EAAE,qBAAqB;CAClB,CAAA,GACR,kBAAC,GAAD;EACE,YAAA;EACA,GAAI;EACJ,IAAI,EAAI,SAAS,QAAQ,OAAO,EAAI;EACpC,YAAY,MAAM,EAAI,GAAG,EAAE,IAAI,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC;EAC3D,iBAAiB,EAAI,GAAG,IAAI,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC;CACzD,CAAA,CACD,EAAA,CAAA,IAbkB,kBAAC,GAAD;EAAM,GAAI;EAAQ;CAAM,CAAA,IADT,kBAAC,GAAD,EAAA,UAAW,EAAE,yBAAyB,EAAY,CAAA;AAgBzF;AAEA,SAAS,EAAK,EAAE,QAAK,WAAQ,SAAM,WAAW,GAAS,gBAAa,cAAW,IAAI,cAA2C;CAC5H,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAM,GAAM,GACZ,IAAW,GAAQ,CAAO,CAAC,CAAC,EAAE,EAAE,OAChC,IAAQ,EAAI,MAAM,IAAI,GAAG,KAAK,IAC9B,IAAS,EAAO,MAAM,MAAM,MAAM,EAAI,MAAM,IAAI,QAAQ,CAAC,KAAK,IAC9D,IAAU,EAAI,MAAM,IAAI,SAAS,MAAM,KACvC,IAAO,GAAO,EAAI,KAAK,GACvB,IAAU;EAAE,GAAG;EAAO;EAAQ,SAAS,IAAU,MAAM,KAAA;CAAU,GACjE,KAAU,MAAkC,EAAI,GAAG,IAAI;EAAE,GAAG;EAAS,GAAG;CAAK,CAAC,GAC9E,CAAC,GAAM,KAAW,EAAS,CAAK;CACtC,SAAgB,EAAQ,CAAK,GAAG,CAAC,CAAK,CAAC;CACvC,IAAM,EAAE,WAAO,aAAU,EAAgB,CAAM,GACzC,IAAQ,IAAU,YAAY,KAAU,KAAA,GACxC,IAAO,EAAS,IAAO,EAAM,iBAAiB,GAAO,GAAM,GAAO,GAAO,CAAQ,GAAG;EAAE,MAAM;EAAY,OAAO,IAAU,YAAY,KAAA;CAAU,GAAG,GAAM,IAAW,GAAG,MAC1K,EAAO,SAAS,OAAO;EAAE,GAAG;EAAG;EAAM,GAAG,KAAS,KAAA;EAAW;EAAO;CAAS,GAAG,CAAM,CACvF,GACM,IAAM,GAAU,gBAAgB,CAAO,GACvC,EAAE,YAAS,cAAW,GAAW,GACjC,IAAS,OAAO,MAAe;EACnC,AAAI,MAAM,EAAQ;GAAE,OAAO,EAAE,8BAA8B,EAAE,OAAO,EAAE,MAAM,CAAC;GAAG,aAAa,EAAE,2BAA2B;GAAG,SAAS,EAAE,uBAAuB;EAAE,CAAC,KAChK,MAAM,EAAI,UAAU,EAAO,SAAS,OAAO,EAAE,EAAE,CAAC;CAEpD;CACA,OACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;KAA2B,UAAA,EAAE,sBAAsB;IAAM,CAAA,GACvE,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAI,GAAG,KAAK;KAC1C,UAAA,EAAE,oBAAoB;IACjB,CAAA,CACL;;GACL,kBAAC,OAAD;IAAK,WAAU;IAAa,MAAK;IAAQ,cAAY,EAAE,sBAAsB;IAA7E,UAAA,CACE,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAS,IAAU,YAAY;KAAW,gBAAc,CAAC;KAAS,eAAe,EAAI,GAAG,IAAI,EAAE,GAAG,EAAM,CAAC;KACvH,UAAA,EAAE,sBAAsB;IACnB,CAAA,GACR,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAS,IAAU,YAAY;KAAW,gBAAc;KAAS,eAAe,EAAI,GAAG,IAAI;MAAE,GAAG;MAAO,SAAS;KAAI,CAAC;KACpI,UAAA,EAAE,wBAAwB;IACrB,CAAA,CACL;;GACL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,QAAD;KACE,WAAU;KACV,MAAK;KACL,WAAW,MAAM;MAEf,AADA,EAAE,eAAe,GACjB,EAAO,EAAE,GAAG,EAAK,KAAK,EAAE,CAAC;KAC3B;KANF,UAAA;MAQE,kBAAC,GAAD;OAAO,SAAS,GAAG,EAAI;OAAK,WAAU;OACnC,UAAA,EAAE,uBAAuB;MACrB,CAAA;MACP,kBAAC,GAAD;OAAO,IAAI,GAAG,EAAI;OAAK,WAAU;OAAM,MAAK;OAAS,OAAO;OAAM,aAAa,EAAE,uBAAuB;OAAG,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;MAAI,CAAA;MACtJ,kBAAC,GAAD;OAAQ,MAAK;OAAS,MAAK;OAAK,SAAQ;OACrC,UAAA,EAAE,mBAAmB;MAChB,CAAA;KACJ;IACL,CAAA,GAAA,CAAC,KACA,kBAAC,IAAD;KAAQ,OAAO;KAAQ,gBAAgB,MAAM,EAAO,EAAE,QAAQ,OAAO,KAAK,EAAE,EAAmB,CAAC;KAAhG,UAAA,CACE,kBAAC,IAAD;MAAe,MAAK;MAAK,cAAY,EAAE,uBAAuB;MAC5D,UAAA,kBAAC,IAAD,EAAA,WAAe,MAAc,EAAE,IAAI,kBAAkB,MAAiC,4BAA4B,EAAe,CAAA;KACpH,CAAA,GACf,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAY,OAAM;MAAI,UAAA,EAAE,4BAA4B;KAAc,CAAA,GACjE,EAAO,KAAK,MACX,kBAAC,GAAD;MAAoB,OAAO;MACxB,UAAA,EAAE,kBAAkB,GAAG;KACd,GAFK,CAEL,CACb,CACY,EAAA,CAAA,CACT;IAEP,CAAA,CAAA;;GACL,kBAAC,IAAD,EAAW,OAAO,EAAI,MAAQ,CAAA;GAC7B,EAAK,WAAW,CAAC,EAAK,MAAM,SAC3B,kBAAC,GAAD,CAAO,CAAA,IACL,EAAK,SAAS,CAAC,EAAK,MAAM,SAC5B,kBAAC,GAAD;IAAY,OAAO,EAAK;IAAO,SAAS,EAAE,2BAA2B;IAAG,OAAO,EAAK;GAAS,CAAA,IAC1F,EAAK,MAAM,SAGd,kBAAC,MAAD;IAAI,WAAU;IAA8D,aAAW,EAAK;IACzF,UAAA,EAAK,MAAM,KAAK,MAAM;KACrB,IAAM,IAAI,EAAc,CAAC,GACnB,IAAO,MAAM,cAAc,IAAc,CAAC,IAAI,KAAA;KACpD,OACE,kBAAC,MAAD;MAAe,WAAU;MAAuC,aAAU;MAAc,eAAa,IAAU,YAAY;MAA3H,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACG,IACC,kBAAC,QAAD;QAAM,WAAU;QAA2B,UAAA,EAAE;OAAY,CAAA,IAEzD,kBAAC,UAAD;QAAQ,MAAK;QAAS,WAAU;QAAoD,eAAe,EAAI,GAAG,EAAE,EAAE;QAC3G,UAAA,EAAE;OACG,CAAA,GAEV,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA;SACG,CAAC,KAAW,kBAAC,GAAD;UAAO,SAAS,GAAM;UAAK,UAAA,EAAE,kBAAkB,GAAG;SAAS,CAAA;SACxE,kBAAC,QAAD,EAAA,UAAO,EAAE,SAAe,CAAA;SACxB,kBAAC,GAAD,EAAc,IAAI,KAAW,EAAE,aAAa,EAAE,aAAa,EAAE,WAAa,CAAA;SACzE,MAAM,eAAe,EAAE,cAAc,kBAAC,QAAD,EAAA,UAAO,EAAE,wBAAwB,EAAE,MAAM,GAAW,GAAG,EAAE,UAAU,EAAE,CAAC,EAAQ,CAAA;QACjH;OACF,CAAA,CAAA;MACL,CAAA,GAAA,kBAAC,OAAD;OAAK,WAAU;OACZ,UAAA,IACC,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAU,UAAU,EAAI;QAAS,eAAe,KAAK,EAAI,UAAU,EAAO,SAAS,QAAQ,EAAE,EAAE,CAAC;QACvH,UAAA,EAAE,oBAAoB;OACjB,CAAA,IAER,kBAAA,GAAA,EAAA,UAAA;QACG,KACC,kBAAC,KAAD;SAAG,WAAW,EAAe;UAAE,MAAM;UAAM,SAAS;SAAQ,CAAC;SAAS;SACnE,UAAA,EAAE,qBAAqB;QACvB,CAAA;QAEL,kBAAC,GAAD;SAAQ,MAAK;SAAK,SAAQ;SAAU,eAAe,EAAI,GAAG,EAAE,EAAE;SAC3D,UAAA,EAAE,eAAe;QACZ,CAAA;QACR,kBAAC,GAAD;SAAQ,MAAK;SAAK,SAAQ;SAAc,UAAU,EAAI;SAAS,eAAe,KAAK,EAAO,CAAC;SACxF,UAAA,EAAE,iBAAiB;QACd,CAAA;OACR,EAAA,CAAA;MAED,CAAA,CACH;KArCK,GAAA,EAAE,EAqCP;IAER,CAAC;GACC,CAAA,IA/CJ,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,IAAU,6BAA6B,sBAAsB;GAAK,CAAA;GAiD5G,kBAAC,IAAD;IAAa;IAAM,SAAS,EAAK;IAAS,UAAU,EAAK;IAAS,SAAS,MAAM,EAAI,GAAG,IAAI;KAAE,GAAG;KAAS,MAAM,IAAI,IAAI,IAAI,KAAA;IAAU,CAAC;GAAI,CAAA;GAC1I;EACE;;AAET;AAUA,IAAM,IAAc;CAAE,OAAO;CAAI,MAAM;CAAI,aAAa;CAAI,WAAW;CAAI,MAAM;AAAG;AA6BpF,SAAgB,EAAc,GAAqB;CACjD,IAAM,EAAC,WAAO,aAAO,EAAgB,EAAE,MAAM,GACvC,CAAC,GAAQ,KAAY,EAAS;EAAC,IAAG,EAAE;EAAG,MAAK,EAAE;EAAK;EAAO;EAAM,KAAI;EAAE,WAAU;CAAmB,CAAC,GACtG,IAAS;CACb,IAAG,EAAQ,OAAK,EAAE,MAAI,EAAQ,SAAO,EAAE,QAAM,EAAQ,WAAS,KAAQ,EAAQ,UAAQ,GAAM;EAC1F,IAAM,IAAU,EAAQ,MAAI,QAAM,EAAE,OAAK,EAAQ,aAAW,EAAQ,SAAO,EAAE,QAAM,EAAQ,WAAS,KAAQ,EAAQ,UAAQ;EAE5H,AADA,IAAS;GAAC,IAAG,EAAE;GAAG,MAAK,EAAE;GAAK;GAAO;GAAM,KAAI,IAAU,EAAQ,MAAI,EAAQ,MAAI;GAAE,WAAU;EAAI,GACjG,EAAW,CAAQ;CACrB;CACA,IAAM,EAAC,UAAM,UAAM,cAAQ,EAAS,EAAC,UAAM,CAAC,GACtC,EAAC,SAAG,EAAY;CACtB,IAAG,CAAC,GAAM,OAAO,IAAM,kBAAC,GAAD;EAAmB;EAAO,SAAS,EAAE,2BAA2B;EAAG,OAAO;CAAQ,CAAA,IAAE,kBAAC,GAAD,CAAM,CAAA;CACjH,IAAM,IAAM;EAAC,GAAG;EAAE;EAAO,YAAW,MAAkB;GAAoD,AAAnD,GAAY,OAAO;IAAC,GAAG;IAAI,WAAU,EAAQ;GAAE,EAAE,GAAE,EAAE,YAAY,CAAO;EAAE;CAAC;CAElI,OADG,EAAM,MAAM,EAAE,KAAK,EAAE,iBAAe,WAAgB,kBAAC,GAAD;EAAwC,GAAI;EAAO,OAAO;CAAO,GAAvC,EAAS,GAA8B,IACjH,kBAAC,GAAD,EAA0C,GAAI,EAAO,GAAzB,EAAS,GAAgB;AAC9D;AAEA,SAAS,EAAsB,EAAE,OAAI,QAAQ,GAAK,SAAM,WAAW,GAAS,iBAAc,iBAAa,YAAQ,eAAY,cAAW,eAAW,YAAS,kBAAkC;CAC1L,IAAM,IAAS,EAAoB,CAAG,GAChC,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAM,GAAM,GACZ,IAAY,GAAQ,CAAO,GAC3B,CAAC,GAAU,KAAe,EAAS,EAAU,EAAE,EAAE,SAAS,IAAI,GAC9D,IAAK,EAAW,GAAI,GAAM,GAAU,EAAE,UAAO,CAAC,GAE9C,IAAS,CAAC,CAAC,EAAS,EAAE,UAAO,CAAC,CAAC,CAAC,OAAO,MAAM,EAAK,EAAE,SAAS,QAC7D,IAAS,EAAiB,CAAO,GAEjC,CAAC,GAAM,KAAW,EAAsB,IAAK,OAAO,CAAK,GACzD,CAAC,GAAO,KAAY,EAA0C,IAAI,GAClE,CAAC,IAAQ,KAAa,EAAwB,IAAI,GAClD,EAAE,aAAS,eAAW,GAAW,GACjC,IAAO,EAAG;CAChB,SAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,IAAS,EAAK,OAAO;EAC3B,GAAS,MAAM,KAAK;GAAE,OAAO,GAAQ,SAAS;GAAI,MAAM,GAAQ,QAAQ;GAAI,aAAa,GAAQ,eAAe;GAAI,MAAM,GAAQ,QAAQ;GACxI,WAAW,EAAK,cAAc,CAAC,EAAK,OAAO,EAAW,IAAI,KAAK,EAAK,UAAU,CAAC,IAAI;EAAG,CAAC;CAC3F,GAAG,CAAC,GAAM,CAAQ,CAAC;CACnB,IAAM,EAAE,iBAAa,GACf,KAAQ,UACL,GAAM,QAAQ,GAAA,CAAI,QAAQ,EAAU,IAAI,GAAK,MAAiB;EACnE,IAAM,IAAM,GAAS,CAAI;EACzB,OAAO,KAAO,MAAQ,IAAO,EAAI,WAAW,KAAK,OAAO,IAAI;CAC9D,CAAC,GACD,CAAC,GAAM,MAAM,EAAQ,CACvB;CACA,IAAI,KAAM,CAAC,GAET,OADI,EAAG,QAAc,kBAAC,GAAD;EAAY,OAAO,EAAG;EAAO,SAAS,EAAE,2BAA2B;EAAG,OAAO,EAAG;CAAS,CAAA,IAE5G,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAA;EAA5B,UAAA,CACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,GAClC,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,CAChC;;CAGT,IAAM,IAAI,GACJ,KAAO,MAAyB,GAAS,OAAS;EAAE,GAAG;EAAM,GAAG;CAAM,EAAE,GACxE,KAAQ,MAAe;EAC3B,IAAM,IAAM,EAAkB,CAAC;EAE/B,AADA,EAAS,CAAG,GACZ,EAAO,GAAK,cAAc;CAC5B,GACM,YAAc;EAAE,OAAO,EAAE,MAAM,KAAK;EAAG,MAAM,EAAE,KAAK,KAAK,KAAK,KAAA;EAAW,aAAa,EAAE,YAAY,KAAK;EAAG,MAAM,EAAE;CAAK,IACzH,KAAO,YAAY;EAGvB,IAFA,EAAS,IAAI,GACb,EAAU,IAAI,GACV,CAAC,EAAE,MAAM,KAAK,GAAG,OAAO,EAAS,EAAE,8BAA8B,CAAC;EACtE,IAAI;GACF,IAAI,CAAC,GAAM;IACT,IAAM,IAAU,MAAM,EAAG,OAAO;KAAE;KAAM,MAAM,GAAG,IAAW,GAAK,EAAE;KAAG,gBAAgB;IAAW,CAAC;IAClG,IAAY,CAAO;IACnB;GACF;GACA,IAAM,IAAQ,MAAM,EAAG,QAAQ,GAAK,CAAC;GAErC,AADA,EAAU,EAAE,kBAAkB,CAAC,GAC/B,GAAS,OAAS;IAAE,GAAG;IAAM,MAAM,EAAM,OAAO,EAAS,EAAE,QAAQ,EAAK;GAAK,EAAE;EACjF,SAAS,GAAG;GACV,EAAK,CAAC;EACR;CACF,GACM,KAAU,OAAO,MAAkB;EAEvC,AADA,EAAS,IAAI,GACb,EAAU,IAAI;EACd,IAAI;GAGF,AAFI,IAAM,MAAM,EAAG,QAAQ,EAAE,YAAY,IAAI,KAAK,EAAE,SAAS,CAAC,CAAC,YAAY,IAAI,KAAA,CAAS,IACnF,MAAM,EAAG,UAAU,GACxB,EAAU,EAAE,IAAO,mCAAmC,kCAAkC,CAAC;EAC3F,SAAS,GAAG;GACV,EAAK,CAAC;EACR;CACF,GACM,KAAS,YAAY;EACrB,IAAC,KAAU,MAAM,GAAQ;GAAE,OAAO,EAAE,8BAA8B,EAAE,OAAO,EAAK,MAAM,CAAC;GAAG,aAAa,EAAE,2BAA2B;GAAG,SAAS,EAAE,uBAAuB;EAAE,CAAC,GAChL,IAAI;GAEF,AADA,MAAM,EAAG,OAAO,GAChB,KAAY,CAAI;EAClB,SAAS,GAAG;GACV,EAAK,CAAC;EACR;CACF,GACM,IAAI,IAAO,EAAc,CAAI,IAAI,SACjC,KAAO,KAAQ,MAAM,cAAc,KAAc,CAAI,IAAI,KAAA,GACzD,KAA0B;EAC9B,OAAO;EACP,WAAW,MAAS,EAAI,EAAE,MAAM,EAAG,WAAW,CAAI,EAAE,CAAC;EACrD,aAAa,KAAQ,MAAS,EAAG,YAAY,CAAI,IAAI,KAAA;EACrD,UAAU,EAAG;EACb,aAAa,EAAE,gCAAgC;EAC/C,cAAc,EAAE,qBAAqB;CACvC;CACA,OACE,kBAAA,GAAA,EAAA,UAAA,CACA,kBAAC,QAAD;EACE,WAAU;EACV,aAAU;EACV,WAAW,MAAM;GAEf,AADA,EAAE,eAAe,GACjB,GAAU;EACZ;EANF,UAAA;GAQE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;MAA2B,UAAA,EAAE,IAAO,6BAA6B,yBAAyB;KAAM,CAAA;KAC7G,KAAQ,kBAAC,GAAD;MAAO,SAAS,GAAM;MAAK,UAAA,EAAE,kBAAkB,GAAG;KAAS,CAAA;KACnE,MACC,kBAAC,KAAD;MAAG,WAAU;MAAsD;MAChE,UAAA,EAAE,qBAAqB;KACvB,CAAA;IAEF;;GACJ,GAAM,cACL,kBAAC,GAAD;IAAO,SAAS,EAAK,eAAe,aAAa,gBAAgB;IAC/D,UAAA,kBAAC,GAAD,EAAA,UACG,EAAK,oBACF,EAAE,kBAAkB,EAAK,eAAe,aAAa,oBAAoB,iBAAiB,EAAE,QAAQ,EAAK,kBAAkB,CAAC,IAC5H,EAAE,kBAAkB,EAAK,eAAe,aAAa,iBAAiB,YAAY,EACtE,CAAA;GACb,CAAA;GAER,EAAU,SAAS,KAClB,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KAAO,SAAS,GAAG,EAAI;KAAS,UAAA,EAAE,yBAAyB;IAAS,CAAA,GACpE,kBAAC,IAAD;KACE,OAAO;KACP,gBAAgB,MAAM;MAEpB,AADA,EAAY,OAAO,KAAK,EAAE,CAAC,GAC3B,EAAQ,IAAO,OAAO,CAAK;KAC7B;KALF,UAAA,CAOE,kBAAC,IAAD;MAAe,IAAI,GAAG,EAAI;MAAQ,WAAU;MAC1C,UAAA,kBAAC,IAAD,EAAA,WAAe,MAAc,EAAU,MAAM,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,SAAS,EAAe,CAAA;KAC9E,CAAA,GACf,kBAAC,IAAD,EAAA,UACG,EAAU,KAAK,MACd,kBAAC,GAAD;MAA0B,OAAO,EAAE;MAChC,UAAA,EAAE;KACO,GAFK,EAAE,KAEP,CACb,EACY,CAAA,CACT;IACL,CAAA,CAAA;;GAEP,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KAAO,SAAS,GAAG,EAAI;KAAU,UAAA,EAAE,0BAA0B;IAAS,CAAA,GACtE,kBAAC,GAAD;KAAO,IAAI,GAAG,EAAI;KAAS,OAAO,EAAE;KAAO,UAAA;KAAS,WAAW;KAAK,WAAW,MAAM,EAAI,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC;IAAI,CAAA,CACnH;;GACL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAO,SAAS,GAAG,EAAI;MAAS,UAAA,EAAE,qBAAqB;KAAS,CAAA,GAChE,kBAAC,GAAD;MAAO,IAAI,GAAG,EAAI;MAAQ,OAAO,EAAE;MAAM,aAAa,EAAE,yBAAyB;MAAG,WAAW,MAAM,EAAI,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC;KAAI,CAAA,CAClI;IACL,CAAA,GAAA,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAO,SAAS,GAAG,EAAI;MAAY,UAAA,EAAE,0BAA0B;KAAS,CAAA,GACxE,kBAAC,GAAD;MAAO,IAAI,GAAG,EAAI;MAAW,MAAK;MAAiB,OAAO,EAAE;MAAW,WAAW,MAAM,EAAI,EAAE,WAAW,EAAE,OAAO,MAAM,CAAC;KAAI,CAAA,CAC1H;IACF,CAAA,CAAA;;GACL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KAAO,SAAS,GAAG,EAAI;KAAgB,UAAA,EAAE,4BAA4B;IAAS,CAAA,GAC9E,kBAAC,GAAD;KAAU,IAAI,GAAG,EAAI;KAAe,MAAM;KAAG,OAAO,EAAE;KAAa,WAAW,MAAM,EAAI,EAAE,aAAa,EAAE,OAAO,MAAM,CAAC;IAAI,CAAA,CACxH;;IACH,KAAU,CAAC,MAAS,kBAAC,IAAD;IAAa;IAAM,KAAK,EAAG;IAAU,QAAQ,EAAG;IAAQ,SAAS,MAAS,KAAK,EAAG,SAAS,CAAI,CAAC,CAAC,MAAM,CAAI;GAAI,CAAA;GACrI,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,QAAD;MAAM,WAAU;MAAuB,UAAA,EAAE,qBAAqB;KAAQ,CAAA;KACrE,IAAe,EAAa,EAAM,IAAI,kBAAC,GAAD;MAAU,cAAY,GAAO;MAAe,MAAM;MAAI,OAAO,EAAE;MAAM,aAAa,GAAO;MAAa,WAAW,MAAM,EAAI,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC;KAAI,CAAA;KAC7L,CAAC,KAAQ,kBAAC,KAAD;MAAG,WAAU;MAAiC,UAAA,EAAE,kCAAkC;KAAK,CAAA;IAC9F;;GACJ,KAAS,kBAAC,IAAD,EAAA,UAAY,OAAO,KAAU,WAAW,IAAQ,EAAa,GAAG,CAAK,EAAa,CAAA;GAC3F,MAAU,CAAC,KACV,kBAAC,KAAD;IAAG,MAAK;IAAS,WAAU;IACxB,UAAA;GACA,CAAA;GAEL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU,EAAG;MAChC,UAAA,EAAG,SAAS,EAAE,eAAe,IAAI,EAAE,IAAO,gBAAgB,4BAA4B;KACjF,CAAA;KACP,KAAQ,CAAC,EAAK,QACb,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;MAAU,UAAU,EAAG;MAAQ,eAAe,KAAK,GAAQ,EAAI;MAC1F,UAAA,EAAE,wBAAwB;KACrB,CAAA;KAET,GAAM,UAAU,eACf,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;MAAU,UAAU,EAAG;MAAQ,eAAe,KAAK,GAAQ,EAAK;MAC3F,UAAA,EAAE,0BAA0B;KACvB,CAAA;KAET,KACC,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;MAAc,WAAU;MAAU,UAAU,EAAG;MAAQ,eAAe,KAAK,GAAO;MAC7G,UAAA,EAAE,uBAAuB;KACpB,CAAA;IAEP;;GACJ;EACG;CACL,CAAA,GAAA,KAAQ,kBAAC,GAAD;EAA6B;EAAY,SAAS;EAAc;EAAiB;EAAiB;CAAS,CAAA,CAClH,EAAA,CAAA;AAEN;AAEA,SAAS,GAAM,EAAE,SAAM,QAAK,WAAQ,aAAwG;CAC1I,IAAM,EAAE,SAAM,EAAY,GACpB,IAAO,EAAyB,IAAI;CAC1C,OACE,kBAAC,OAAD;EAAK,WAAU;EAAe,aAAU;EAAxC,UAAA,CACE,kBAAC,QAAD;GAAM,WAAU;GAAuB,UAAA,EAAE,sBAAsB;EAAQ,CAAA,GACtE,IACC,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACG,IAAM,kBAAC,OAAD;KAAU;KAAK,KAAI;KAAG,WAAU;IAA0D,CAAA,IAAI,kBAAC,QAAD;KAAM,WAAU;KAAyB,UAAA,EAAE,wBAAwB;IAAQ,CAAA;IAChL,kBAAC,GAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAU,UAAU;KAAQ,eAAe,EAAK,SAAS,MAAM;KACpG,UAAA,EAAE,IAAM,4BAA4B,yBAAyB;IACxD,CAAA;IACP,KACC,kBAAC,GAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAQ,UAAU;KAAQ,eAAe,EAAO,IAAI;KACzF,UAAA,EAAE,wBAAwB;IACrB,CAAA;IAEV,kBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,QAAO;KACP,QAAA;KACA,cAAY,EAAE,sBAAsB;KACpC,WAAW,MAAM;MACf,IAAM,IAAI,EAAE,OAAO,QAAQ;MAE3B,AADA,EAAE,OAAO,QAAQ,IACb,KAAG,EAAO,CAAC;KACjB;IACD,CAAA;GACE;EAEL,CAAA,IAAA,kBAAC,KAAD;GAAG,WAAU;GAAiC,UAAA,EAAE,kCAAkC;EAAK,CAAA,CAEtF;;AAET"}
@@ -6,81 +6,90 @@ import { Badge as i } from "../../ui/badge.js";
6
6
  import { Select as a, SelectContent as o, SelectItem as s, SelectTrigger as c, SelectValue as l } from "../../ui/select.js";
7
7
  import { ErrorLine as u, useAction as d } from "../social/parts.js";
8
8
  import { LoadFailed as f, Rows as p } from "./shell.js";
9
- import { usePageReview as m } from "../../react/page-review.js";
10
- import { useState as h } from "react";
11
- import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
12
- import { cn as y } from "cn";
9
+ import { useEditorSaveTask as m } from "../content/editor-save.js";
10
+ import { usePageReview as h } from "../../react/page-review.js";
11
+ import { useState as g } from "react";
12
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
13
+ import { cn as b } from "cn";
13
14
  //#region src/components/admin/page-review.tsx
14
- function b(e) {
15
+ function x(e) {
15
16
  if (e) try {
16
17
  let t = new URL(e, globalThis.location?.href ?? "http://localhost/");
17
18
  if (t.protocol === "http:" || t.protocol === "https:") return e;
18
19
  } catch {}
19
20
  }
20
- var x = (e) => e.replace(/[_-]+/g, " "), S = (e) => e.source_entry || e.source_name;
21
- function C({ content: C, version: w, client: T, onError: E, className: D, appearance: O }) {
22
- let { t: k } = e(), A = m(C, w, { client: T }), j = d("reader.save", E), [M, N] = h(!1), P = A.review, F = P?.pages.some((e) => e.flags.includes("segmented_selection_preserved")) ?? !1, I = (e) => {
23
- !P || F || j.pending || A.loading || (N(!1), j.run(() => A.update({
21
+ var S = (e) => e.replace(/[_-]+/g, " "), C = (e) => e.source_entry || e.source_name;
22
+ function w({ content: w, version: T, client: E, onError: D, className: O, appearance: k }) {
23
+ let { t: A } = e(), j = h(w, T, { client: E }), M = d("reader.save", D), [N, P] = g(!1);
24
+ m(`page-review:${T}`, {
25
+ label: A("pageReview.title"),
26
+ dirty: !1,
27
+ pending: M.pending,
28
+ valid: !0,
29
+ save: async () => !0
30
+ });
31
+ let F = j.review, I = F?.pages.some((e) => e.flags.includes("segmented_selection_preserved")) ?? !1, L = (e) => {
32
+ !F || I || M.pending || j.loading || (P(!1), M.run(() => j.update({
24
33
  ...e,
25
- revision: P.revision
26
- })).then((e) => e && N(!0)));
27
- }, L = (() => {
28
- if (!P) return A.error ? /* @__PURE__ */ _(f, {
29
- error: A.error,
30
- message: k("pageReview.loadFailed"),
31
- retry: A.reload
32
- }) : /* @__PURE__ */ _(p, { count: 2 });
33
- if (!P.pages.length) return /* @__PURE__ */ _("p", {
34
+ revision: F.revision
35
+ })).then((e) => e && P(!0)));
36
+ }, R = (() => {
37
+ if (!F) return j.error ? /* @__PURE__ */ v(f, {
38
+ error: j.error,
39
+ message: A("pageReview.loadFailed"),
40
+ retry: j.reload
41
+ }) : /* @__PURE__ */ v(p, { count: 2 });
42
+ if (!F.pages.length) return /* @__PURE__ */ v("p", {
34
43
  className: "text-muted-foreground",
35
- children: k("pageReview.empty")
44
+ children: A("pageReview.empty")
36
45
  });
37
- let e = F || j.pending || A.loading;
38
- return /* @__PURE__ */ v(g, { children: [
39
- /* @__PURE__ */ _("p", {
46
+ let e = I || M.pending || j.loading;
47
+ return /* @__PURE__ */ y(_, { children: [
48
+ /* @__PURE__ */ v("p", {
40
49
  className: "text-muted-foreground",
41
- children: k("pageReview.summary", {
42
- selected: P.selected,
43
- total: P.pages.length,
44
- flagged: P.flagged
50
+ children: A("pageReview.summary", {
51
+ selected: F.selected,
52
+ total: F.pages.length,
53
+ flagged: F.flagged
45
54
  })
46
55
  }),
47
- F ? /* @__PURE__ */ _("p", {
56
+ I ? /* @__PURE__ */ v("p", {
48
57
  role: "status",
49
58
  className: "rounded-md border border-border p-2 text-muted-foreground",
50
- children: k("pageReview.chapterGuard")
51
- }) : /* @__PURE__ */ _("p", {
59
+ children: A("pageReview.chapterGuard")
60
+ }) : /* @__PURE__ */ v("p", {
52
61
  className: "text-xs text-muted-foreground",
53
- children: k("pageReview.help")
62
+ children: A("pageReview.help")
54
63
  }),
55
- /* @__PURE__ */ _("div", {
64
+ /* @__PURE__ */ v("div", {
56
65
  className: "max-h-[32rem] overflow-y-auto rounded-md border border-border p-2",
57
- children: /* @__PURE__ */ _(r, {
58
- items: P.pages,
66
+ children: /* @__PURE__ */ v(r, {
67
+ items: F.pages,
59
68
  id: (e) => e.id,
60
- name: S,
61
- label: k("pageReview.order"),
62
- disabled: e || P.pages.length < 2,
69
+ name: C,
70
+ label: A("pageReview.order"),
71
+ disabled: e || F.pages.length < 2,
63
72
  onMove: (e, t) => {
64
73
  if (e === t) return;
65
- let n = P.pages.map((e) => e.id), [r] = n.splice(e, 1);
66
- n.splice(t, 0, r), I({ order: n });
74
+ let n = F.pages.map((e) => e.id), [r] = n.splice(e, 1);
75
+ n.splice(t, 0, r), L({ order: n });
67
76
  },
68
77
  children: (t, n, r) => {
69
- let u = b(t.preview_url), d = b(t.source_url);
70
- return /* @__PURE__ */ v("div", {
78
+ let u = x(t.preview_url), d = x(t.source_url);
79
+ return /* @__PURE__ */ y("div", {
71
80
  className: "flex gap-2 rounded-md border border-border p-2",
72
81
  "data-ckui": "page-review-row",
73
82
  "data-included": t.included,
74
83
  "data-mode": t.mode,
75
- children: [/* @__PURE__ */ v("div", {
84
+ children: [/* @__PURE__ */ y("div", {
76
85
  className: "flex shrink-0 flex-col items-center gap-1",
77
86
  children: [
78
- /* @__PURE__ */ _("span", {
87
+ /* @__PURE__ */ v("span", {
79
88
  className: "text-xs text-muted-foreground",
80
89
  children: r + 1
81
90
  }),
82
91
  n,
83
- u && /* @__PURE__ */ _("img", {
92
+ u && /* @__PURE__ */ v("img", {
84
93
  src: u,
85
94
  alt: "",
86
95
  loading: "lazy",
@@ -88,81 +97,81 @@ function C({ content: C, version: w, client: T, onError: E, className: D, appear
88
97
  className: "h-16 w-12 rounded object-contain"
89
98
  })
90
99
  ]
91
- }), /* @__PURE__ */ v("div", {
100
+ }), /* @__PURE__ */ y("div", {
92
101
  className: "grid min-w-0 flex-1 gap-1",
93
102
  children: [
94
- /* @__PURE__ */ v("p", {
103
+ /* @__PURE__ */ y("p", {
95
104
  className: "break-words",
96
- children: [/* @__PURE__ */ v("span", {
105
+ children: [/* @__PURE__ */ y("span", {
97
106
  className: "text-muted-foreground",
98
- children: [k("pageReview.original"), " "]
99
- }), S(t)]
107
+ children: [A("pageReview.original"), " "]
108
+ }), C(t)]
100
109
  }),
101
- /* @__PURE__ */ v("p", {
110
+ /* @__PURE__ */ y("p", {
102
111
  className: "break-words",
103
- children: [/* @__PURE__ */ v("span", {
112
+ children: [/* @__PURE__ */ y("span", {
104
113
  className: "text-muted-foreground",
105
- children: [k("pageReview.canonical"), " "]
114
+ children: [A("pageReview.canonical"), " "]
106
115
  }), t.id]
107
116
  }),
108
- /* @__PURE__ */ v("div", {
117
+ /* @__PURE__ */ y("div", {
109
118
  className: "flex flex-wrap items-center gap-2",
110
119
  children: [
111
- /* @__PURE__ */ _(i, {
120
+ /* @__PURE__ */ v(i, {
112
121
  variant: t.included ? "outline" : "secondary",
113
- children: k(t.included ? "pageReview.included" : "pageReview.excluded")
122
+ children: A(t.included ? "pageReview.included" : "pageReview.excluded")
114
123
  }),
115
- !!t.flags.length && /* @__PURE__ */ _(i, {
124
+ !!t.flags.length && /* @__PURE__ */ v(i, {
116
125
  variant: "outline",
117
- children: k("pageReview.flagged")
126
+ children: A("pageReview.flagged")
118
127
  }),
119
- d && /* @__PURE__ */ _("a", {
128
+ d && /* @__PURE__ */ v("a", {
120
129
  href: d,
121
130
  target: "_blank",
122
131
  rel: "noopener noreferrer",
123
132
  className: "text-xs underline",
124
- children: k("pageReview.source")
133
+ children: A("pageReview.source")
125
134
  })
126
135
  ]
127
136
  }),
128
- /* @__PURE__ */ v("p", {
137
+ /* @__PURE__ */ y("p", {
129
138
  className: "text-xs text-muted-foreground",
130
139
  children: [
131
- x(t.decision.reason),
140
+ S(t.decision.reason),
132
141
  " · ",
133
- k("pageReview.confidence", { confidence: x(t.decision.confidence) })
142
+ A("pageReview.confidence", { confidence: S(t.decision.confidence) })
134
143
  ]
135
144
  }),
136
- !!t.flags.length && /* @__PURE__ */ _("p", {
145
+ !!t.flags.length && /* @__PURE__ */ v("p", {
137
146
  className: "text-xs text-muted-foreground",
138
- children: t.flags.map(x).join(" · ")
147
+ children: t.flags.map(S).join(" · ")
139
148
  }),
140
- /* @__PURE__ */ v(a, {
149
+ /* @__PURE__ */ y(a, {
141
150
  value: t.mode,
142
151
  onValueChange: (e) => {
143
- e !== t.mode && (e === "auto" || e === "include" || e === "exclude") && I({ pages: [{
152
+ e !== t.mode && (e === "auto" || e === "include" || e === "exclude") && L({ pages: [{
144
153
  id: t.id,
145
154
  mode: e
146
155
  }] });
147
156
  },
148
- children: [/* @__PURE__ */ _(c, {
157
+ children: [/* @__PURE__ */ v(c, {
149
158
  size: "sm",
150
159
  className: "w-44",
151
160
  disabled: e,
152
- "aria-label": k("pageReview.choice", { name: S(t) }),
153
- children: /* @__PURE__ */ _(l, { children: (e) => k(e === "include" ? "pageReview.include" : e === "exclude" ? "pageReview.exclude" : "pageReview.automatic") })
154
- }), /* @__PURE__ */ v(o, { children: [
155
- /* @__PURE__ */ _(s, {
161
+ "aria-label": A("pageReview.choice", { name: C(t) }),
162
+ children: /* @__PURE__ */ v(l, { children: (e) => A(e === "include" ? "pageReview.include" : e === "exclude" ? "pageReview.exclude" : "pageReview.automatic") })
163
+ }), /* @__PURE__ */ y(o, { children: [
164
+ /* @__PURE__ */ v(s, {
156
165
  value: "auto",
157
- children: k("pageReview.automatic")
166
+ children: A("pageReview.automatic")
158
167
  }),
159
- /* @__PURE__ */ _(s, {
168
+ /* @__PURE__ */ v(s, {
160
169
  value: "include",
161
- children: k("pageReview.include")
170
+ children: A("pageReview.include")
162
171
  }),
163
- /* @__PURE__ */ _(s, {
172
+ /* @__PURE__ */ v(s, {
164
173
  value: "exclude",
165
- children: k("pageReview.exclude")
174
+ children: A("pageReview.exclude")
166
175
  })
167
176
  ] })]
168
177
  })
@@ -172,45 +181,45 @@ function C({ content: C, version: w, client: T, onError: E, className: D, appear
172
181
  }
173
182
  })
174
183
  }),
175
- j.error?.status === 409 ? /* @__PURE__ */ _("p", {
184
+ M.error?.status === 409 ? /* @__PURE__ */ v("p", {
176
185
  role: "alert",
177
186
  className: "text-destructive",
178
- children: k("pageReview.conflict")
179
- }) : /* @__PURE__ */ _(u, { error: j.error }),
180
- A.error && /* @__PURE__ */ _(f, {
181
- error: A.error,
182
- message: k("pageReview.loadFailed"),
183
- retry: A.reload
187
+ children: A("pageReview.conflict")
188
+ }) : /* @__PURE__ */ v(u, { error: M.error }),
189
+ j.error && /* @__PURE__ */ v(f, {
190
+ error: j.error,
191
+ message: A("pageReview.loadFailed"),
192
+ retry: j.reload
184
193
  }),
185
- M && /* @__PURE__ */ _("span", {
194
+ N && /* @__PURE__ */ v("span", {
186
195
  role: "status",
187
196
  className: "text-muted-foreground",
188
- children: k("pageReview.saved")
197
+ children: A("pageReview.saved")
189
198
  })
190
199
  ] });
191
200
  })();
192
- return /* @__PURE__ */ v(t, {
193
- appearance: O,
194
- className: y("grid gap-3 text-sm", D),
201
+ return /* @__PURE__ */ y(t, {
202
+ appearance: k,
203
+ className: b("grid gap-3 text-sm", O),
195
204
  "data-ckui": "page-review",
196
- "aria-busy": j.pending || A.loading,
197
- children: [/* @__PURE__ */ v("div", {
205
+ "aria-busy": M.pending || j.loading,
206
+ children: [/* @__PURE__ */ y("div", {
198
207
  className: "flex items-center justify-between gap-2",
199
- children: [/* @__PURE__ */ _("h4", {
208
+ children: [/* @__PURE__ */ v("h4", {
200
209
  className: "font-medium",
201
- children: k("pageReview.title")
202
- }), /* @__PURE__ */ _(n, {
210
+ children: A("pageReview.title")
211
+ }), /* @__PURE__ */ v(n, {
203
212
  type: "button",
204
213
  size: "xs",
205
214
  variant: "ghost",
206
- disabled: j.pending || A.loading,
207
- onClick: A.reload,
208
- children: k("pageReview.refresh")
215
+ disabled: M.pending || j.loading,
216
+ onClick: j.reload,
217
+ children: A("pageReview.refresh")
209
218
  })]
210
- }), L]
219
+ }), R]
211
220
  });
212
221
  }
213
222
  //#endregion
214
- export { C as PageReview };
223
+ export { w as PageReview };
215
224
 
216
225
  //# sourceMappingURL=page-review.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-review.js","names":[],"sources":["../../../src/components/admin/page-review.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useState } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { PageReviewEntry, PageReviewPatch } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { ContentKitErrorHandler } from \"../../react/context.js\";\nimport { usePageReview } from \"../../react/page-review.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { SortableList } from \"../sortable-list.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport { LoadFailed, Rows } from \"./shell.js\";\n\nexport interface PageReviewProps {\n content: string;\n version: string;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Only link the editor media URLs supplied by the server, never upload paths. */\nfunction imageURL(value?: string): string | undefined {\n if (!value) return;\n try {\n const url = new URL(value, globalThis.location?.href ?? \"http://localhost/\");\n if (url.protocol === \"http:\" || url.protocol === \"https:\") return value;\n } catch { /* An invalid or unsupported URL has no preview. */ }\n}\n\nconst words = (value: string) => value.replace(/[_-]+/g, \" \");\nconst originalName = (page: PageReviewEntry) => page.source_entry || page.source_name;\n\n/** Review automatic page choices without holding up ordinary publication. */\nexport function PageReview({ content, version, client, onError, className, appearance }: PageReviewProps) {\n const { t } = useMessages();\n const r = usePageReview(content, version, { client });\n const save = useAction(\"reader.save\", onError);\n const [saved, setSaved] = useState(false);\n const review = r.review;\n const chapterGuarded = review?.pages.some((page) => page.flags.includes(\"segmented_selection_preserved\")) ?? false;\n const apply = (patch: Omit<PageReviewPatch, \"revision\">) => {\n if (!review || chapterGuarded || save.pending || r.loading) return;\n setSaved(false);\n void save.run(() => r.update({ ...patch, revision: review.revision })).then((result) => result && setSaved(true));\n };\n const body = (() => {\n if (!review) return r.error ? <LoadFailed error={r.error} message={t(\"pageReview.loadFailed\")} retry={r.reload} /> : <Rows count={2} />;\n if (!review.pages.length) return <p className=\"text-muted-foreground\">{t(\"pageReview.empty\")}</p>;\n const disabled = chapterGuarded || save.pending || r.loading;\n return <>\n <p className=\"text-muted-foreground\">{t(\"pageReview.summary\", { selected: review.selected, total: review.pages.length, flagged: review.flagged })}</p>\n {chapterGuarded\n ? <p role=\"status\" className=\"rounded-md border border-border p-2 text-muted-foreground\">{t(\"pageReview.chapterGuard\")}</p>\n : <p className=\"text-xs text-muted-foreground\">{t(\"pageReview.help\")}</p>}\n <div className=\"max-h-[32rem] overflow-y-auto rounded-md border border-border p-2\">\n <SortableList items={review.pages} id={(page) => page.id} name={originalName} label={t(\"pageReview.order\")} disabled={disabled || review.pages.length < 2}\n onMove={(from, to) => {\n if (from === to) return;\n const order = review.pages.map((page) => page.id);\n const [moved] = order.splice(from, 1);\n order.splice(to, 0, moved!);\n apply({ order });\n }}>\n {(page, handle, index) => {\n const preview = imageURL(page.preview_url);\n const source = imageURL(page.source_url);\n return <div className=\"flex gap-2 rounded-md border border-border p-2\" data-ckui=\"page-review-row\" data-included={page.included} data-mode={page.mode}>\n <div className=\"flex shrink-0 flex-col items-center gap-1\">\n <span className=\"text-xs text-muted-foreground\">{index + 1}</span>\n {handle}\n {preview && <img src={preview} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"h-16 w-12 rounded object-contain\" />}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <p className=\"break-words\"><span className=\"text-muted-foreground\">{t(\"pageReview.original\")} </span>{originalName(page)}</p>\n <p className=\"break-words\"><span className=\"text-muted-foreground\">{t(\"pageReview.canonical\")} </span>{page.id}</p>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant={page.included ? \"outline\" : \"secondary\"}>{t(page.included ? \"pageReview.included\" : \"pageReview.excluded\")}</Badge>\n {!!page.flags.length && <Badge variant=\"outline\">{t(\"pageReview.flagged\")}</Badge>}\n {source && <a href={source} target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-xs underline\">{t(\"pageReview.source\")}</a>}\n </div>\n <p className=\"text-xs text-muted-foreground\">{words(page.decision.reason)} · {t(\"pageReview.confidence\", { confidence: words(page.decision.confidence) })}</p>\n {!!page.flags.length && <p className=\"text-xs text-muted-foreground\">{page.flags.map(words).join(\" · \")}</p>}\n <Select value={page.mode} onValueChange={(mode) => {\n if (mode !== page.mode && (mode === \"auto\" || mode === \"include\" || mode === \"exclude\")) apply({ pages: [{ id: page.id, mode }] });\n }}>\n <SelectTrigger size=\"sm\" className=\"w-44\" disabled={disabled} aria-label={t(\"pageReview.choice\", { name: originalName(page) })}>\n <SelectValue>{(mode: string) => t(mode === \"include\" ? \"pageReview.include\" : mode === \"exclude\" ? \"pageReview.exclude\" : \"pageReview.automatic\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"auto\">{t(\"pageReview.automatic\")}</SelectItem>\n <SelectItem value=\"include\">{t(\"pageReview.include\")}</SelectItem>\n <SelectItem value=\"exclude\">{t(\"pageReview.exclude\")}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>;\n }}\n </SortableList>\n </div>\n {save.error?.status === 409 ? <p role=\"alert\" className=\"text-destructive\">{t(\"pageReview.conflict\")}</p> : <ErrorLine error={save.error} />}\n {r.error && <LoadFailed error={r.error} message={t(\"pageReview.loadFailed\")} retry={r.reload} />}\n {saved && <span role=\"status\" className=\"text-muted-foreground\">{t(\"pageReview.saved\")}</span>}\n </>;\n })();\n return <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3 text-sm\", className)} data-ckui=\"page-review\" aria-busy={save.pending || r.loading}>\n <div className=\"flex items-center justify-between gap-2\">\n <h4 className=\"font-medium\">{t(\"pageReview.title\")}</h4>\n <Button type=\"button\" size=\"xs\" variant=\"ghost\" disabled={save.pending || r.loading} onClick={r.reload}>{t(\"pageReview.refresh\")}</Button>\n </div>\n {body}\n </ContentKitUiRoot>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"page-review.js","names":[],"sources":["../../../src/components/admin/page-review.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useState } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { PageReviewEntry, PageReviewPatch } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { ContentKitErrorHandler } from \"../../react/context.js\";\nimport { usePageReview } from \"../../react/page-review.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { SortableList } from \"../sortable-list.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport { useEditorSaveTask } from \"../content/editor-save.js\";\nimport { LoadFailed, Rows } from \"./shell.js\";\n\nexport interface PageReviewProps {\n content: string;\n version: string;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Only link the editor media URLs supplied by the server, never upload paths. */\nfunction imageURL(value?: string): string | undefined {\n if (!value) return;\n try {\n const url = new URL(value, globalThis.location?.href ?? \"http://localhost/\");\n if (url.protocol === \"http:\" || url.protocol === \"https:\") return value;\n } catch { /* An invalid or unsupported URL has no preview. */ }\n}\n\nconst words = (value: string) => value.replace(/[_-]+/g, \" \");\nconst originalName = (page: PageReviewEntry) => page.source_entry || page.source_name;\n\n/** Review automatic page choices without holding up ordinary publication. */\nexport function PageReview({ content, version, client, onError, className, appearance }: PageReviewProps) {\n const { t } = useMessages();\n const r = usePageReview(content, version, { client });\n const save = useAction(\"reader.save\", onError);\n const [saved, setSaved] = useState(false);\n useEditorSaveTask(`page-review:${version}`,{label:t(\"pageReview.title\"),dirty:false,pending:save.pending,valid:true,save:async()=>true});\n const review = r.review;\n const chapterGuarded = review?.pages.some((page) => page.flags.includes(\"segmented_selection_preserved\")) ?? false;\n const apply = (patch: Omit<PageReviewPatch, \"revision\">) => {\n if (!review || chapterGuarded || save.pending || r.loading) return;\n setSaved(false);\n void save.run(() => r.update({ ...patch, revision: review.revision })).then((result) => result && setSaved(true));\n };\n const body = (() => {\n if (!review) return r.error ? <LoadFailed error={r.error} message={t(\"pageReview.loadFailed\")} retry={r.reload} /> : <Rows count={2} />;\n if (!review.pages.length) return <p className=\"text-muted-foreground\">{t(\"pageReview.empty\")}</p>;\n const disabled = chapterGuarded || save.pending || r.loading;\n return <>\n <p className=\"text-muted-foreground\">{t(\"pageReview.summary\", { selected: review.selected, total: review.pages.length, flagged: review.flagged })}</p>\n {chapterGuarded\n ? <p role=\"status\" className=\"rounded-md border border-border p-2 text-muted-foreground\">{t(\"pageReview.chapterGuard\")}</p>\n : <p className=\"text-xs text-muted-foreground\">{t(\"pageReview.help\")}</p>}\n <div className=\"max-h-[32rem] overflow-y-auto rounded-md border border-border p-2\">\n <SortableList items={review.pages} id={(page) => page.id} name={originalName} label={t(\"pageReview.order\")} disabled={disabled || review.pages.length < 2}\n onMove={(from, to) => {\n if (from === to) return;\n const order = review.pages.map((page) => page.id);\n const [moved] = order.splice(from, 1);\n order.splice(to, 0, moved!);\n apply({ order });\n }}>\n {(page, handle, index) => {\n const preview = imageURL(page.preview_url);\n const source = imageURL(page.source_url);\n return <div className=\"flex gap-2 rounded-md border border-border p-2\" data-ckui=\"page-review-row\" data-included={page.included} data-mode={page.mode}>\n <div className=\"flex shrink-0 flex-col items-center gap-1\">\n <span className=\"text-xs text-muted-foreground\">{index + 1}</span>\n {handle}\n {preview && <img src={preview} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"h-16 w-12 rounded object-contain\" />}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <p className=\"break-words\"><span className=\"text-muted-foreground\">{t(\"pageReview.original\")} </span>{originalName(page)}</p>\n <p className=\"break-words\"><span className=\"text-muted-foreground\">{t(\"pageReview.canonical\")} </span>{page.id}</p>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant={page.included ? \"outline\" : \"secondary\"}>{t(page.included ? \"pageReview.included\" : \"pageReview.excluded\")}</Badge>\n {!!page.flags.length && <Badge variant=\"outline\">{t(\"pageReview.flagged\")}</Badge>}\n {source && <a href={source} target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-xs underline\">{t(\"pageReview.source\")}</a>}\n </div>\n <p className=\"text-xs text-muted-foreground\">{words(page.decision.reason)} · {t(\"pageReview.confidence\", { confidence: words(page.decision.confidence) })}</p>\n {!!page.flags.length && <p className=\"text-xs text-muted-foreground\">{page.flags.map(words).join(\" · \")}</p>}\n <Select value={page.mode} onValueChange={(mode) => {\n if (mode !== page.mode && (mode === \"auto\" || mode === \"include\" || mode === \"exclude\")) apply({ pages: [{ id: page.id, mode }] });\n }}>\n <SelectTrigger size=\"sm\" className=\"w-44\" disabled={disabled} aria-label={t(\"pageReview.choice\", { name: originalName(page) })}>\n <SelectValue>{(mode: string) => t(mode === \"include\" ? \"pageReview.include\" : mode === \"exclude\" ? \"pageReview.exclude\" : \"pageReview.automatic\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"auto\">{t(\"pageReview.automatic\")}</SelectItem>\n <SelectItem value=\"include\">{t(\"pageReview.include\")}</SelectItem>\n <SelectItem value=\"exclude\">{t(\"pageReview.exclude\")}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>;\n }}\n </SortableList>\n </div>\n {save.error?.status === 409 ? <p role=\"alert\" className=\"text-destructive\">{t(\"pageReview.conflict\")}</p> : <ErrorLine error={save.error} />}\n {r.error && <LoadFailed error={r.error} message={t(\"pageReview.loadFailed\")} retry={r.reload} />}\n {saved && <span role=\"status\" className=\"text-muted-foreground\">{t(\"pageReview.saved\")}</span>}\n </>;\n })();\n return <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3 text-sm\", className)} data-ckui=\"page-review\" aria-busy={save.pending || r.loading}>\n <div className=\"flex items-center justify-between gap-2\">\n <h4 className=\"font-medium\">{t(\"pageReview.title\")}</h4>\n <Button type=\"button\" size=\"xs\" variant=\"ghost\" disabled={save.pending || r.loading} onClick={r.reload}>{t(\"pageReview.refresh\")}</Button>\n </div>\n {body}\n </ContentKitUiRoot>;\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { ContentModel } from '../../client/generated/wire.js';
2
+ import { ContentEditorProps } from './content-manager.js';
3
+ /** A reader work, its files and associations are one editing session. */
4
+ export declare function ReaderContentEditor(p: ContentEditorProps & {
5
+ model: ContentModel;
6
+ }): import('react').JSX.Element;