@openrails/contentkit-ui 0.79.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -54
- package/dist/client/beacons.d.ts +53 -0
- package/dist/client/beacons.js +72 -0
- package/dist/client/beacons.js.map +1 -0
- package/dist/client/catalog.d.ts +2 -2
- package/dist/client/catalog.js +1 -1
- package/dist/client/catalog.js.map +1 -1
- package/dist/client/client.d.ts +24 -11
- package/dist/client/client.js +37 -29
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/comments.d.ts +12 -8
- package/dist/client/content/comments.js +33 -20
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.d.ts +24 -9
- package/dist/client/content/favorites.js +21 -22
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.d.ts +1 -1
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/reactions.d.ts +11 -6
- package/dist/client/content/reactions.js +23 -18
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/content/types.d.ts +40 -21
- package/dist/client/content/types.js +5 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +55 -0
- package/dist/client/contents.js +144 -0
- package/dist/client/contents.js.map +1 -0
- package/dist/client/discovery.d.ts +126 -0
- package/dist/client/discovery.js +125 -0
- package/dist/client/discovery.js.map +1 -0
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +12 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +1244 -370
- package/dist/client/generated/routes.js +1870 -554
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +960 -134
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/group-fields.d.ts +25 -0
- package/dist/client/group-fields.js +63 -0
- package/dist/client/group-fields.js.map +1 -0
- package/dist/client/groups.d.ts +58 -0
- package/dist/client/groups.js +119 -0
- package/dist/client/groups.js.map +1 -0
- package/dist/client/http.d.ts +24 -12
- package/dist/client/http.js +20 -24
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +11 -6
- package/dist/client/media/api.d.ts +1 -1
- package/dist/client/media/api.js +4 -2
- package/dist/client/media/api.js.map +1 -1
- package/dist/client/media/client.d.ts +8 -9
- package/dist/client/media/client.js +58 -71
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/ref.d.ts +30 -0
- package/dist/client/media/ref.js +10 -3
- package/dist/client/media/ref.js.map +1 -1
- package/dist/client/offers.d.ts +41 -0
- package/dist/client/offers.js +66 -0
- package/dist/client/offers.js.map +1 -0
- package/dist/client/route.d.ts +2 -0
- package/dist/client/route.js +2 -1
- package/dist/client/route.js.map +1 -1
- package/dist/client/versions.d.ts +20 -0
- package/dist/client/versions.js +53 -0
- package/dist/client/versions.js.map +1 -0
- package/dist/client.js +35 -30
- package/dist/components/admin/content-manager.d.ts +80 -0
- package/dist/components/admin/content-manager.js +591 -0
- package/dist/components/admin/content-manager.js.map +1 -0
- package/dist/components/admin/group-manager.d.ts +24 -0
- package/dist/components/admin/group-manager.js +244 -0
- package/dist/components/admin/group-manager.js.map +1 -0
- package/dist/components/admin/media-item-panel.js +1 -1
- package/dist/components/admin/media-item-panel.js.map +1 -1
- package/dist/components/content/card.d.ts +44 -0
- package/dist/components/content/card.js +285 -0
- package/dist/components/content/card.js.map +1 -0
- package/dist/components/content/editors.d.ts +40 -0
- package/dist/components/content/editors.js +764 -0
- package/dist/components/content/editors.js.map +1 -0
- package/dist/components/content/filters.d.ts +28 -0
- package/dist/components/content/filters.js +134 -0
- package/dist/components/content/filters.js.map +1 -0
- package/dist/components/content/grid.d.ts +62 -0
- package/dist/components/content/grid.js +233 -0
- package/dist/components/content/grid.js.map +1 -0
- package/dist/components/content/index.d.ts +8 -0
- package/dist/components/content/listing.d.ts +20 -0
- package/dist/components/content/listing.js +150 -0
- package/dist/components/content/listing.js.map +1 -0
- package/dist/components/content/offer-fields.d.ts +8 -0
- package/dist/components/content/offer-fields.js +127 -0
- package/dist/components/content/offer-fields.js.map +1 -0
- package/dist/components/content/paywall.d.ts +53 -0
- package/dist/components/content/paywall.js +221 -0
- package/dist/components/content/paywall.js.map +1 -0
- package/dist/components/content/search.d.ts +27 -0
- package/dist/components/content/search.js +164 -0
- package/dist/components/content/search.js.map +1 -0
- package/dist/components/groups/banner.d.ts +51 -0
- package/dist/components/groups/banner.js +194 -0
- package/dist/components/groups/banner.js.map +1 -0
- package/dist/components/groups/directory.d.ts +37 -0
- package/dist/components/groups/directory.js +117 -0
- package/dist/components/groups/directory.js.map +1 -0
- package/dist/components/groups/editor.d.ts +44 -0
- package/dist/components/groups/editor.js +860 -0
- package/dist/components/groups/editor.js.map +1 -0
- package/dist/components/groups/fields.d.ts +13 -0
- package/dist/components/groups/fields.js +68 -0
- package/dist/components/groups/fields.js.map +1 -0
- package/dist/components/groups/header.d.ts +25 -0
- package/dist/components/groups/header.js +131 -0
- package/dist/components/groups/header.js.map +1 -0
- package/dist/components/groups/index.d.ts +6 -0
- package/dist/components/groups/memberships.d.ts +55 -0
- package/dist/components/groups/memberships.js +527 -0
- package/dist/components/groups/memberships.js.map +1 -0
- package/dist/components/groups/parts.d.ts +49 -0
- package/dist/components/groups/parts.js +172 -0
- package/dist/components/groups/parts.js.map +1 -0
- package/dist/components/media-folder-editor.js +153 -152
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/social/comments.d.ts +7 -5
- package/dist/components/social/comments.js +6 -2
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +4 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/components/social/moderation.d.ts +1 -1
- package/dist/components/social/moderation.js +3 -3
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/editor/rich-text-editor.d.ts +4 -4
- package/dist/editor/rich-text-editor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +23 -9
- package/dist/locales/admin/de.js +5 -66
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +5 -66
- package/dist/locales/admin/en.js +19 -80
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +5 -66
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +5 -66
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +5 -66
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +5 -66
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/content/de.d.ts +2 -0
- package/dist/locales/content/de.js +98 -0
- package/dist/locales/content/de.js.map +1 -0
- package/dist/locales/content/en.d.ts +93 -0
- package/dist/locales/content/en.js +98 -0
- package/dist/locales/content/en.js.map +1 -0
- package/dist/locales/content/es.d.ts +2 -0
- package/dist/locales/content/es.js +98 -0
- package/dist/locales/content/es.js.map +1 -0
- package/dist/locales/content/ja.d.ts +2 -0
- package/dist/locales/content/ja.js +98 -0
- package/dist/locales/content/ja.js.map +1 -0
- package/dist/locales/content/ko.d.ts +2 -0
- package/dist/locales/content/ko.js +98 -0
- package/dist/locales/content/ko.js.map +1 -0
- package/dist/locales/content/zh.d.ts +2 -0
- package/dist/locales/content/zh.js +98 -0
- package/dist/locales/content/zh.js.map +1 -0
- package/dist/locales/de.js +11 -2
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.d.ts +2 -0
- package/dist/locales/discovery/de.js +68 -0
- package/dist/locales/discovery/de.js.map +1 -0
- package/dist/locales/discovery/en.d.ts +63 -0
- package/dist/locales/discovery/en.js +68 -0
- package/dist/locales/discovery/en.js.map +1 -0
- package/dist/locales/discovery/es.d.ts +2 -0
- package/dist/locales/discovery/es.js +68 -0
- package/dist/locales/discovery/es.js.map +1 -0
- package/dist/locales/discovery/ja.d.ts +2 -0
- package/dist/locales/discovery/ja.js +68 -0
- package/dist/locales/discovery/ja.js.map +1 -0
- package/dist/locales/discovery/ko.d.ts +2 -0
- package/dist/locales/discovery/ko.js +68 -0
- package/dist/locales/discovery/ko.js.map +1 -0
- package/dist/locales/discovery/zh.d.ts +2 -0
- package/dist/locales/discovery/zh.js +68 -0
- package/dist/locales/discovery/zh.js.map +1 -0
- package/dist/locales/en.d.ts +288 -67
- package/dist/locales/en.js +11 -2
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +11 -2
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.d.ts +2 -0
- package/dist/locales/groups/de.js +133 -0
- package/dist/locales/groups/de.js.map +1 -0
- package/dist/locales/groups/en.d.ts +128 -0
- package/dist/locales/groups/en.js +133 -0
- package/dist/locales/groups/en.js.map +1 -0
- package/dist/locales/groups/es.d.ts +2 -0
- package/dist/locales/groups/es.js +133 -0
- package/dist/locales/groups/es.js.map +1 -0
- package/dist/locales/groups/ja.d.ts +2 -0
- package/dist/locales/groups/ja.js +133 -0
- package/dist/locales/groups/ja.js.map +1 -0
- package/dist/locales/groups/ko.d.ts +2 -0
- package/dist/locales/groups/ko.js +133 -0
- package/dist/locales/groups/ko.js.map +1 -0
- package/dist/locales/groups/zh.d.ts +2 -0
- package/dist/locales/groups/zh.js +133 -0
- package/dist/locales/groups/zh.js.map +1 -0
- package/dist/locales/ja.js +11 -2
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +11 -2
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +2 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +2 -1
- package/dist/locales/social/en.js +2 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +2 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +2 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +2 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +2 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +11 -2
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/admin.d.ts +1 -1
- package/dist/react/admin.js +8 -6
- package/dist/react/admin.js.map +1 -1
- package/dist/react/comments.d.ts +9 -7
- package/dist/react/comments.js +42 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.d.ts +57 -0
- package/dist/react/contents.js +214 -0
- package/dist/react/contents.js.map +1 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/crop.js +45 -44
- package/dist/react/crop.js.map +1 -1
- package/dist/react/detail.d.ts +30 -0
- package/dist/react/detail.js +42 -0
- package/dist/react/detail.js.map +1 -0
- package/dist/react/discovery.d.ts +138 -0
- package/dist/react/discovery.js +299 -0
- package/dist/react/discovery.js.map +1 -0
- package/dist/react/engagement.d.ts +5 -5
- package/dist/react/engagement.js +55 -41
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.js +54 -53
- package/dist/react/folder.js.map +1 -1
- package/dist/react/groups.d.ts +115 -0
- package/dist/react/groups.js +229 -0
- package/dist/react/groups.js.map +1 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/public.d.ts +6 -5
- package/dist/react/public.js +19 -22
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +105 -106
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +2 -2
- package/dist/react/resources.js +69 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/store.js +41 -39
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.js +54 -53
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +2 -1
- package/dist/react/use-resource.js +1 -0
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react/versions.d.ts +57 -0
- package/dist/react/versions.js +94 -0
- package/dist/react/versions.js.map +1 -0
- package/dist/react/video.js +87 -86
- package/dist/react/video.js.map +1 -1
- package/dist/react.js +9 -5
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/client/content/posts.d.ts +0 -52
- package/dist/client/content/posts.js +0 -84
- package/dist/client/content/posts.js.map +0 -1
- package/dist/client/taxonomy.d.ts +0 -34
- package/dist/client/taxonomy.js +0 -84
- package/dist/client/taxonomy.js.map +0 -1
- package/dist/components/admin/post-manager.d.ts +0 -45
- package/dist/components/admin/post-manager.js +0 -602
- package/dist/components/admin/post-manager.js.map +0 -1
- package/dist/components/admin/taxonomy-manager.d.ts +0 -54
- package/dist/components/admin/taxonomy-manager.js +0 -976
- package/dist/components/admin/taxonomy-manager.js.map +0 -1
- package/dist/react/posts.d.ts +0 -61
- package/dist/react/posts.js +0 -183
- package/dist/react/posts.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"taxonomy-manager.js","names":[],"sources":["../../../src/components/admin/taxonomy-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 type { Assignment, AssignmentPage, ContentRef, Edge, MergeReport, Name, NodeDetail, NodePage, NodeRow } 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 { useErrorReporter } from \"../../react/context.js\";\nimport { usePublicImage } from \"../../react/public.js\";\nimport { keyOf, useContentScope, useResource } from \"../../react/use-resource.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } 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 { Tabs, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { CoverUpload } from \"../slot-upload.js\";\nimport { contentError, ErrorLine, RelativeTime, useAction } from \"../social/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 };\nexport type TaxonomyRelation = Edge[\"relation\"];\n\n/** One node kind the flow manages. */\nexport interface TaxonomyKind {\n /** The kind as the store registers it, e.g. \"tag\", \"series\". */\n kind: string;\n label: string;\n /** Edges a node of this kind may have, \"node is <relation> of a <to> node\": e.g. a character member_of a series. */\n relations?: readonly { relation: TaxonomyRelation; to: string; label: string; inverseLabel?: string }[];\n /** A cover in the host's media: the media kind whose items are node ids, its upload path (\"cover\") and public preset (\"cover\"). */\n cover?: { kind: string; path?: string; preset?: string };\n}\n\nexport interface TaxonomyManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The kinds, in tab order; the first is at basePath. */\n kinds: readonly TaxonomyKind[];\n /** Languages names are written in; the first resolves the list's names. */\n languages?: readonly Choice[];\n /** Counts of this content kind; default every kind. */\n contentKind?: string;\n /** Host-owned fields of a node (a creator's type, a tag's restricted flag), below its names. */\n renderFields?: (node: NodeDetail) => ReactNode;\n /** One assigned content, e.g. a link with its title. Default its kind and id. */\n renderContent?: (ref: ContentRef, assignment: Assignment) => ReactNode;\n pageSize?: number;\n}\n\n/** Whether the caller may manage the taxonomy (root:taxonomy:write). */\nexport function useTaxonomyManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"taxonomy\"> {\n return useStaffAccess([\"taxonomy\"] as const, o);\n}\n\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst SORTS = [\"name\", \"count\", \"created\", \"updated\", \"oldest\"] as const;\nconst STATES = [\"active\", \"deleted\", \"merged\"] as const;\ntype NodeState = (typeof STATES)[number];\n\n/** A slug from a name: lowercase words joined by dashes. */\nexport function slugify(name: string): string {\n return name\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .trim()\n .replace(/[\\s/]+/g, \"-\")\n .replace(/[^\\p{L}\\p{N}-]+/gu, \"\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 200);\n}\n\n/**\n * Staff: the taxonomy per kind. Each kind's nodes by name, state, language,\n * minimum count and order; a node's names and aliases per language, host\n * fields, relations (with a typeahead), cover, counts and assigned contents;\n * create, delete, restore and merge. Kinds are at basePath/{kind}, a node at\n * basePath/{kind}/{id}, filters in the query.\n */\nexport function TaxonomyManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: TaxonomyManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"taxonomy-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Taxonomy {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<TaxonomyManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\ntype Ctx = Inner & { loc: AdminLocation; kind: TaxonomyKind; lang: string };\n\nfunction Taxonomy({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useTaxonomyManagerAccess({ client: p.client });\n if (access.loading) return <Rows />;\n if (!access.allowed.taxonomy) return <NoAccess>{t(\"taxonomyManager.noAccess\")}</NoAccess>;\n if (!p.kinds.length) return null;\n const [k, sub] = loc.view.split(\"/\");\n const kind = p.kinds.find((x) => x.kind === k) ?? p.kinds[0]!;\n const ctx: Ctx = { ...p, loc, kind, lang: choices(p.languages)[0]?.value ?? \"en\" };\n return (\n <>\n {p.kinds.length > 1 && (\n <Tabs value={kind.kind} onValueChange={(v) => loc.go(String(v))}>\n <TabsList>\n {p.kinds.map((x) => (\n <TabsTrigger key={x.kind} value={x.kind}>\n {x.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )}\n {!sub ? <NodeList key={kind.kind} {...ctx} /> : sub === \"new\" ? <NodeCreate key={kind.kind} {...ctx} /> : <NodeView key={sub} {...ctx} id={sub} />}\n </>\n );\n}\n\nfunction NodeList({ loc, kind, client, languages: offered, contentKind, pageSize = 25, lang }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const query = loc.query.get(\"q\") ?? \"\";\n const stateParam = loc.query.get(\"state\") ?? \"\";\n const state: NodeState = (STATES as readonly string[]).includes(stateParam) ? (stateParam as NodeState) : \"active\";\n const language = loc.query.get(\"language\") || lang;\n const minCount = Math.max(0, Number(loc.query.get(\"min_count\")) || 0);\n const sortParam = loc.query.get(\"sort\") ?? \"\";\n const sort = (SORTS as readonly string[]).includes(sortParam) ? sortParam : \"name\";\n const page = pageOf(loc.query);\n const filters: { q?: string; state?: NodeState; language?: string; min_count?: number; sort?: string } = {\n q: query,\n state: state === \"active\" ? undefined : state,\n language: language === lang ? undefined : language,\n min_count: minCount || undefined,\n sort: sort === \"name\" ? undefined : sort,\n };\n const filter = (next: Partial<typeof filters>) => loc.go(kind.kind, { ...filters, ...next });\n const [text, setText] = useState(query);\n useEffect(() => setText(query), [query]);\n const { store, scope } = useContentScope(client);\n const offset = (page - 1) * pageSize;\n const r = useResource<NodePage>(store, keyOf(\"taxonomy-nodes\", scope, kind.kind, query, state, language, minCount, sort, contentKind, offset, pageSize), { type: \"taxonomy-nodes\" }, (signal) =>\n client.taxonomy.nodes(\n { kind: kind.kind, q: query || undefined, state: [state], language, min_count: minCount || undefined, sort, content_kind: contentKind, offset, limit: pageSize },\n signal,\n ),\n );\n const nodes = r.data?.nodes ?? [];\n const total = r.data?.total ?? 0;\n const loading = r.loading || !r.loaded;\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\">{kind.label}</h2>\n <Button size=\"sm\" onClick={() => loc.go(`${kind.kind}/new`)}>\n {t(\"taxonomyManager.new\")}\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(\"taxonomyManager.search\")}\n </Label>\n <Input id={`${ids}-q`} className=\"h-8\" type=\"search\" value={text} placeholder={t(\"taxonomyManager.search\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n <div className=\"flex flex-wrap gap-2\">\n <Select value={state} onValueChange={(v) => filter({ state: v === \"active\" ? undefined : (String(v) as NodeState) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"taxonomyManager.state\")}>\n <SelectValue>{(v: string) => t(`taxonomyManager.${v as NodeState}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {STATES.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`taxonomyManager.${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languages.length > 1 && (\n <Select value={language} onValueChange={(v) => filter({ language: v === lang ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"taxonomyManager.language\")}>\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 )}\n <Select value={sort} onValueChange={(v) => filter({ sort: v === \"name\" ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"taxonomyManager.sort\")}>\n <SelectValue>{(v: string) => t(`taxonomyManager.sort_${v as (typeof SORTS)[number]}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {SORTS.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`taxonomyManager.sort_${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <div className=\"flex items-center gap-1.5\">\n <Label htmlFor={`${ids}-min`} className=\"text-xs text-muted-foreground\">\n {t(\"taxonomyManager.minCount\")}\n </Label>\n <Input\n id={`${ids}-min`}\n className=\"h-8 w-20\"\n type=\"number\"\n min={0}\n value={minCount || \"\"}\n onChange={(e) => filter({ min_count: Number(e.target.value) || undefined })}\n />\n </div>\n </div>\n </div>\n {loading && !nodes.length ? (\n <Rows />\n ) : r.error && !nodes.length ? (\n <LoadFailed error={r.error} message={t(\"taxonomyManager.loadFailed\")} retry={r.reload} />\n ) : !nodes.length ? (\n <p className=\"text-muted-foreground\">{t(\"taxonomyManager.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={loading}>\n {nodes.map((n) => (\n <li key={n.taxonomy_id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"node-row\" data-state={n.state}>\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.kind}/${n.taxonomy_id}`)}>\n {n.name || n.slug}\n </button>\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n <span>{n.slug}</span>\n {n.state !== \"active\" && <Badge variant=\"outline\">{t(`taxonomyManager.${n.state}`)}</Badge>}\n <RelativeTime at={n.updated_at} />\n </div>\n </div>\n <span className=\"text-sm tabular-nums text-muted-foreground\" title={t(\"taxonomyManager.count\")}>\n {n.count}\n </span>\n </li>\n ))}\n </ul>\n )}\n {total > 0 && <p className=\"text-center text-xs text-muted-foreground\">{m.plural(\"taxonomyManager.total\", total)}</p>}\n <Pager page={page} hasNext={offset + nodes.length < total} disabled={loading} onPage={(n) => loc.go(kind.kind, { ...filters, page: n > 1 ? n : undefined })} />\n </div>\n );\n}\n\nfunction NodeCreate({ loc, kind, client, languages: offered, lang, onError }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const [names, setNames] = useState<Record<string, string>>({});\n const [slug, setSlug] = useState(\"\");\n const [slugEdited, setSlugEdited] = useState(false);\n const create = useAction(\"taxonomy.save\", onError);\n const first = names[lang] ?? \"\";\n useEffect(() => {\n if (!slugEdited) setSlug(slugify(first));\n }, [first, slugEdited]);\n const list = languages.length ? languages : [{ value: lang, label: lang }];\n const submit = async () => {\n const given: Name[] = list.filter((l) => names[l.value]?.trim()).map((l) => ({ language: l.value, kind: \"name\", name: names[l.value]!.trim(), source_revision: 0 }));\n const made = await create.run(() => client.taxonomy.createNodes([{ kind: kind.kind, slug: slug.trim(), names: given, source_revision: 0 }]));\n if (made?.[0]) loc.go(`${kind.kind}/${made[0].taxonomy_id}`, undefined, { replace: true });\n };\n return (\n <form\n className=\"grid gap-4\"\n data-ckui=\"node-create\"\n onSubmit={(e) => {\n e.preventDefault();\n void submit();\n }}\n >\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind.kind)}>\n {t(\"taxonomyManager.back\", { kind: kind.label })}\n </Button>\n <h2 className=\"text-base font-semibold\">{t(\"taxonomyManager.newTitle\", { kind: kind.label })}</h2>\n {list.map((l, i) => (\n <div key={l.value} className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-${l.value}`}>{t(\"taxonomyManager.nameIn\", { language: l.label })}</Label>\n <Input id={`${ids}-${l.value}`} required={i === 0} value={names[l.value] ?? \"\"} onChange={(e) => setNames((n) => ({ ...n, [l.value]: e.target.value }))} />\n </div>\n ))}\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-slug`}>{t(\"taxonomyManager.slug\")}</Label>\n <Input\n id={`${ids}-slug`}\n required\n value={slug}\n onChange={(e) => {\n setSlugEdited(true);\n setSlug(e.target.value);\n }}\n />\n </div>\n {create.error && <ErrorLine>{contentError(m, create.error)}</ErrorLine>}\n <Button type=\"submit\" className=\"justify-self-start\" disabled={create.pending || !first.trim() || !slug.trim()}>\n {create.pending ? t(\"common.saving\") : t(\"taxonomyManager.create\")}\n </Button>\n </form>\n );\n}\n\nfunction NodeView({ id, loc, kind, client, lang, ...p }: Ctx & { id: string }) {\n const m = useMessages();\n const { t } = m;\n const { store, scope } = useContentScope(client);\n const r = useResource<NodeDetail>(store, keyOf(\"taxonomy-node\", scope, id), { type: \"taxonomy-node\", id }, (signal) => client.taxonomy.node(id, signal));\n const node = r.data;\n const partnerIds = useMemo(() => [...new Set((node?.edges ?? []).flatMap((e) => [e.from_taxonomy_id, e.to_taxonomy_id]).filter((x) => x !== id))].sort(), [node?.edges, id]);\n const partners = useResource<NodePage>(\n store,\n partnerIds.length ? keyOf(\"taxonomy-partners\", scope, lang, ...partnerIds) : null,\n { type: \"taxonomy-partners\" },\n (signal) => client.taxonomy.nodes({ id: partnerIds, state: [\"active\", \"merged\", \"deleted\"], language: lang, limit: Math.min(500, partnerIds.length) }, signal),\n );\n const byId = useMemo(() => new Map((partners.data?.nodes ?? []).map((n) => [n.taxonomy_id, n])), [partners.data]);\n const save = useAction(\"taxonomy.save\", p.onError);\n const { confirm, dialog } = useConfirm();\n const back = (\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind.kind)}>\n {t(\"taxonomyManager.back\", { kind: kind.label })}\n </Button>\n );\n if (!node) {\n return (\n <>\n {back}\n {r.error ? <LoadFailed error={r.error} message={t(\"taxonomyManager.nodeLoadFailed\")} retry={r.reload} /> : <Skeleton className=\"h-40 w-full\" />}\n </>\n );\n }\n const canonical = node.names.find((n) => n.kind === \"name\" && n.language === lang) ?? node.names.find((n) => n.kind === \"name\");\n const title = canonical?.name ?? node.slug;\n const setState = async (state: \"active\" | \"deleted\") => {\n if (state === \"deleted\" && !(await confirm({ title: t(\"taxonomyManager.deleteTitle\", { name: title }), description: t(\"taxonomyManager.deleteBody\"), confirm: t(\"taxonomyManager.delete\") }))) return;\n await save.run(() => client.taxonomy.updateNode(id, { state }));\n };\n return (\n <div className=\"grid gap-5\" data-ckui=\"node-view\" data-state={node.state}>\n {back}\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold break-words\">{title}</h2>\n <Badge variant={node.state === \"active\" ? \"outline\" : \"secondary\"}>{t(`taxonomyManager.${node.state}`)}</Badge>\n <div className=\"ms-auto flex flex-wrap gap-2\">\n {node.state === \"active\" && <MergeButton node={node} title={title} kind={kind} client={client} loc={loc} lang={lang} onError={p.onError} />}\n {node.state === \"active\" ? (\n <Button size=\"sm\" variant=\"destructive\" disabled={save.pending} onClick={() => void setState(\"deleted\")}>\n {t(\"taxonomyManager.delete\")}\n </Button>\n ) : node.state === \"deleted\" ? (\n <Button size=\"sm\" variant=\"outline\" disabled={save.pending} onClick={() => void setState(\"active\")}>\n {t(\"taxonomyManager.restore\")}\n </Button>\n ) : null}\n </div>\n </div>\n {node.state === \"merged\" && <MergedInto node={node} byId={byId} kind={kind} loc={loc} />}\n <ErrorLine error={save.error} />\n <SlugField node={node} client={client} onError={p.onError} />\n <Names node={node} client={client} languages={p.languages} lang={lang} onError={p.onError} />\n {p.renderFields && <section data-ckui=\"node-fields\">{p.renderFields(node)}</section>}\n {kind.cover && <Cover node={node} cover={kind.cover} client={client} />}\n <Relations node={node} kind={kind} kinds={p.kinds} byId={byId} client={client} loc={loc} lang={lang} onError={p.onError} />\n <Counts node={node} />\n <Contents id={id} client={client} renderContent={p.renderContent} />\n {dialog}\n </div>\n );\n}\n\nfunction Section({ title, children, ckui }: { title: ReactNode; children: ReactNode; ckui: string }) {\n return (\n <section className=\"grid gap-2\" data-ckui={ckui}>\n <h3 className=\"font-medium\">{title}</h3>\n {children}\n </section>\n );\n}\n\nfunction SlugField({ node, client, onError }: { node: NodeDetail; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const ids = useId();\n const [slug, setSlug] = useState(node.slug);\n useEffect(() => setSlug(node.slug), [node.slug]);\n const save = useAction(\"taxonomy.save\", onError);\n return (\n <form\n className=\"flex flex-wrap items-end gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n void save.run(() => client.taxonomy.updateNode(node.taxonomy_id, { slug: slug.trim() }));\n }}\n >\n <div className=\"grid min-w-48 flex-1 gap-1.5\">\n <Label htmlFor={`${ids}-slug`}>{t(\"taxonomyManager.slug\")}</Label>\n <Input id={`${ids}-slug`} className=\"h-8\" value={slug} onChange={(e) => setSlug(e.target.value)} />\n </div>\n <Button type=\"submit\" size=\"sm\" variant=\"outline\" disabled={save.pending || !slug.trim() || slug.trim() === node.slug}>\n {t(\"common.save\")}\n </Button>\n <ErrorLine error={save.error} className=\"basis-full\" />\n </form>\n );\n}\n\nfunction Names({ node, client, languages: offered, lang, onError }: { node: NodeDetail; client: ContentKitClient; languages?: readonly Choice[]; lang: string; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const ids = useId();\n const languages = choices(offered);\n const list = languages.length ? languages : [{ value: lang, label: lang }];\n const [language, setLanguage] = useState(list[0]!.value);\n const [kind, setKind] = useState<Name[\"kind\"]>(\"alias\");\n const [text, setText] = useState(\"\");\n const save = useAction(\"taxonomy.save\", onError);\n const id = node.taxonomy_id;\n const label = (code: string) => list.find((l) => l.value === code)?.label ?? code;\n const sorted = [...node.names].sort((a, b) => a.language.localeCompare(b.language) || (a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === \"name\" ? -1 : 1));\n return (\n <Section title={t(\"taxonomyManager.names\")} ckui=\"node-names\">\n {sorted.length ? (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {sorted.map((n) => (\n <li key={`${n.language}:${n.name}`} className=\"flex flex-wrap items-center gap-2 p-2.5\" data-ckui=\"node-name\" data-kind={n.kind}>\n <Badge variant=\"outline\">{label(n.language)}</Badge>\n <span className=\"min-w-0 flex-1 break-words\">{n.name}</span>\n <span className=\"text-xs text-muted-foreground\">{t(n.kind === \"name\" ? \"taxonomyManager.canonical\" : \"taxonomyManager.alias\")}</span>\n {n.kind === \"alias\" && (\n <Button size=\"xs\" variant=\"ghost\" disabled={save.pending} onClick={() => void save.run(() => client.taxonomy.names(id, \"add\", [{ ...n, kind: \"name\" }]))}>\n {t(\"taxonomyManager.makeCanonical\")}\n </Button>\n )}\n <Button size=\"xs\" variant=\"ghost\" disabled={save.pending} aria-label={t(\"taxonomyManager.removeName\", { name: n.name })} onClick={() => void save.run(() => client.taxonomy.names(id, \"remove\", [n]))}>\n {t(\"common.remove\")}\n </Button>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"taxonomyManager.noNames\")}</p>\n )}\n <form\n className=\"flex flex-wrap items-end gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n const name = text.trim();\n if (!name) return;\n void save.run(() => client.taxonomy.names(id, \"add\", [{ language, kind, name, source_revision: 0 }])).then((d) => d && setText(\"\"));\n }}\n >\n {list.length > 1 && (\n <Select value={language} onValueChange={(v) => setLanguage(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={t(\"taxonomyManager.language\")}>\n <SelectValue>{(v: string) => label(v)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {list.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <Select value={kind} onValueChange={(v) => setKind(v as Name[\"kind\"])}>\n <SelectTrigger size=\"sm\" aria-label={t(\"taxonomyManager.nameKind\")}>\n <SelectValue>{(v: string) => t(v === \"name\" ? \"taxonomyManager.canonical\" : \"taxonomyManager.alias\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"alias\">{t(\"taxonomyManager.alias\")}</SelectItem>\n <SelectItem value=\"name\">{t(\"taxonomyManager.canonical\")}</SelectItem>\n </SelectContent>\n </Select>\n <Label htmlFor={`${ids}-name`} className=\"sr-only\">\n {t(\"taxonomyManager.newName\")}\n </Label>\n <Input id={`${ids}-name`} className=\"h-8 min-w-40 flex-1\" value={text} placeholder={t(\"taxonomyManager.newName\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\" disabled={save.pending || !text.trim()}>\n {t(\"taxonomyManager.addName\")}\n </Button>\n </form>\n <ErrorLine error={save.error} />\n </Section>\n );\n}\n\nfunction Cover({ node, cover, client }: { node: NodeDetail; cover: NonNullable<TaxonomyKind[\"cover\"]>; client: ContentKitClient }) {\n const { t } = useMessages();\n const item = { kind: cover.kind, id: node.taxonomy_id };\n const { image } = usePublicImage(cover.kind, node.taxonomy_id, cover.preset ?? \"cover\", { client });\n return (\n <section className=\"grid max-w-xl gap-2\" data-ckui=\"node-cover\">\n <CoverUpload item={item} path={cover.path ?? \"cover\"} image={image} client={client} removable label={t(\"taxonomyManager.cover\")} />\n </section>\n );\n}\n\nfunction MergedInto({ node, byId, kind, loc }: { node: NodeDetail; byId: Map<string, NodeRow>; kind: TaxonomyKind; loc: AdminLocation }) {\n const { t } = useMessages();\n const into = node.edges.find((e) => e.relation === \"alias_of\" && e.from_taxonomy_id === node.taxonomy_id)?.to_taxonomy_id;\n if (!into) return null;\n const target = byId.get(into);\n return (\n <p className=\"text-muted-foreground\" data-ckui=\"merged-into\">\n {t(\"taxonomyManager.mergedInto\")}{\" \"}\n <button type=\"button\" className=\"font-medium text-foreground hover:underline\" onClick={() => loc.go(`${target?.kind ?? kind.kind}/${into}`)}>\n {target?.name || target?.slug || into}\n </button>\n </p>\n );\n}\n\nfunction MergeButton({ node, title, kind, client, loc, lang, onError }: { node: NodeDetail; title: string; kind: TaxonomyKind; client: ContentKitClient; loc: AdminLocation; lang: string; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const [picking, setPicking] = useState(false);\n const merge = useAction(\"taxonomy.save\", onError);\n const { confirm, dialog } = useConfirm();\n const [report, setReport] = useState<MergeReport | null>(null);\n const pick = async (into: NodeRow) => {\n setPicking(false);\n const ok = await confirm({\n title: t(\"taxonomyManager.mergeTitle\", { from: title, into: into.name || into.slug }),\n description: t(\"taxonomyManager.mergeBody\"),\n confirm: t(\"taxonomyManager.merge\"),\n typed: t(\"taxonomyManager.mergeWord\"),\n });\n if (!ok) return;\n const r = await merge.run(() => client.taxonomy.merge(node.taxonomy_id, into.taxonomy_id));\n if (r) {\n setReport(r);\n loc.go(`${kind.kind}/${into.taxonomy_id}`);\n }\n };\n return (\n <>\n {picking ? (\n <div className=\"flex flex-wrap items-center gap-2\" data-ckui=\"merge-picker\">\n <NodePicker kind={kind.kind} exclude={node.taxonomy_id} client={client} lang={lang} label={t(\"taxonomyManager.mergeInto\")} onPick={(n) => void pick(n)} />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setPicking(false)}>\n {t(\"common.cancel\")}\n </Button>\n </div>\n ) : (\n <Button size=\"sm\" variant=\"outline\" disabled={merge.pending} onClick={() => setPicking(true)}>\n {t(\"taxonomyManager.merge\")}\n </Button>\n )}\n {merge.error && <ErrorLine error={merge.error} className=\"basis-full\" />}\n {report && (\n <p role=\"status\" className=\"sr-only\">\n {t(\"taxonomyManager.mergeDone\", { count: report.assignments_moved })}\n </p>\n )}\n {dialog}\n </>\n );\n}\n\n/** A typeahead over one kind's active nodes. */\nfunction NodePicker({ kind, exclude, client, lang, label, onPick }: { kind: string; exclude?: string; client: ContentKitClient; lang: string; label: string; onPick: (n: NodeRow) => void }) {\n const { t } = useMessages();\n const id = useId();\n const [text, setText] = useState(\"\");\n const [results, setResults] = useState<NodeRow[]>([]);\n const [active, setActive] = useState(0);\n const [error, setError] = useState<ContentKitError | null>(null);\n const seq = useRef(0);\n useEffect(() => {\n const q = text.trim();\n const n = ++seq.current;\n if (!q) {\n setResults([]);\n return;\n }\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n client.taxonomy.nodes({ kind, q, language: lang, limit: 8, sort: \"name\" }, ctl.signal).then(\n (page) => {\n if (n !== seq.current) return;\n setError(null);\n setResults(page.nodes.filter((x) => x.taxonomy_id !== exclude));\n setActive(0);\n },\n (e) => {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\" && n === seq.current) setError(err);\n },\n );\n }, 200);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [text, kind, lang, exclude, client]);\n const open = results.length > 0;\n return (\n <div className=\"relative grid min-w-56 flex-1 gap-1\" data-ckui=\"node-picker\">\n <Input\n role=\"combobox\"\n aria-label={label}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open ? `${id}-${active}` : undefined}\n autoComplete=\"off\"\n className=\"h-8\"\n value={text}\n placeholder={t(\"taxonomyManager.searchNodes\")}\n autoFocus\n onChange={(e) => setText(e.target.value)}\n onKeyDown={(e) => {\n if (!open) return;\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActive((a) => Math.min(results.length - 1, a + 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive((a) => Math.max(0, a - 1));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n onPick(results[active]!);\n } else if (e.key === \"Escape\") setResults([]);\n }}\n />\n {open && (\n <ul id={`${id}-list`} role=\"listbox\" aria-label={label} className=\"absolute top-full z-10 mt-1 grid w-full overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md\">\n {results.map((n, i) => (\n <li\n key={n.taxonomy_id}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === active}\n className={`cursor-pointer px-3 py-1.5 ${i === active ? \"bg-accent text-accent-foreground\" : \"\"}`}\n onMouseEnter={() => setActive(i)}\n onMouseDown={(e) => {\n e.preventDefault();\n onPick(n);\n }}\n >\n {n.name || n.slug} <span className=\"text-xs text-muted-foreground\">{n.slug}</span>\n </li>\n ))}\n </ul>\n )}\n {error && <ErrorLine error={error} />}\n </div>\n );\n}\n\nfunction Relations({\n node,\n kind,\n kinds,\n byId,\n client,\n loc,\n lang,\n onError,\n}: {\n node: NodeDetail;\n kind: TaxonomyKind;\n kinds: readonly TaxonomyKind[];\n byId: Map<string, NodeRow>;\n client: ContentKitClient;\n loc: AdminLocation;\n lang: string;\n onError?: Inner[\"onError\"];\n}) {\n const { t } = useMessages();\n const save = useAction(\"taxonomy.save\", onError);\n const [adding, setAdding] = useState<number | null>(null);\n const id = node.taxonomy_id;\n const out = node.edges.filter((e) => e.from_taxonomy_id === id && e.relation !== \"alias_of\");\n const incoming = node.edges.filter((e) => e.to_taxonomy_id === id && e.relation !== \"alias_of\");\n const own = kind.relations ?? [];\n // Relations other kinds declare toward this one, shown from this side.\n const inverse = kinds.flatMap((k) => (k.relations ?? []).filter((r) => r.to === kind.kind).map((r) => ({ ...r, from: k })));\n const link = (other: string, fallbackKind: string) => {\n const n = byId.get(other);\n return (\n <button type=\"button\" className=\"font-medium hover:underline\" onClick={() => loc.go(`${n?.kind ?? fallbackKind}/${other}`)}>\n {n?.name || n?.slug || other}\n </button>\n );\n };\n if (!own.length && !inverse.length && !out.length && !incoming.length) return null;\n return (\n <Section title={t(\"taxonomyManager.relations\")} ckui=\"node-relations\">\n {own.map((r, i) => {\n const edges = out.filter((e) => e.relation === r.relation && (byId.get(e.to_taxonomy_id)?.kind ?? r.to) === r.to);\n return (\n <div key={`${r.relation}:${r.to}`} className=\"grid gap-1.5\" data-ckui=\"relation\" data-relation={r.relation}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">{r.label}</span>\n {edges.map((e) => (\n <span key={e.to_taxonomy_id} className=\"inline-flex items-center gap-1 rounded-md border border-border px-2 py-0.5\" data-ckui=\"edge\">\n {link(e.to_taxonomy_id, r.to)}\n <button\n type=\"button\"\n className=\"text-muted-foreground hover:text-destructive\"\n aria-label={t(\"taxonomyManager.removeRelation\", { name: byId.get(e.to_taxonomy_id)?.name ?? e.to_taxonomy_id })}\n disabled={save.pending}\n onClick={() => void save.run(() => client.taxonomy.edges(\"remove\", [e]))}\n >\n ×\n </button>\n </span>\n ))}\n {adding !== i && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => setAdding(i)}>\n {t(\"taxonomyManager.addRelation\", { relation: r.label })}\n </Button>\n )}\n </div>\n {adding === i && (\n <div className=\"flex flex-wrap items-center gap-2\">\n <NodePicker\n kind={r.to}\n exclude={id}\n client={client}\n lang={lang}\n label={r.label}\n onPick={(n) => {\n setAdding(null);\n void save.run(() => client.taxonomy.edges(\"add\", [{ from_taxonomy_id: id, relation: r.relation, to_taxonomy_id: n.taxonomy_id, source_revision: 0 }]));\n }}\n />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setAdding(null)}>\n {t(\"common.cancel\")}\n </Button>\n </div>\n )}\n </div>\n );\n })}\n {inverse.map((r) => {\n const edges = incoming.filter((e) => e.relation === r.relation && (byId.get(e.from_taxonomy_id)?.kind ?? r.from.kind) === r.from.kind);\n if (!edges.length) return null;\n return (\n <div key={`in:${r.relation}:${r.from.kind}`} className=\"flex flex-wrap items-center gap-2\" data-ckui=\"relation\" data-relation={`inverse:${r.relation}`}>\n <span className=\"text-sm text-muted-foreground\">{r.inverseLabel ?? r.from.label}</span>\n {edges.map((e) => (\n <span key={e.from_taxonomy_id} className=\"rounded-md border border-border px-2 py-0.5\" data-ckui=\"edge\">\n {link(e.from_taxonomy_id, r.from.kind)}\n </span>\n ))}\n </div>\n );\n })}\n <ErrorLine error={save.error} />\n </Section>\n );\n}\n\nfunction Counts({ node }: { node: NodeDetail }) {\n const { t } = useMessages();\n return (\n <Section title={t(\"taxonomyManager.counts\")} ckui=\"node-counts\">\n {node.counts.length ? (\n <ul className=\"flex flex-wrap gap-2\">\n {node.counts.map((c) => (\n <li key={`${c.content_kind}:${c.language}`} className=\"rounded-md border border-border px-2 py-1 text-xs\">\n {c.content_kind} · {c.language}: <span className=\"tabular-nums font-medium\">{c.count}</span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"taxonomyManager.noCounts\")}</p>\n )}\n </Section>\n );\n}\n\nfunction Contents({ id, client, renderContent }: { id: string; client: ContentKitClient; renderContent?: Inner[\"renderContent\"] }) {\n const { t } = useMessages();\n const report = useErrorReporter();\n const [pages, setPages] = useState<AssignmentPage[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<ContentKitError | null>(null);\n const load = async (cursor?: string) => {\n setLoading(true);\n setError(null);\n try {\n const page = await client.taxonomy.nodeAssignments(id, { cursor, limit: 25 });\n setPages((ps) => (cursor ? [...ps, page] : [page]));\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"taxonomy.load\");\n } finally {\n setLoading(false);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => void load(), [id, client]);\n const items = pages.flatMap((p) => p.assignments);\n const next = pages.at(-1)?.next_cursor;\n return (\n <Section title={t(\"taxonomyManager.contents\")} ckui=\"node-contents\">\n {loading && !items.length ? (\n <Rows count={2} />\n ) : error && !items.length ? (\n <LoadFailed error={error} message={t(\"taxonomyManager.contentsLoadFailed\")} retry={() => void load()} />\n ) : !items.length ? (\n <p className=\"text-muted-foreground\">{t(\"taxonomyManager.noContents\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {items.map((a) => (\n <li\n key={`${a.content_kind}/${a.content_id}/${a.content_version_id ?? \"\"}/${a.relation}`}\n className=\"flex flex-wrap items-center gap-2 p-2.5\"\n data-ckui=\"node-content\"\n data-state={a.state}\n >\n <span className=\"min-w-0 flex-1 break-words\">\n {renderContent ? renderContent(a, a) : `${a.content_kind}/${a.content_id}${a.content_version_id ? ` (${a.content_version_id})` : \"\"}`}\n </span>\n <span className=\"text-xs text-muted-foreground\">{a.relation}</span>\n {a.state === \"proposed\" && <Badge variant=\"secondary\">{t(\"taxonomyManager.proposed\")}</Badge>}\n </li>\n ))}\n </ul>\n )}\n {next && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={loading} onClick={() => void load(next)}>\n {t(\"moderation.loadMore\")}\n </Button>\n )}\n </Section>\n );\n}\n"],"mappings":"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"}
|
package/dist/react/posts.d.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { ContentKitClient } from '../client/client.js';
|
|
2
|
-
import { ContentKitError } from '../client/errors.js';
|
|
3
|
-
import { Sort } from '../client/content/types.js';
|
|
4
|
-
import { Post, PostInput } from '../client/generated/wire.js';
|
|
5
|
-
import { UseList } from './use-resource.js';
|
|
6
|
-
export interface PostFilter {
|
|
7
|
-
language?: string;
|
|
8
|
-
sort?: Sort;
|
|
9
|
-
/** Staff: every post, drafts and scheduled ones included (root:posts:write). */
|
|
10
|
-
admin?: boolean;
|
|
11
|
-
/** With admin: true only drafts, false no drafts. */
|
|
12
|
-
draft?: boolean;
|
|
13
|
-
/** With admin: the deleted posts instead of the live ones. */
|
|
14
|
-
deleted?: boolean;
|
|
15
|
-
/** With admin: only posts whose title, excerpt or body contains this text. */
|
|
16
|
-
q?: string;
|
|
17
|
-
pageSize?: number;
|
|
18
|
-
}
|
|
19
|
-
/** Posts, a page at a time: the published ones, or every one for staff. */
|
|
20
|
-
export declare function usePosts(f?: PostFilter, o?: {
|
|
21
|
-
client?: ContentKitClient;
|
|
22
|
-
}): UseList<Post>;
|
|
23
|
-
export interface UsePost {
|
|
24
|
-
/** null before create (or while loading). */
|
|
25
|
-
post: Post | null;
|
|
26
|
-
loading: boolean;
|
|
27
|
-
error?: ContentKitError;
|
|
28
|
-
/** Writes in flight. */
|
|
29
|
-
saving: boolean;
|
|
30
|
-
/** Creates the post; the hook then edits it. */
|
|
31
|
-
create: (input: PostInput) => Promise<Post>;
|
|
32
|
-
/** Updates the given fields; a body from the editor is stored with image references (storedBody). */
|
|
33
|
-
update: (patch: PostInput) => Promise<Post>;
|
|
34
|
-
remove: () => Promise<void>;
|
|
35
|
-
/** Restores the deleted post (root:posts:write). */
|
|
36
|
-
restore: () => Promise<Post>;
|
|
37
|
-
/** Uploads the cover (null clears it); resolves with a URL that shows it now. */
|
|
38
|
-
setCover: (file: Blob | null) => Promise<string | null>;
|
|
39
|
-
/** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */
|
|
40
|
-
uploadImage: (file: Blob) => Promise<string>;
|
|
41
|
-
/**
|
|
42
|
-
* A URL that shows one of the post's images now, by name ("i-{uuid}") or
|
|
43
|
-
* reference: a fresh upload from its file, else its public file
|
|
44
|
-
* (`post.images`), else, while it has none (an unpublished post's), its
|
|
45
|
-
* signed editor view from an editor read. Anything else is returned as is.
|
|
46
|
-
*/
|
|
47
|
-
imageSrc: (image: string | null | undefined) => string | undefined;
|
|
48
|
-
/** The cover through imageSrc. */
|
|
49
|
-
coverSrc: string | undefined;
|
|
50
|
-
/** post.body with its images through imageSrc: what a rich-text editor shows. */
|
|
51
|
-
editorBody: string | undefined;
|
|
52
|
-
/** HTML from the editor with every image URL this hook showed turned back into the image's reference. */
|
|
53
|
-
storedBody: (html: string) => string;
|
|
54
|
-
}
|
|
55
|
-
/** Whether readers see the post, and so its images' public files exist. */
|
|
56
|
-
export declare function isPublished(post: Post, now?: number): boolean;
|
|
57
|
-
/** One post: read, and for staff (root:posts:write) create, update, delete, restore, cover and body images. null creates a new post. */
|
|
58
|
-
export declare function usePost(id: string | null | undefined, o?: {
|
|
59
|
-
initial?: Post;
|
|
60
|
-
client?: ContentKitClient;
|
|
61
|
-
}): UsePost;
|
package/dist/react/posts.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { useGuarded as e } from "./context.js";
|
|
2
|
-
import { useMediaRead as t } from "./read.js";
|
|
3
|
-
import { keyOf as n, offsetPages as r, useContentScope as i, useList as a, useResource as o } from "./use-resource.js";
|
|
4
|
-
import { IMAGE_SCHEME as s, imageRef as c, imageRefs as l } from "../client/content/posts.js";
|
|
5
|
-
import { useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
6
|
-
//#region src/react/posts.ts
|
|
7
|
-
function h(e = {}, t = {}) {
|
|
8
|
-
let { client: o, store: s, scope: c } = i(t.client), l = e.pageSize ?? 20;
|
|
9
|
-
return a(s, n("posts", c, !!e.admin, e.language, e.sort, e.draft, e.deleted, e.q, l), {
|
|
10
|
-
type: "posts",
|
|
11
|
-
scope: e.admin && e.deleted ? "deleted" : void 0
|
|
12
|
-
}, r(l, (t, n) => e.admin ? o.posts.adminList({
|
|
13
|
-
...t,
|
|
14
|
-
language: e.language,
|
|
15
|
-
draft: e.draft,
|
|
16
|
-
deleted: e.deleted,
|
|
17
|
-
q: e.q
|
|
18
|
-
}, n) : o.posts.list({
|
|
19
|
-
...t,
|
|
20
|
-
language: e.language,
|
|
21
|
-
sort: e.sort
|
|
22
|
-
}, n)));
|
|
23
|
-
}
|
|
24
|
-
var g = (e) => [...new Set([...(e ?? "").matchAll(l())].map((e) => e[1]))], _ = (e) => {
|
|
25
|
-
let t = e.slice(e.lastIndexOf("/") + 1), n = t.lastIndexOf(".");
|
|
26
|
-
return n > 0 ? t.slice(0, n) : t;
|
|
27
|
-
};
|
|
28
|
-
function v(e, t = Date.now()) {
|
|
29
|
-
return !e.is_draft && !e.moderation && (!e.live_at || Date.parse(e.live_at) <= t);
|
|
30
|
-
}
|
|
31
|
-
function y(r, a = {}) {
|
|
32
|
-
let { client: h, store: v, scope: y } = i(a.client), [b, x] = m(null), S = r ?? b, C = S ? n("post", y, S) : null, w = o(v, C, {
|
|
33
|
-
type: "post",
|
|
34
|
-
id: S ?? void 0
|
|
35
|
-
}, (e) => h.posts.get(S, e), { initial: a.initial }), T = w.data ?? null, [E, D] = m(0), O = e(), k = u(async (e) => {
|
|
36
|
-
D((e) => e + 1);
|
|
37
|
-
try {
|
|
38
|
-
return await O(e);
|
|
39
|
-
} finally {
|
|
40
|
-
D((e) => e - 1);
|
|
41
|
-
}
|
|
42
|
-
}, [O]), A = u(() => {
|
|
43
|
-
if (!S) throw Error("contentkit-ui: create the post first");
|
|
44
|
-
return S;
|
|
45
|
-
}, [S]), [j, M] = m(/* @__PURE__ */ new Map()), N = p([]);
|
|
46
|
-
d(() => {
|
|
47
|
-
let e = N.current;
|
|
48
|
-
return () => {
|
|
49
|
-
for (let t of e) URL.revokeObjectURL(t);
|
|
50
|
-
};
|
|
51
|
-
}, []);
|
|
52
|
-
let P = f(() => new Map(Object.entries(T?.images ?? {})), [T?.images]), F = f(() => {
|
|
53
|
-
let e = g(T?.body);
|
|
54
|
-
return T?.cover && !T.cover_url && !e.includes(T.cover) && e.push(T.cover), e.filter((e) => !j.has(e));
|
|
55
|
-
}, [
|
|
56
|
-
T?.body,
|
|
57
|
-
T?.cover,
|
|
58
|
-
T?.cover_url,
|
|
59
|
-
j
|
|
60
|
-
]), I = f(() => h.media.postRef(S ?? "none"), [h, S]), L = t(I, {
|
|
61
|
-
editor: !0,
|
|
62
|
-
read: F.length ? void 0 : null,
|
|
63
|
-
client: h
|
|
64
|
-
}), R = f(() => {
|
|
65
|
-
let e = /* @__PURE__ */ new Map();
|
|
66
|
-
for (let t of L.read?.files ?? []) t.upload && t.editor_url && e.set(_(t.path), t.editor_url);
|
|
67
|
-
return e;
|
|
68
|
-
}, [L.read]), z = (e) => !!L.read?.files.some((t) => t.failed && _(t.path) === e), B = !!L.read && F.some((e) => !R.has(e) && !z(e)), V = p(0), { reload: H } = L;
|
|
69
|
-
d(() => {
|
|
70
|
-
if (!B) {
|
|
71
|
-
V.current = 0;
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (V.current >= 6) return;
|
|
75
|
-
let e = setTimeout(() => {
|
|
76
|
-
V.current++, H();
|
|
77
|
-
}, 1e3 * 2 ** V.current);
|
|
78
|
-
return () => clearTimeout(e);
|
|
79
|
-
}, [
|
|
80
|
-
B,
|
|
81
|
-
L.read,
|
|
82
|
-
H
|
|
83
|
-
]);
|
|
84
|
-
let U = p(/* @__PURE__ */ new Map());
|
|
85
|
-
d(() => {
|
|
86
|
-
for (let e of [
|
|
87
|
-
j,
|
|
88
|
-
P,
|
|
89
|
-
R
|
|
90
|
-
]) for (let [t, n] of e) U.current.set(n, t);
|
|
91
|
-
}, [
|
|
92
|
-
j,
|
|
93
|
-
P,
|
|
94
|
-
R
|
|
95
|
-
]);
|
|
96
|
-
let W = u((e) => {
|
|
97
|
-
if (!e) return;
|
|
98
|
-
let t = e.startsWith("contentkit:") ? e.slice(s.length) : e;
|
|
99
|
-
return j.get(t) ?? P.get(t) ?? R.get(t) ?? e;
|
|
100
|
-
}, [
|
|
101
|
-
j,
|
|
102
|
-
P,
|
|
103
|
-
R
|
|
104
|
-
]), G = f(() => T?.body.replace(l(), (e, t) => j.get(t) ?? R.get(t) ?? e), [
|
|
105
|
-
T?.body,
|
|
106
|
-
j,
|
|
107
|
-
R
|
|
108
|
-
]), K = u((e) => {
|
|
109
|
-
let t = new Map(U.current);
|
|
110
|
-
for (let e of [
|
|
111
|
-
j,
|
|
112
|
-
P,
|
|
113
|
-
R
|
|
114
|
-
]) for (let [n, r] of e) t.set(r, n);
|
|
115
|
-
let n = e;
|
|
116
|
-
for (let [e, r] of t) n = n.split(e).join(c(r)).split(e.replaceAll("&", "&")).join(c(r));
|
|
117
|
-
return n;
|
|
118
|
-
}, [
|
|
119
|
-
j,
|
|
120
|
-
P,
|
|
121
|
-
R
|
|
122
|
-
]), q = u((e, t) => {
|
|
123
|
-
if (typeof URL.createObjectURL != "function") return;
|
|
124
|
-
let n = URL.createObjectURL(t);
|
|
125
|
-
return N.current.push(n), M((t) => new Map(t).set(e, n)), n;
|
|
126
|
-
}, []), J = u((e) => k(async () => {
|
|
127
|
-
let t = await h.posts.create(e);
|
|
128
|
-
return x(t.id), t;
|
|
129
|
-
}), [h, k]), Y = u((e) => k(() => h.posts.update(A(), e.body === void 0 ? e : {
|
|
130
|
-
...e,
|
|
131
|
-
body: K(e.body)
|
|
132
|
-
})), [
|
|
133
|
-
h,
|
|
134
|
-
k,
|
|
135
|
-
A,
|
|
136
|
-
K
|
|
137
|
-
]), X = u(() => k(() => h.posts.delete(A())), [
|
|
138
|
-
h,
|
|
139
|
-
k,
|
|
140
|
-
A
|
|
141
|
-
]), Z = u(() => k(() => h.posts.restore(A())), [
|
|
142
|
-
h,
|
|
143
|
-
k,
|
|
144
|
-
A
|
|
145
|
-
]), Q = u((e) => k(async () => {
|
|
146
|
-
if (!e) return h.posts.setCover(A(), null);
|
|
147
|
-
let t = await h.media.uploadNamed(h.media.postRef(A()), e), n = await h.posts.setCover(A(), t.name), r = q(t.name, e) ?? n;
|
|
148
|
-
return r && U.current.set(r, t.name), r;
|
|
149
|
-
}), [
|
|
150
|
-
h,
|
|
151
|
-
k,
|
|
152
|
-
A,
|
|
153
|
-
q
|
|
154
|
-
]), $ = u((e) => k(async () => {
|
|
155
|
-
let t = await h.media.uploadInline(A(), e), n = q(t.name, e) ?? t.url ?? t.ref;
|
|
156
|
-
return U.current.set(n, t.name), n;
|
|
157
|
-
}), [
|
|
158
|
-
h,
|
|
159
|
-
k,
|
|
160
|
-
A,
|
|
161
|
-
q
|
|
162
|
-
]);
|
|
163
|
-
return {
|
|
164
|
-
post: T,
|
|
165
|
-
loading: !!C && (w.loading || !w.loaded && a.initial === void 0),
|
|
166
|
-
error: w.error,
|
|
167
|
-
saving: E > 0,
|
|
168
|
-
create: J,
|
|
169
|
-
update: Y,
|
|
170
|
-
remove: X,
|
|
171
|
-
restore: Z,
|
|
172
|
-
setCover: Q,
|
|
173
|
-
uploadImage: $,
|
|
174
|
-
imageSrc: W,
|
|
175
|
-
coverSrc: W(T?.cover),
|
|
176
|
-
editorBody: G,
|
|
177
|
-
storedBody: K
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
//#endregion
|
|
181
|
-
export { v as isPublished, y as usePost, h as usePosts };
|
|
182
|
-
|
|
183
|
-
//# sourceMappingURL=posts.js.map
|
package/dist/react/posts.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"posts.js","names":[],"sources":["../../src/react/posts.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Sort } from \"../client/content/types.js\";\nimport type { Post, PostInput } from \"../client/generated/wire.js\";\nimport { IMAGE_SCHEME, imageRef, imageRefs } from \"../client/content/posts.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface PostFilter {\n language?: string;\n sort?: Sort;\n /** Staff: every post, drafts and scheduled ones included (root:posts:write). */\n admin?: boolean;\n /** With admin: true only drafts, false no drafts. */\n draft?: boolean;\n /** With admin: the deleted posts instead of the live ones. */\n deleted?: boolean;\n /** With admin: only posts whose title, excerpt or body contains this text. */\n q?: string;\n pageSize?: number;\n}\n\n/** Posts, a page at a time: the published ones, or every one for staff. */\nexport function usePosts(f: PostFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Post> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n return useList(\n store,\n keyOf(\"posts\", scope, !!f.admin, f.language, f.sort, f.draft, f.deleted, f.q, size),\n { type: \"posts\", scope: f.admin && f.deleted ? \"deleted\" : undefined },\n offsetPages(size, (q, signal) =>\n f.admin\n ? client.posts.adminList({ ...q, language: f.language, draft: f.draft, deleted: f.deleted, q: f.q }, signal)\n : client.posts.list({ ...q, language: f.language, sort: f.sort }, signal),\n ),\n );\n}\n\nexport interface UsePost {\n /** null before create (or while loading). */\n post: Post | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the post; the hook then edits it. */\n create: (input: PostInput) => Promise<Post>;\n /** Updates the given fields; a body from the editor is stored with image references (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (root:posts:write). */\n restore: () => Promise<Post>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the post's images now, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`post.images`), else, while it has none (an unpublished post's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: string | undefined;\n /** post.body with its images through imageSrc: what a rich-text editor shows. */\n editorBody: string | undefined;\n /** HTML from the editor with every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body still references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\n\n/** Whether readers see the post, and so its images' public files exist. */\nexport function isPublished(post: Post, now = Date.now()): boolean {\n return !post.is_draft && !post.moderation && (!post.live_at || Date.parse(post.live_at) <= now);\n}\n\n/** One post: read, and for staff (root:posts:write) create, update, delete, restore, cover and body images. null creates a new post. */\nexport function usePost(id: string | null | undefined, o: { initial?: Post; client?: ContentKitClient } = {}): UsePost {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"post\", scope, pid) : null;\n const r = useResource<Post | null>(store, key, { type: \"post\", id: pid ?? undefined }, (s) => client.posts.get(pid!, s), { initial: o.initial });\n const post = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const guarded = useGuarded();\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await guarded(fn);\n } finally {\n setInflight((n) => n - 1);\n }\n }, [guarded]);\n const need = useCallback(() => {\n if (!pid) throw new Error(\"contentkit-ui: create the post first\");\n return pid;\n }, [pid]);\n\n // Images: fresh uploads show from their file, published ones from post.images,\n // the rest (an unpublished post's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(post?.images ?? {})), [post?.images]);\n const pending = useMemo(() => {\n const names = refNames(post?.body);\n if (post?.cover && !post.cover_url && !names.includes(post.cover)) names.push(post.cover);\n return names.filter((n) => !previews.has(n));\n }, [post?.body, post?.cover, post?.cover_url, previews]);\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: pending.length ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => !!read.read?.files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = !!read.read && pending.some((n) => !views.has(n) && !failed(n));\n const tries = useRef(0);\n const { reload } = read;\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, read.read, reload]);\n\n // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageRefs(), (ref, n: string) => previews.get(n) ?? views.get(n) ?? ref), [post?.body, previews, views]);\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\n }, []);\n\n const create = useCallback(\n (input: PostInput) =>\n run(async () => {\n const post = await client.posts.create(input);\n setCreated(post.id);\n return post;\n }),\n [client, run],\n );\n const update = useCallback(\n (patch: PostInput) => run(() => client.posts.update(need(), patch.body === undefined ? patch : { ...patch, body: storedBody(patch.body) })),\n [client, run, need, storedBody],\n );\n const remove = useCallback(() => run(() => client.posts.delete(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.posts.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) return client.posts.setCover(need(), null);\n const up = await client.media.uploadNamed(client.media.postRef(need()), file);\n const cover = await client.posts.setCover(need(), up.name);\n const url = preview(up.name, file) ?? cover;\n if (url) shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline(need(), file);\n const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, preview],\n );\n return {\n post,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n coverSrc: imageSrc(post?.cover),\n editorBody,\n storedBody,\n };\n}\n"],"mappings":"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"}
|