@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.
Files changed (295) hide show
  1. package/README.md +82 -54
  2. package/dist/client/beacons.d.ts +53 -0
  3. package/dist/client/beacons.js +72 -0
  4. package/dist/client/beacons.js.map +1 -0
  5. package/dist/client/catalog.d.ts +2 -2
  6. package/dist/client/catalog.js +1 -1
  7. package/dist/client/catalog.js.map +1 -1
  8. package/dist/client/client.d.ts +24 -11
  9. package/dist/client/client.js +37 -29
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/content/comments.d.ts +12 -8
  12. package/dist/client/content/comments.js +33 -20
  13. package/dist/client/content/comments.js.map +1 -1
  14. package/dist/client/content/favorites.d.ts +24 -9
  15. package/dist/client/content/favorites.js +21 -22
  16. package/dist/client/content/favorites.js.map +1 -1
  17. package/dist/client/content/moderation.d.ts +1 -1
  18. package/dist/client/content/moderation.js.map +1 -1
  19. package/dist/client/content/reactions.d.ts +11 -6
  20. package/dist/client/content/reactions.js +23 -18
  21. package/dist/client/content/reactions.js.map +1 -1
  22. package/dist/client/content/types.d.ts +40 -21
  23. package/dist/client/content/types.js +5 -2
  24. package/dist/client/content/types.js.map +1 -1
  25. package/dist/client/contents.d.ts +55 -0
  26. package/dist/client/contents.js +144 -0
  27. package/dist/client/contents.js.map +1 -0
  28. package/dist/client/discovery.d.ts +126 -0
  29. package/dist/client/discovery.js +125 -0
  30. package/dist/client/discovery.js.map +1 -0
  31. package/dist/client/generated/error-codes.d.ts +1 -1
  32. package/dist/client/generated/error-codes.js +12 -0
  33. package/dist/client/generated/error-codes.js.map +1 -1
  34. package/dist/client/generated/routes.d.ts +1244 -370
  35. package/dist/client/generated/routes.js +1870 -554
  36. package/dist/client/generated/routes.js.map +1 -1
  37. package/dist/client/generated/wire.d.ts +960 -134
  38. package/dist/client/generated/wire.js.map +1 -1
  39. package/dist/client/group-fields.d.ts +25 -0
  40. package/dist/client/group-fields.js +63 -0
  41. package/dist/client/group-fields.js.map +1 -0
  42. package/dist/client/groups.d.ts +58 -0
  43. package/dist/client/groups.js +119 -0
  44. package/dist/client/groups.js.map +1 -0
  45. package/dist/client/http.d.ts +24 -12
  46. package/dist/client/http.js +20 -24
  47. package/dist/client/http.js.map +1 -1
  48. package/dist/client/index.d.ts +11 -6
  49. package/dist/client/media/api.d.ts +1 -1
  50. package/dist/client/media/api.js +4 -2
  51. package/dist/client/media/api.js.map +1 -1
  52. package/dist/client/media/client.d.ts +8 -9
  53. package/dist/client/media/client.js +58 -71
  54. package/dist/client/media/client.js.map +1 -1
  55. package/dist/client/media/ref.d.ts +30 -0
  56. package/dist/client/media/ref.js +10 -3
  57. package/dist/client/media/ref.js.map +1 -1
  58. package/dist/client/offers.d.ts +41 -0
  59. package/dist/client/offers.js +66 -0
  60. package/dist/client/offers.js.map +1 -0
  61. package/dist/client/route.d.ts +2 -0
  62. package/dist/client/route.js +2 -1
  63. package/dist/client/route.js.map +1 -1
  64. package/dist/client/versions.d.ts +20 -0
  65. package/dist/client/versions.js +53 -0
  66. package/dist/client/versions.js.map +1 -0
  67. package/dist/client.js +35 -30
  68. package/dist/components/admin/content-manager.d.ts +80 -0
  69. package/dist/components/admin/content-manager.js +591 -0
  70. package/dist/components/admin/content-manager.js.map +1 -0
  71. package/dist/components/admin/group-manager.d.ts +24 -0
  72. package/dist/components/admin/group-manager.js +244 -0
  73. package/dist/components/admin/group-manager.js.map +1 -0
  74. package/dist/components/admin/media-item-panel.js +1 -1
  75. package/dist/components/admin/media-item-panel.js.map +1 -1
  76. package/dist/components/content/card.d.ts +44 -0
  77. package/dist/components/content/card.js +285 -0
  78. package/dist/components/content/card.js.map +1 -0
  79. package/dist/components/content/editors.d.ts +40 -0
  80. package/dist/components/content/editors.js +764 -0
  81. package/dist/components/content/editors.js.map +1 -0
  82. package/dist/components/content/filters.d.ts +28 -0
  83. package/dist/components/content/filters.js +134 -0
  84. package/dist/components/content/filters.js.map +1 -0
  85. package/dist/components/content/grid.d.ts +62 -0
  86. package/dist/components/content/grid.js +233 -0
  87. package/dist/components/content/grid.js.map +1 -0
  88. package/dist/components/content/index.d.ts +8 -0
  89. package/dist/components/content/listing.d.ts +20 -0
  90. package/dist/components/content/listing.js +150 -0
  91. package/dist/components/content/listing.js.map +1 -0
  92. package/dist/components/content/offer-fields.d.ts +8 -0
  93. package/dist/components/content/offer-fields.js +127 -0
  94. package/dist/components/content/offer-fields.js.map +1 -0
  95. package/dist/components/content/paywall.d.ts +53 -0
  96. package/dist/components/content/paywall.js +221 -0
  97. package/dist/components/content/paywall.js.map +1 -0
  98. package/dist/components/content/search.d.ts +27 -0
  99. package/dist/components/content/search.js +164 -0
  100. package/dist/components/content/search.js.map +1 -0
  101. package/dist/components/groups/banner.d.ts +51 -0
  102. package/dist/components/groups/banner.js +194 -0
  103. package/dist/components/groups/banner.js.map +1 -0
  104. package/dist/components/groups/directory.d.ts +37 -0
  105. package/dist/components/groups/directory.js +117 -0
  106. package/dist/components/groups/directory.js.map +1 -0
  107. package/dist/components/groups/editor.d.ts +44 -0
  108. package/dist/components/groups/editor.js +860 -0
  109. package/dist/components/groups/editor.js.map +1 -0
  110. package/dist/components/groups/fields.d.ts +13 -0
  111. package/dist/components/groups/fields.js +68 -0
  112. package/dist/components/groups/fields.js.map +1 -0
  113. package/dist/components/groups/header.d.ts +25 -0
  114. package/dist/components/groups/header.js +131 -0
  115. package/dist/components/groups/header.js.map +1 -0
  116. package/dist/components/groups/index.d.ts +6 -0
  117. package/dist/components/groups/memberships.d.ts +55 -0
  118. package/dist/components/groups/memberships.js +527 -0
  119. package/dist/components/groups/memberships.js.map +1 -0
  120. package/dist/components/groups/parts.d.ts +49 -0
  121. package/dist/components/groups/parts.js +172 -0
  122. package/dist/components/groups/parts.js.map +1 -0
  123. package/dist/components/media-folder-editor.js +153 -152
  124. package/dist/components/media-folder-editor.js.map +1 -1
  125. package/dist/components/social/comments.d.ts +7 -5
  126. package/dist/components/social/comments.js +6 -2
  127. package/dist/components/social/comments.js.map +1 -1
  128. package/dist/components/social/engagement.d.ts +4 -3
  129. package/dist/components/social/engagement.js.map +1 -1
  130. package/dist/components/social/moderation.d.ts +1 -1
  131. package/dist/components/social/moderation.js +3 -3
  132. package/dist/components/social/moderation.js.map +1 -1
  133. package/dist/editor/rich-text-editor.d.ts +4 -4
  134. package/dist/editor/rich-text-editor.js.map +1 -1
  135. package/dist/index.d.ts +4 -2
  136. package/dist/index.js +23 -9
  137. package/dist/locales/admin/de.js +5 -66
  138. package/dist/locales/admin/de.js.map +1 -1
  139. package/dist/locales/admin/en.d.ts +5 -66
  140. package/dist/locales/admin/en.js +19 -80
  141. package/dist/locales/admin/en.js.map +1 -1
  142. package/dist/locales/admin/es.js +5 -66
  143. package/dist/locales/admin/es.js.map +1 -1
  144. package/dist/locales/admin/ja.js +5 -66
  145. package/dist/locales/admin/ja.js.map +1 -1
  146. package/dist/locales/admin/ko.js +5 -66
  147. package/dist/locales/admin/ko.js.map +1 -1
  148. package/dist/locales/admin/zh.js +5 -66
  149. package/dist/locales/admin/zh.js.map +1 -1
  150. package/dist/locales/content/de.d.ts +2 -0
  151. package/dist/locales/content/de.js +98 -0
  152. package/dist/locales/content/de.js.map +1 -0
  153. package/dist/locales/content/en.d.ts +93 -0
  154. package/dist/locales/content/en.js +98 -0
  155. package/dist/locales/content/en.js.map +1 -0
  156. package/dist/locales/content/es.d.ts +2 -0
  157. package/dist/locales/content/es.js +98 -0
  158. package/dist/locales/content/es.js.map +1 -0
  159. package/dist/locales/content/ja.d.ts +2 -0
  160. package/dist/locales/content/ja.js +98 -0
  161. package/dist/locales/content/ja.js.map +1 -0
  162. package/dist/locales/content/ko.d.ts +2 -0
  163. package/dist/locales/content/ko.js +98 -0
  164. package/dist/locales/content/ko.js.map +1 -0
  165. package/dist/locales/content/zh.d.ts +2 -0
  166. package/dist/locales/content/zh.js +98 -0
  167. package/dist/locales/content/zh.js.map +1 -0
  168. package/dist/locales/de.js +11 -2
  169. package/dist/locales/de.js.map +1 -1
  170. package/dist/locales/discovery/de.d.ts +2 -0
  171. package/dist/locales/discovery/de.js +68 -0
  172. package/dist/locales/discovery/de.js.map +1 -0
  173. package/dist/locales/discovery/en.d.ts +63 -0
  174. package/dist/locales/discovery/en.js +68 -0
  175. package/dist/locales/discovery/en.js.map +1 -0
  176. package/dist/locales/discovery/es.d.ts +2 -0
  177. package/dist/locales/discovery/es.js +68 -0
  178. package/dist/locales/discovery/es.js.map +1 -0
  179. package/dist/locales/discovery/ja.d.ts +2 -0
  180. package/dist/locales/discovery/ja.js +68 -0
  181. package/dist/locales/discovery/ja.js.map +1 -0
  182. package/dist/locales/discovery/ko.d.ts +2 -0
  183. package/dist/locales/discovery/ko.js +68 -0
  184. package/dist/locales/discovery/ko.js.map +1 -0
  185. package/dist/locales/discovery/zh.d.ts +2 -0
  186. package/dist/locales/discovery/zh.js +68 -0
  187. package/dist/locales/discovery/zh.js.map +1 -0
  188. package/dist/locales/en.d.ts +288 -67
  189. package/dist/locales/en.js +11 -2
  190. package/dist/locales/en.js.map +1 -1
  191. package/dist/locales/es.js +11 -2
  192. package/dist/locales/es.js.map +1 -1
  193. package/dist/locales/groups/de.d.ts +2 -0
  194. package/dist/locales/groups/de.js +133 -0
  195. package/dist/locales/groups/de.js.map +1 -0
  196. package/dist/locales/groups/en.d.ts +128 -0
  197. package/dist/locales/groups/en.js +133 -0
  198. package/dist/locales/groups/en.js.map +1 -0
  199. package/dist/locales/groups/es.d.ts +2 -0
  200. package/dist/locales/groups/es.js +133 -0
  201. package/dist/locales/groups/es.js.map +1 -0
  202. package/dist/locales/groups/ja.d.ts +2 -0
  203. package/dist/locales/groups/ja.js +133 -0
  204. package/dist/locales/groups/ja.js.map +1 -0
  205. package/dist/locales/groups/ko.d.ts +2 -0
  206. package/dist/locales/groups/ko.js +133 -0
  207. package/dist/locales/groups/ko.js.map +1 -0
  208. package/dist/locales/groups/zh.d.ts +2 -0
  209. package/dist/locales/groups/zh.js +133 -0
  210. package/dist/locales/groups/zh.js.map +1 -0
  211. package/dist/locales/ja.js +11 -2
  212. package/dist/locales/ja.js.map +1 -1
  213. package/dist/locales/ko.js +11 -2
  214. package/dist/locales/ko.js.map +1 -1
  215. package/dist/locales/social/de.js +2 -1
  216. package/dist/locales/social/de.js.map +1 -1
  217. package/dist/locales/social/en.d.ts +2 -1
  218. package/dist/locales/social/en.js +2 -1
  219. package/dist/locales/social/en.js.map +1 -1
  220. package/dist/locales/social/es.js +2 -1
  221. package/dist/locales/social/es.js.map +1 -1
  222. package/dist/locales/social/ja.js +2 -1
  223. package/dist/locales/social/ja.js.map +1 -1
  224. package/dist/locales/social/ko.js +2 -1
  225. package/dist/locales/social/ko.js.map +1 -1
  226. package/dist/locales/social/zh.js +2 -1
  227. package/dist/locales/social/zh.js.map +1 -1
  228. package/dist/locales/zh.js +11 -2
  229. package/dist/locales/zh.js.map +1 -1
  230. package/dist/react/admin.d.ts +1 -1
  231. package/dist/react/admin.js +8 -6
  232. package/dist/react/admin.js.map +1 -1
  233. package/dist/react/comments.d.ts +9 -7
  234. package/dist/react/comments.js +42 -27
  235. package/dist/react/comments.js.map +1 -1
  236. package/dist/react/contents.d.ts +57 -0
  237. package/dist/react/contents.js +214 -0
  238. package/dist/react/contents.js.map +1 -0
  239. package/dist/react/context.d.ts +1 -1
  240. package/dist/react/context.js.map +1 -1
  241. package/dist/react/crop.js +45 -44
  242. package/dist/react/crop.js.map +1 -1
  243. package/dist/react/detail.d.ts +30 -0
  244. package/dist/react/detail.js +42 -0
  245. package/dist/react/detail.js.map +1 -0
  246. package/dist/react/discovery.d.ts +138 -0
  247. package/dist/react/discovery.js +299 -0
  248. package/dist/react/discovery.js.map +1 -0
  249. package/dist/react/engagement.d.ts +5 -5
  250. package/dist/react/engagement.js +55 -41
  251. package/dist/react/engagement.js.map +1 -1
  252. package/dist/react/folder.js +54 -53
  253. package/dist/react/folder.js.map +1 -1
  254. package/dist/react/groups.d.ts +115 -0
  255. package/dist/react/groups.js +229 -0
  256. package/dist/react/groups.js.map +1 -0
  257. package/dist/react/index.d.ts +5 -1
  258. package/dist/react/public.d.ts +6 -5
  259. package/dist/react/public.js +19 -22
  260. package/dist/react/public.js.map +1 -1
  261. package/dist/react/read.js +105 -106
  262. package/dist/react/read.js.map +1 -1
  263. package/dist/react/resources.d.ts +2 -2
  264. package/dist/react/resources.js +69 -40
  265. package/dist/react/resources.js.map +1 -1
  266. package/dist/react/store.js +41 -39
  267. package/dist/react/store.js.map +1 -1
  268. package/dist/react/upload.js +54 -53
  269. package/dist/react/upload.js.map +1 -1
  270. package/dist/react/use-resource.d.ts +2 -1
  271. package/dist/react/use-resource.js +1 -0
  272. package/dist/react/use-resource.js.map +1 -1
  273. package/dist/react/versions.d.ts +57 -0
  274. package/dist/react/versions.js +94 -0
  275. package/dist/react/versions.js.map +1 -0
  276. package/dist/react/video.js +87 -86
  277. package/dist/react/video.js.map +1 -1
  278. package/dist/react.js +9 -5
  279. package/dist/styles.css +1 -1
  280. package/package.json +1 -1
  281. package/dist/client/content/posts.d.ts +0 -52
  282. package/dist/client/content/posts.js +0 -84
  283. package/dist/client/content/posts.js.map +0 -1
  284. package/dist/client/taxonomy.d.ts +0 -34
  285. package/dist/client/taxonomy.js +0 -84
  286. package/dist/client/taxonomy.js.map +0 -1
  287. package/dist/components/admin/post-manager.d.ts +0 -45
  288. package/dist/components/admin/post-manager.js +0 -602
  289. package/dist/components/admin/post-manager.js.map +0 -1
  290. package/dist/components/admin/taxonomy-manager.d.ts +0 -54
  291. package/dist/components/admin/taxonomy-manager.js +0 -976
  292. package/dist/components/admin/taxonomy-manager.js.map +0 -1
  293. package/dist/react/posts.d.ts +0 -61
  294. package/dist/react/posts.js +0 -183
  295. package/dist/react/posts.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-manager.js","names":[],"sources":["../../../src/components/admin/content-manager.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport { imageRefs } from \"../../client/contents.js\";\nimport type { Content } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContent } from \"../../react/contents.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button, buttonVariants } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"../social/bans.js\";\nimport { contentError, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { ContentSections } from \"../content/editors.js\";\nimport type { Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Pager, Rows, pageOf, useConfirm, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\n/** What ContentManager hands its body editor. */\nexport interface BodyEditorProps {\n /** The body as HTML, its images at URLs that show them now. */\n value: string;\n onChange: (html: string) => void;\n /** Uploads an image to the item and resolves with a URL that shows it; absent until the item exists. */\n uploadImage?: (file: File) => Promise<string>;\n disabled?: boolean;\n placeholder?: string;\n \"aria-label\"?: string;\n}\n\nexport interface ContentManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The content kind managed. */\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /**\n * The body editor: `richTextEditor` from `@openrails/contentkit-ui/editor`,\n * or the host's own. Without it, the body is a plain HTML field.\n */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n /** Display names of kinds, traits, tiers, attributes and values by identifier. */\n labels?: Labels;\n pageSize?: number;\n}\n\n/** Whether the caller manages content: root:content:write holders, creators and owner-group editors. */\nexport function useContentManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"contents\"> {\n return useStaffAccess([\"contents\"] as const, o);\n}\n\nexport type ContentStatus = \"draft\" | \"scheduled\" | \"published\" | \"held\" | \"rejected\";\n\n/** An item's status as the manager names it. */\nexport function contentStatus(c: Content): ContentStatus {\n if (c.moderation === \"rejected\") return \"rejected\";\n if (c.moderation === \"held\") return \"held\";\n if (c.state === \"draft\") return \"draft\";\n if (!c.live) return \"scheduled\";\n return \"published\";\n}\n\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst badge: Record<ContentStatus, \"default\" | \"secondary\" | \"outline\" | \"destructive\"> = { published: \"default\", scheduled: \"secondary\", draft: \"outline\", held: \"secondary\", rejected: \"destructive\" };\nconst states = [\"draft\", \"scheduled\", \"published\"] as const;\n\n/**\n * Content items of one kind for their creators and staff: every one the\n * caller manages, by state and text, deleted ones on their own to restore;\n * create and edit title, slug, description, schedule, cover and body with\n * inline images per language, publish or keep a draft, and delete; then its\n * listing and attributes, access rule and price, versions (order, languages,\n * traits, schedule, access override, price, segments, media) and groups. The\n * list is at basePath, a new item at basePath/new and each item at\n * basePath/{id}, filters in the query.\n */\nexport function ContentManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"content-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Contents {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<ContentManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Contents({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useContentManagerAccess({ client: p.client });\n if (access.loading) return <Rows />;\n if (!access.allowed.contents) return <NoAccess>{t(\"contentManager.noAccess\")}</NoAccess>;\n if (!loc.view) return <List {...p} loc={loc} />;\n return (\n <>\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(\"\")}>\n {t(\"contentManager.back\")}\n </Button>\n <ContentEditor\n key={loc.view}\n {...p}\n id={loc.view === \"new\" ? null : loc.view}\n onCreated={(c) => loc.go(c.id, undefined, { replace: true })}\n onDeleted={() => loc.go(\"\", undefined, { replace: true })}\n />\n </>\n );\n}\n\nfunction List({ loc, client, kind, languages: offered, contentHref, pageSize = 20, onError }: Inner & { loc: AdminLocation }) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const language = choices(offered)[0]?.value;\n const query = loc.query.get(\"q\") ?? \"\";\n const status = states.find((s) => s === loc.query.get(\"status\")) ?? \"\";\n const deleted = loc.query.get(\"deleted\") === \"1\";\n const page = pageOf(loc.query);\n const filters = { q: query, status, deleted: deleted ? \"1\" : undefined };\n const filter = (next: Partial<typeof filters>) => loc.go(\"\", { ...filters, ...next });\n const [text, setText] = useState(query);\n useEffect(() => setText(query), [query]);\n const { store, scope } = useContentScope(client);\n const state = deleted ? \"deleted\" : status || undefined;\n const list = usePaged(store, keyOf(\"contents-page\", scope, kind, query, state, pageSize), { type: \"contents\", scope: deleted ? \"deleted\" : undefined }, page, pageSize, (q, signal) =>\n client.contents.manage({ ...q, kind, q: query || undefined, state, language }, signal),\n );\n const del = useAction(\"content.save\", onError);\n const { confirm, dialog } = useConfirm();\n const remove = async (c: Content) => {\n if (await confirm({ title: t(\"contentManager.deleteTitle\", { title: c.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") })) {\n await del.run(() => client.contents.remove(c.id));\n }\n };\n return (\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"contentManager.title\")}</h2>\n <Button size=\"sm\" onClick={() => loc.go(\"new\")}>\n {t(\"contentManager.new\")}\n </Button>\n </div>\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"contentManager.title\")}>\n <Button size=\"sm\" variant={deleted ? \"outline\" : \"default\"} aria-pressed={!deleted} onClick={() => loc.go(\"\", { q: query })}>\n {t(\"contentManager.items\")}\n </Button>\n <Button size=\"sm\" variant={deleted ? \"default\" : \"outline\"} aria-pressed={deleted} onClick={() => loc.go(\"\", { q: query, deleted: \"1\" })}>\n {t(\"contentManager.deleted\")}\n </Button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto]\">\n <form\n className=\"flex gap-2\"\n role=\"search\"\n onSubmit={(e) => {\n e.preventDefault();\n filter({ q: text.trim() });\n }}\n >\n <Label htmlFor={`${ids}-q`} className=\"sr-only\">\n {t(\"contentManager.search\")}\n </Label>\n <Input id={`${ids}-q`} className=\"h-8\" type=\"search\" value={text} placeholder={t(\"contentManager.search\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n {!deleted && (\n <Select value={status} onValueChange={(v) => filter({ status: String(v ?? \"\") as typeof status })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentManager.status\")}>\n <SelectValue>{(v: string) => t(v ? `contentManager.${v as (typeof states)[number]}` : \"contentManager.allStatuses\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"contentManager.allStatuses\")}</SelectItem>\n {states.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`contentManager.${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <ErrorLine error={del.error} />\n {list.loading && !list.items.length ? (\n <Rows />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"contentManager.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(deleted ? \"contentManager.noDeleted\" : \"contentManager.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={list.loading}>\n {list.items.map((c) => {\n const s = contentStatus(c);\n const href = s === \"published\" ? contentHref?.(c) : undefined;\n return (\n <li key={c.id} className=\"flex flex-wrap items-start gap-3 p-3\" data-ckui=\"content-row\" data-status={deleted ? \"deleted\" : s}>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n {deleted ? (\n <span className=\"font-medium break-words\">{c.title}</span>\n ) : (\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(c.id)}>\n {c.title}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {!deleted && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n <span>{c.language}</span>\n <RelativeTime at={deleted && c.deleted_at ? c.deleted_at : c.updated_at} />\n {s === \"scheduled\" && c.publish_at && <span>{t(\"pollManager.goesLive\", { date: formatDate(m, c.publish_at) })}</span>}\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={del.pending} onClick={() => void del.run(() => client.contents.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <>\n {href && (\n <a className={buttonVariants({ size: \"sm\", variant: \"ghost\" })} href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n <Button size=\"sm\" variant=\"outline\" onClick={() => loc.go(c.id)}>\n {t(\"comments.edit\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void remove(c)}>\n {t(\"comments.delete\")}\n </Button>\n </>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pager page={page} hasNext={list.hasNext} disabled={list.loading} onPage={(n) => loc.go(\"\", { ...filters, page: n > 1 ? n : undefined })} />\n {dialog}\n </div>\n );\n}\n\ninterface Form {\n title: string;\n slug: string;\n description: string;\n publishAt: string;\n body: string;\n}\n\nconst empty: Form = { title: \"\", slug: \"\", description: \"\", publishAt: \"\", body: \"\" };\n\nexport interface ContentEditorProps {\n /** The item edited; null creates one of kind. */\n id: string | null;\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /** The body editor; without it, a plain HTML field. */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n labels?: Labels;\n /** A new item's owner group: a channel its editors write for. */\n ownerGroup?: string;\n /** After create: show the new item (the editor goes on editing it). */\n onCreated?: (content: Content) => void;\n onDeleted?: (content: Content) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n}\n\n/**\n * One content item for its creators and staff: title, slug, description,\n * schedule, cover and body per language, publish, unpublish and delete;\n * then listing and attributes, access rule and price, versions and groups.\n */\nexport function ContentEditor({ id, client: own, kind, languages: offered, renderEditor, contentHref, labels, ownerGroup, onCreated, onDeleted, onError }: ContentEditorProps) {\n const client = useContentKitClient(own);\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const [language, setLanguage] = useState(languages[0]?.value ?? \"en\");\n const ed = useContent(id, kind, language, { client });\n const report = useErrorReporter(onError);\n // The body is kept as stored (image references); the editor shows it with URLs.\n const [form, setForm] = useState<Form | null>(id ? null : empty);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const { confirm, dialog } = useConfirm();\n const item = ed.content;\n useEffect(() => {\n if (!item) return;\n const stored = item.text?.[language];\n setForm((f) => f ?? { title: stored?.title ?? \"\", slug: stored?.slug ?? \"\", description: stored?.description ?? \"\", body: stored?.body ?? \"\",\n publishAt: item.publish_at && !item.live ? localInput(new Date(item.publish_at)) : \"\" });\n }, [item, language]);\n const { imageSrc } = ed;\n const shown = useMemo(\n () => (form?.body ?? \"\").replace(imageRefs(), (ref, name: string) => {\n const src = imageSrc(name);\n return src && src !== name ? src.replaceAll(\"&\", \"&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 <Cover item={item} src={ed.coverSrc} saving={ed.saving} onFile={(file) => void ed.setCover(file).catch(fail)} />\n <div className=\"grid gap-1.5\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.body\")}</span>\n {renderEditor ? renderEditor(editor) : <Textarea aria-label={editor[\"aria-label\"]} rows={12} value={f.body} placeholder={editor.placeholder} onChange={(e) => set({ body: e.target.value })} />}\n {!item && <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>}\n </div>\n {error && <ErrorLine>{typeof error === \"string\" ? error : contentError(m, error)}</ErrorLine>}\n {notice && !error && (\n <p role=\"status\" className=\"text-sm text-muted-foreground\">\n {notice}\n </p>\n )}\n <div className=\"flex flex-wrap gap-2 border-t border-border pt-4\">\n <Button type=\"submit\" disabled={ed.saving}>\n {ed.saving ? t(\"common.saving\") : t(item ? \"common.save\" : \"contentManager.createDraft\")}\n </Button>\n {item && !item.live && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(true)}>\n {t(\"contentManager.publish\")}\n </Button>\n )}\n {item?.state === \"published\" && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(false)}>\n {t(\"contentManager.unpublish\")}\n </Button>\n )}\n {item && (\n <Button type=\"button\" variant=\"destructive\" className=\"ms-auto\" disabled={ed.saving} onClick={() => void remove()}>\n {t(\"contentManager.delete\")}\n </Button>\n )}\n </div>\n {dialog}\n </form>\n {item && <ContentSections content={item} client={client} onError={onError} labels={labels} />}\n </>\n );\n}\n\nfunction Cover({ item, src, saving, onFile }: { item: Content | null; src?: string; saving: boolean; onFile: (file: File | null) => void }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"content-cover\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.cover\")}</span>\n {item ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {src ? <img src={src} alt=\"\" className=\"h-20 w-36 rounded-md border border-border object-cover\" /> : <span className=\"text-muted-foreground\">{t(\"contentManager.noCover\")}</span>}\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={saving} onClick={() => file.current?.click()}>\n {t(src ? \"pollEditor.replaceImage\" : \"contentManager.addCover\")}\n </Button>\n {src && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={() => onFile(null)}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n <input\n ref={file}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(\"contentManager.cover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) onFile(f);\n }}\n />\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { ReactNode } from 'react';
2
+ import { Membership } from '../../client/generated/wire.js';
3
+ import { FlowClientOptions, StaffAccess } from '../../react/admin.js';
4
+ import { Labels } from '../groups/parts.js';
5
+ import { AdminFlowProps, AdminRouteProps } from './shell.js';
6
+ export interface GroupManagerProps extends AdminFlowProps, AdminRouteProps {
7
+ /** The group kinds managed, in tab order; default the content model's site-owned kinds. */
8
+ groupKinds?: readonly string[];
9
+ /** Display names of kinds, attributes, roles and values by identifier. */
10
+ labels?: Labels;
11
+ /** Counts of this content kind; default every kind. */
12
+ contentKind?: string;
13
+ /** One member, e.g. a link with its title; default its content id. */
14
+ renderContent?: (member: Membership) => ReactNode;
15
+ pageSize?: number;
16
+ }
17
+ /** Whether the caller may manage groups (root:groups:write). */
18
+ export declare function useGroupManagerAccess(o?: FlowClientOptions): StaffAccess<"groups">;
19
+ /**
20
+ * Staff: groups per kind. A kind's groups by letter, order and language; a
21
+ * new group; a group's editor (GroupEditor) and its members (GroupMembers).
22
+ * Kinds are at basePath/{kind}, a group at basePath/{kind}/{id}, filters in the query.
23
+ */
24
+ export declare function GroupManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: GroupManagerProps): import('react').JSX.Element;
@@ -0,0 +1,244 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { Button as t } from "../../ui/button.js";
3
+ import { Select as n, SelectContent as r, SelectItem as i, SelectTrigger as a, SelectValue as o } from "../../ui/select.js";
4
+ import { AdminRoot as s, LoadFailed as c, NoAccess as l, Rows as u } from "./shell.js";
5
+ import { useAdminLocation as d, useFlowClient as f, useStaffAccess as p } from "../../react/admin.js";
6
+ import { Tabs as m, TabsList as h, TabsTrigger as g } from "../../ui/tabs.js";
7
+ import { LETTERS as _ } from "../../client/group-fields.js";
8
+ import { useGroups as v, useKinds as y } from "../../react/groups.js";
9
+ import { labelOf as b, useLanguageName as x } from "../groups/parts.js";
10
+ import { GroupEditor as S } from "../groups/editor.js";
11
+ import { GroupMembers as C } from "../groups/memberships.js";
12
+ import { useMemo as w } from "react";
13
+ import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
14
+ //#region src/components/admin/group-manager.tsx
15
+ function O(e = {}) {
16
+ return p(["groups"], e);
17
+ }
18
+ var k = [
19
+ "name",
20
+ "count",
21
+ "newest"
22
+ ];
23
+ function A({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages: c, ...l }) {
24
+ let u = f({
25
+ client: e,
26
+ baseUrl: t,
27
+ auth: n
28
+ });
29
+ return /* @__PURE__ */ E(s, {
30
+ flow: "group-manager",
31
+ appearance: i,
32
+ className: a,
33
+ language: o,
34
+ messages: c,
35
+ guard: r,
36
+ children: /* @__PURE__ */ E(j, {
37
+ ...l,
38
+ client: u
39
+ })
40
+ });
41
+ }
42
+ function j({ basePath: t, navigation: n, ...r }) {
43
+ let { t: i } = e(), a = d(t, n), o = O({ client: r.client }), s = y({ client: r.client }), f = s.kinds, p = w(() => r.groupKinds ?? Object.entries(f?.group_kinds ?? {}).filter(([, e]) => e.owner !== "user").map(([e]) => e).sort(), [r.groupKinds, f]);
44
+ if (o.loading || s.loading) return /* @__PURE__ */ E(u, {});
45
+ if (!o.allowed.groups) return /* @__PURE__ */ E(l, { children: i("groupManager.noAccess") });
46
+ if (!f) return /* @__PURE__ */ E(c, {
47
+ error: s.error,
48
+ message: i("groupManager.kindsFailed"),
49
+ retry: s.reload
50
+ });
51
+ if (!p.length) return /* @__PURE__ */ E("p", {
52
+ className: "text-muted-foreground",
53
+ children: i("groupManager.noKinds")
54
+ });
55
+ let [_, v] = a.view.split("/"), x = p.includes(_ ?? "") ? _ : p[0], S = {
56
+ ...r,
57
+ loc: a,
58
+ kind: x,
59
+ kinds: f
60
+ };
61
+ return /* @__PURE__ */ D(T, { children: [p.length > 1 && /* @__PURE__ */ E(m, {
62
+ value: x,
63
+ onValueChange: (e) => a.go(String(e)),
64
+ children: /* @__PURE__ */ E(h, {
65
+ className: "flex-wrap",
66
+ children: p.map((e) => /* @__PURE__ */ E(g, {
67
+ value: e,
68
+ children: b(r.labels, e)
69
+ }, e))
70
+ })
71
+ }), v ? v === "new" ? /* @__PURE__ */ E(P, { ...S }, x) : /* @__PURE__ */ E(F, {
72
+ ...S,
73
+ id: v
74
+ }, v) : /* @__PURE__ */ E(N, { ...S }, x)] });
75
+ }
76
+ function M({ loc: n, kind: r }) {
77
+ let { t: i } = e();
78
+ return /* @__PURE__ */ E(t, {
79
+ variant: "ghost",
80
+ size: "sm",
81
+ className: "justify-self-start",
82
+ onClick: () => n.go(r),
83
+ children: i("groupManager.back")
84
+ });
85
+ }
86
+ function N({ loc: s, kind: l, kinds: d, client: f, contentKind: p, labels: m, pageSize: h = 50 }) {
87
+ let g = e(), { t: y } = g, S = x(), C = d.languages, w = s.query.get("letter") ?? "", T = _.includes(w) ? w : void 0, O = s.query.get("sort") ?? "", A = k.includes(O) ? O : "name", j = s.query.get("language") ?? "", M = C.includes(j) ? j : C[0], N = {
88
+ letter: T,
89
+ sort: A === "name" ? void 0 : A,
90
+ language: M === C[0] ? void 0 : M
91
+ }, P = (e) => s.go(l, {
92
+ ...N,
93
+ ...e
94
+ }), F = v({
95
+ kind: l,
96
+ letter: T,
97
+ sort: A,
98
+ language: M,
99
+ contentKind: p,
100
+ pageSize: h
101
+ }, { client: f }), I = b(m, l);
102
+ return /* @__PURE__ */ D("div", {
103
+ className: "grid gap-4",
104
+ "data-ckui": "group-list",
105
+ "data-kind": l,
106
+ children: [
107
+ /* @__PURE__ */ D("div", {
108
+ className: "flex flex-wrap items-center justify-between gap-2",
109
+ children: [/* @__PURE__ */ E("h2", {
110
+ className: "text-base font-semibold",
111
+ children: I
112
+ }), /* @__PURE__ */ E(t, {
113
+ size: "sm",
114
+ onClick: () => s.go(`${l}/new`),
115
+ children: y("groupManager.new", { kind: I })
116
+ })]
117
+ }),
118
+ /* @__PURE__ */ D("div", {
119
+ className: "flex flex-wrap items-center gap-2",
120
+ children: [/* @__PURE__ */ D(n, {
121
+ value: A,
122
+ onValueChange: (e) => P({ sort: e === "name" ? void 0 : String(e) }),
123
+ children: [/* @__PURE__ */ E(a, {
124
+ size: "sm",
125
+ "aria-label": y("groupManager.sort"),
126
+ children: /* @__PURE__ */ E(o, { children: (e) => y(`groupManager.sort_${e}`) })
127
+ }), /* @__PURE__ */ E(r, { children: k.map((e) => /* @__PURE__ */ E(i, {
128
+ value: e,
129
+ children: y(`groupManager.sort_${e}`)
130
+ }, e)) })]
131
+ }), C.length > 1 && /* @__PURE__ */ D(n, {
132
+ value: M,
133
+ onValueChange: (e) => P({ language: e === C[0] ? void 0 : String(e) }),
134
+ children: [/* @__PURE__ */ E(a, {
135
+ size: "sm",
136
+ "aria-label": y("groupManager.language"),
137
+ children: /* @__PURE__ */ E(o, { children: (e) => S(e) })
138
+ }), /* @__PURE__ */ E(r, { children: C.map((e) => /* @__PURE__ */ E(i, {
139
+ value: e,
140
+ children: S(e)
141
+ }, e)) })]
142
+ })]
143
+ }),
144
+ /* @__PURE__ */ E("nav", {
145
+ "aria-label": y("groups.byLetter"),
146
+ className: "flex flex-wrap gap-1",
147
+ "data-ckui": "letters",
148
+ children: [void 0, ..._].map((e) => /* @__PURE__ */ E(t, {
149
+ size: "xs",
150
+ variant: T === e ? "default" : "ghost",
151
+ "aria-pressed": T === e,
152
+ "aria-label": e === void 0 ? void 0 : e === "#" ? y("groups.otherLetters") : e.toUpperCase(),
153
+ className: "min-w-7",
154
+ onClick: () => P({ letter: e }),
155
+ children: e === void 0 ? y("groups.all") : e.toUpperCase()
156
+ }, e ?? "all"))
157
+ }),
158
+ F.loading && !F.items.length ? /* @__PURE__ */ E(u, {}) : F.error && !F.items.length ? /* @__PURE__ */ E(c, {
159
+ error: F.error,
160
+ message: y("groupManager.loadFailed"),
161
+ retry: F.reload
162
+ }) : F.items.length ? /* @__PURE__ */ E("ul", {
163
+ className: "grid divide-y divide-border rounded-lg border border-border",
164
+ "aria-busy": F.loading,
165
+ children: F.items.map((e) => /* @__PURE__ */ D("li", {
166
+ className: "flex flex-wrap items-center gap-3 p-3",
167
+ "data-ckui": "group-row",
168
+ "data-slug": e.slug,
169
+ children: [/* @__PURE__ */ D("div", {
170
+ className: "grid min-w-0 flex-1 gap-0.5",
171
+ children: [/* @__PURE__ */ E("button", {
172
+ type: "button",
173
+ className: "text-left font-medium break-words hover:underline",
174
+ onClick: () => s.go(`${l}/${e.id}`),
175
+ children: e.name || e.slug
176
+ }), /* @__PURE__ */ E("span", {
177
+ className: "text-xs text-muted-foreground",
178
+ children: e.slug
179
+ })]
180
+ }), /* @__PURE__ */ E("span", {
181
+ className: "text-sm text-muted-foreground tabular-nums",
182
+ title: y("groupManager.count"),
183
+ children: new Intl.NumberFormat(g.language).format(e.count)
184
+ })]
185
+ }, e.id))
186
+ }) : /* @__PURE__ */ E("p", {
187
+ className: "text-muted-foreground",
188
+ children: y("groups.empty")
189
+ }),
190
+ F.hasMore && /* @__PURE__ */ E(t, {
191
+ variant: "outline",
192
+ size: "sm",
193
+ className: "justify-self-center",
194
+ disabled: F.loadingMore,
195
+ onClick: () => void F.loadMore(),
196
+ children: F.loadingMore ? y("groups.loading") : y("groups.loadMore")
197
+ })
198
+ ]
199
+ });
200
+ }
201
+ function P({ loc: e, kind: t, client: n, labels: r, onError: i }) {
202
+ return /* @__PURE__ */ D("div", {
203
+ className: "grid gap-4",
204
+ children: [/* @__PURE__ */ E(M, {
205
+ loc: e,
206
+ kind: t
207
+ }), /* @__PURE__ */ E(S, {
208
+ id: null,
209
+ kind: t,
210
+ labels: r,
211
+ client: n,
212
+ onError: i,
213
+ onCreated: (n) => e.go(`${t}/${n.id}`, void 0, { replace: !0 })
214
+ })]
215
+ });
216
+ }
217
+ function F({ loc: e, kind: t, client: n, labels: r, onError: i, renderContent: a, id: o }) {
218
+ return /* @__PURE__ */ D("div", {
219
+ className: "grid gap-4",
220
+ children: [/* @__PURE__ */ E(M, {
221
+ loc: e,
222
+ kind: t
223
+ }), /* @__PURE__ */ E(S, {
224
+ id: o,
225
+ kind: t,
226
+ labels: r,
227
+ client: n,
228
+ onError: i,
229
+ onMerged: (n) => e.go(`${t}/${n.into}`, void 0, { replace: !0 }),
230
+ children: (e) => /* @__PURE__ */ E(C, {
231
+ group: e,
232
+ labels: r,
233
+ client: n,
234
+ onError: i,
235
+ renderContent: a,
236
+ onUseCover: (t) => n.groups.update(e.id, { cover: { content_id: t } })
237
+ })
238
+ })]
239
+ });
240
+ }
241
+ //#endregion
242
+ export { A as GroupManager, O as useGroupManagerAccess };
243
+
244
+ //# sourceMappingURL=group-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-manager.js","names":[],"sources":["../../../src/components/admin/group-manager.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { LETTERS } from \"../../client/group-fields.js\";\nimport type { ContentModel, Membership } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useGroups, useKinds, type GroupSort } from \"../../react/groups.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Tabs, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { GroupEditor } from \"../groups/editor.js\";\nimport { GroupMembers } from \"../groups/memberships.js\";\nimport { labelOf, useLanguageName, type Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\nexport interface GroupManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The group kinds managed, in tab order; default the content model's site-owned kinds. */\n groupKinds?: readonly string[];\n /** Display names of kinds, attributes, roles and values by identifier. */\n labels?: Labels;\n /** Counts of this content kind; default every kind. */\n contentKind?: string;\n /** One member, e.g. a link with its title; default its content id. */\n renderContent?: (member: Membership) => ReactNode;\n pageSize?: number;\n}\n\n/** Whether the caller may manage groups (root:groups:write). */\nexport function useGroupManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"groups\"> {\n return useStaffAccess([\"groups\"] as const, o);\n}\n\nconst SORTS: readonly GroupSort[] = [\"name\", \"count\", \"newest\"];\n\n/**\n * Staff: groups per kind. A kind's groups by letter, order and language; a\n * new group; a group's editor (GroupEditor) and its members (GroupMembers).\n * Kinds are at basePath/{kind}, a group at basePath/{kind}/{id}, filters in the query.\n */\nexport function GroupManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: GroupManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"group-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Manager {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<GroupManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\ntype Ctx = Inner & { loc: AdminLocation; kind: string; kinds: ContentModel };\n\nfunction Manager({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useGroupManagerAccess({ client: p.client });\n const model = useKinds({ client: p.client });\n const kinds = model.kinds;\n const list = useMemo(\n () =>\n p.groupKinds ??\n Object.entries(kinds?.group_kinds ?? {})\n .filter(([, k]) => k.owner !== \"user\")\n .map(([name]) => name)\n .sort(),\n [p.groupKinds, kinds],\n );\n if (access.loading || model.loading) return <Rows />;\n if (!access.allowed.groups) return <NoAccess>{t(\"groupManager.noAccess\")}</NoAccess>;\n if (!kinds) return <LoadFailed error={model.error} message={t(\"groupManager.kindsFailed\")} retry={model.reload} />;\n if (!list.length) return <p className=\"text-muted-foreground\">{t(\"groupManager.noKinds\")}</p>;\n const [k, sub] = loc.view.split(\"/\");\n const kind = list.includes(k ?? \"\") ? k! : list[0]!;\n const ctx: Ctx = { ...p, loc, kind, kinds };\n return (\n <>\n {list.length > 1 && (\n <Tabs value={kind} onValueChange={(v) => loc.go(String(v))}>\n <TabsList className=\"flex-wrap\">\n {list.map((x) => (\n <TabsTrigger key={x} value={x}>\n {labelOf(p.labels, x)}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )}\n {!sub ? <List key={kind} {...ctx} /> : sub === \"new\" ? <Create key={kind} {...ctx} /> : <Edit key={sub} {...ctx} id={sub} />}\n </>\n );\n}\n\nfunction Back({ loc, kind }: { loc: AdminLocation; kind: string }) {\n const { t } = useMessages();\n return (\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind)}>\n {t(\"groupManager.back\")}\n </Button>\n );\n}\n\nfunction List({ loc, kind, kinds, client, contentKind, labels, pageSize = 50 }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const languages = kinds.languages;\n const letterParam = loc.query.get(\"letter\") ?? \"\";\n const letter = LETTERS.includes(letterParam) ? letterParam : undefined;\n const sortParam = loc.query.get(\"sort\") ?? \"\";\n const sort = (SORTS as readonly string[]).includes(sortParam) ? (sortParam as GroupSort) : \"name\";\n const languageParam = loc.query.get(\"language\") ?? \"\";\n const language = languages.includes(languageParam) ? languageParam : languages[0];\n const filters: { letter?: string; sort?: GroupSort; language?: string } = { letter, sort: sort === \"name\" ? undefined : sort, language: language === languages[0] ? undefined : language };\n const filter = (next: Partial<typeof filters>) => loc.go(kind, { ...filters, ...next });\n const groups = useGroups({ kind, letter, sort, language, contentKind, pageSize }, { client });\n const label = labelOf(labels, kind);\n return (\n <div className=\"grid gap-4\" data-ckui=\"group-list\" data-kind={kind}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{label}</h2>\n <Button size=\"sm\" onClick={() => loc.go(`${kind}/new`)}>\n {t(\"groupManager.new\", { kind: label })}\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Select value={sort} onValueChange={(v) => filter({ sort: v === \"name\" ? undefined : (String(v) as GroupSort) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.sort\")}>\n <SelectValue>{(v: string) => t(`groupManager.sort_${v as GroupSort}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {SORTS.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`groupManager.sort_${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languages.length > 1 && (\n <Select value={language} onValueChange={(v) => filter({ language: v === languages[0] ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.language\")}>\n <SelectValue>{(v: string) => languageName(v)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <nav aria-label={t(\"groups.byLetter\")} className=\"flex flex-wrap gap-1\" data-ckui=\"letters\">\n {[undefined, ...LETTERS].map((l) => (\n <Button\n key={l ?? \"all\"}\n size=\"xs\"\n variant={letter === l ? \"default\" : \"ghost\"}\n aria-pressed={letter === l}\n aria-label={l === undefined ? undefined : l === \"#\" ? t(\"groups.otherLetters\") : l.toUpperCase()}\n className=\"min-w-7\"\n onClick={() => filter({ letter: l })}\n >\n {l === undefined ? t(\"groups.all\") : l.toUpperCase()}\n </Button>\n ))}\n </nav>\n {groups.loading && !groups.items.length ? (\n <Rows />\n ) : groups.error && !groups.items.length ? (\n <LoadFailed error={groups.error} message={t(\"groupManager.loadFailed\")} retry={groups.reload} />\n ) : !groups.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"groups.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={groups.loading}>\n {groups.items.map((g) => (\n <li key={g.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"group-row\" data-slug={g.slug}>\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(`${kind}/${g.id}`)}>\n {g.name || g.slug}\n </button>\n <span className=\"text-xs text-muted-foreground\">{g.slug}</span>\n </div>\n <span className=\"text-sm text-muted-foreground tabular-nums\" title={t(\"groupManager.count\")}>\n {new Intl.NumberFormat(m.language).format(g.count)}\n </span>\n </li>\n ))}\n </ul>\n )}\n {groups.hasMore && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={groups.loadingMore} onClick={() => void groups.loadMore()}>\n {groups.loadingMore ? t(\"groups.loading\") : t(\"groups.loadMore\")}\n </Button>\n )}\n </div>\n );\n}\n\nfunction Create({ loc, kind, client, labels, onError }: Ctx) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n id={null}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onCreated={(g) => loc.go(`${kind}/${g.id}`, undefined, { replace: true })}\n />\n </div>\n );\n}\n\nfunction Edit({ loc, kind, client, labels, onError, renderContent, id }: Ctx & { id: string }) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n id={id}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onMerged={(r) => loc.go(`${kind}/${r.into}`, undefined, { replace: true })}\n >\n {(group) => (\n <GroupMembers\n group={group}\n labels={labels}\n client={client}\n onError={onError}\n renderContent={renderContent}\n onUseCover={(content) => client.groups.update(group.id, { cover: { content_id: content } })}\n />\n )}\n </GroupEditor>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AA4BA,SAAgB,EAAsB,IAAuB,CAAC,GAA0B;CACtF,OAAO,EAAe,CAAC,QAAQ,GAAY,CAAC;AAC9C;AAEA,IAAM,IAA8B;CAAC;CAAQ;CAAS;AAAQ;AAO9D,SAAgB,EAAa,EAAE,QAAQ,GAAK,YAAS,SAAM,UAAO,eAAY,cAAW,aAAU,aAAU,GAAG,KAAwB;CACtI,IAAM,IAAS,EAAc;EAAE,QAAQ;EAAK;EAAS;CAAK,CAAC;CAC3D,OACE,kBAAC,GAAD;EAAW,MAAK;EAA4B;EAAuB;EAAqB;EAAoB;EAAiB;EAC3H,UAAA,kBAAC,GAAD;GAAS,GAAI;GAAW;EAAS,CAAA;CACxB,CAAA;AAEf;AAKA,SAAS,EAAQ,EAAE,aAAU,eAAY,GAAG,KAAY;CACtD,IAAM,EAAE,SAAM,EAAY,GACpB,IAAM,EAAiB,GAAU,CAAU,GAC3C,IAAS,EAAsB,EAAE,QAAQ,EAAE,OAAO,CAAC,GACnD,IAAQ,EAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GACrC,IAAQ,EAAM,OACd,IAAO,QAET,EAAE,cACF,OAAO,QAAQ,GAAO,eAAe,CAAC,CAAC,CAAC,CACrC,QAAQ,GAAG,OAAO,EAAE,UAAU,MAAM,CAAC,CACrC,KAAK,CAAC,OAAU,CAAI,CAAC,CACrB,KAAK,GACV,CAAC,EAAE,YAAY,CAAK,CACtB;CACA,IAAI,EAAO,WAAW,EAAM,SAAS,OAAO,kBAAC,GAAD,CAAO,CAAA;CACnD,IAAI,CAAC,EAAO,QAAQ,QAAQ,OAAO,kBAAC,GAAD,EAAA,UAAW,EAAE,uBAAuB,EAAY,CAAA;CACnF,IAAI,CAAC,GAAO,OAAO,kBAAC,GAAD;EAAY,OAAO,EAAM;EAAO,SAAS,EAAE,0BAA0B;EAAG,OAAO,EAAM;CAAS,CAAA;CACjH,IAAI,CAAC,EAAK,QAAQ,OAAO,kBAAC,KAAD;EAAG,WAAU;EAAyB,UAAA,EAAE,sBAAsB;CAAK,CAAA;CAC5F,IAAM,CAAC,GAAG,KAAO,EAAI,KAAK,MAAM,GAAG,GAC7B,IAAO,EAAK,SAAS,KAAK,EAAE,IAAI,IAAK,EAAK,IAC1C,IAAW;EAAE,GAAG;EAAG;EAAK;EAAM;CAAM;CAC1C,OACE,kBAAA,GAAA,EAAA,UAAA,CACG,EAAK,SAAS,KACb,kBAAC,GAAD;EAAM,OAAO;EAAM,gBAAgB,MAAM,EAAI,GAAG,OAAO,CAAC,CAAC;EACvD,UAAA,kBAAC,GAAD;GAAU,WAAU;GACjB,UAAA,EAAK,KAAK,MACT,kBAAC,GAAD;IAAqB,OAAO;IACzB,UAAA,EAAQ,EAAE,QAAQ,CAAC;GACT,GAFK,CAEL,CACd;EACO,CAAA;CACN,CAAA,GAEN,IAAqC,MAAQ,QAAQ,kBAAC,GAAD,EAAmB,GAAI,EAAM,GAAhB,CAAgB,IAAI,kBAAC,GAAD;EAAgB,GAAI;EAAK,IAAI;CAAM,GAAxB,CAAwB,IAAnH,kBAAC,GAAD,EAAiB,GAAI,EAAM,GAAhB,CAAgB,CACnC,EAAA,CAAA;AAEN;AAEA,SAAS,EAAK,EAAE,QAAK,WAA8C;CACjE,IAAM,EAAE,SAAM,EAAY;CAC1B,OACE,kBAAC,GAAD;EAAQ,SAAQ;EAAQ,MAAK;EAAK,WAAU;EAAqB,eAAe,EAAI,GAAG,CAAI;EACxF,UAAA,EAAE,mBAAmB;CAChB,CAAA;AAEZ;AAEA,SAAS,EAAK,EAAE,QAAK,SAAM,UAAO,WAAQ,gBAAa,WAAQ,cAAW,MAAW;CACnF,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAe,EAAgB,GAC/B,IAAY,EAAM,WAClB,IAAc,EAAI,MAAM,IAAI,QAAQ,KAAK,IACzC,IAAS,EAAQ,SAAS,CAAW,IAAI,IAAc,KAAA,GACvD,IAAY,EAAI,MAAM,IAAI,MAAM,KAAK,IACrC,IAAQ,EAA4B,SAAS,CAAS,IAAK,IAA0B,QACrF,IAAgB,EAAI,MAAM,IAAI,UAAU,KAAK,IAC7C,IAAW,EAAU,SAAS,CAAa,IAAI,IAAgB,EAAU,IACzE,IAAoE;EAAE;EAAQ,MAAM,MAAS,SAAS,KAAA,IAAY;EAAM,UAAU,MAAa,EAAU,KAAK,KAAA,IAAY;CAAS,GACnL,KAAU,MAAkC,EAAI,GAAG,GAAM;EAAE,GAAG;EAAS,GAAG;CAAK,CAAC,GAChF,IAAS,EAAU;EAAE;EAAM;EAAQ;EAAM;EAAU;EAAa;CAAS,GAAG,EAAE,UAAO,CAAC,GACtF,IAAQ,EAAQ,GAAQ,CAAI;CAClC,OACE,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAU;EAAa,aAAW;EAA9D,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;KAA2B,UAAA;IAAU,CAAA,GACnD,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAI,GAAG,GAAG,EAAK,KAAK;KAClD,UAAA,EAAE,oBAAoB,EAAE,MAAM,EAAM,CAAC;IAChC,CAAA,CACL;;GACL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KAAQ,OAAO;KAAM,gBAAgB,MAAM,EAAO,EAAE,MAAM,MAAM,SAAS,KAAA,IAAa,OAAO,CAAC,EAAgB,CAAC;KAA/G,UAAA,CACE,kBAAC,GAAD;MAAe,MAAK;MAAK,cAAY,EAAE,mBAAmB;MACxD,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAc,EAAE,qBAAqB,GAAgB,EAAe,CAAA;KACtE,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAM,KAAK,MACV,kBAAC,GAAD;MAAoB,OAAO;MACxB,UAAA,EAAE,qBAAqB,GAAG;KACjB,GAFK,CAEL,CACb,EACY,CAAA,CACT;IACP,CAAA,GAAA,EAAU,SAAS,KAClB,kBAAC,GAAD;KAAQ,OAAO;KAAU,gBAAgB,MAAM,EAAO,EAAE,UAAU,MAAM,EAAU,KAAK,KAAA,IAAY,OAAO,CAAC,EAAE,CAAC;KAA9G,UAAA,CACE,kBAAC,GAAD;MAAe,MAAK;MAAK,cAAY,EAAE,uBAAuB;MAC5D,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAc,EAAa,CAAC,EAAe,CAAA;KAC7C,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAU,KAAK,MACd,kBAAC,GAAD;MAAoB,OAAO;MACxB,UAAA,EAAa,CAAC;KACL,GAFK,CAEL,CACb,EACY,CAAA,CACT;IAEP,CAAA,CAAA;;GACL,kBAAC,OAAD;IAAK,cAAY,EAAE,iBAAiB;IAAG,WAAU;IAAuB,aAAU;IAC/E,UAAA,CAAC,KAAA,GAAW,GAAG,CAAO,CAAC,CAAC,KAAK,MAC5B,kBAAC,GAAD;KAEE,MAAK;KACL,SAAS,MAAW,IAAI,YAAY;KACpC,gBAAc,MAAW;KACzB,cAAY,MAAM,KAAA,IAAY,KAAA,IAAY,MAAM,MAAM,EAAE,qBAAqB,IAAI,EAAE,YAAY;KAC/F,WAAU;KACV,eAAe,EAAO,EAAE,QAAQ,EAAE,CAAC;KAElC,UAAA,MAAM,KAAA,IAAY,EAAE,YAAY,IAAI,EAAE,YAAY;IAC7C,GATD,KAAK,KASJ,CACT;GACE,CAAA;GACJ,EAAO,WAAW,CAAC,EAAO,MAAM,SAC/B,kBAAC,GAAD,CAAO,CAAA,IACL,EAAO,SAAS,CAAC,EAAO,MAAM,SAChC,kBAAC,GAAD;IAAY,OAAO,EAAO;IAAO,SAAS,EAAE,yBAAyB;IAAG,OAAO,EAAO;GAAS,CAAA,IAC5F,EAAO,MAAM,SAGhB,kBAAC,MAAD;IAAI,WAAU;IAA8D,aAAW,EAAO;IAC3F,UAAA,EAAO,MAAM,KAAK,MACjB,kBAAC,MAAD;KAAe,WAAU;KAAwC,aAAU;KAAY,aAAW,EAAE;KAApG,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAU;OAAoD,eAAe,EAAI,GAAG,GAAG,EAAK,GAAG,EAAE,IAAI;OACxH,UAAA,EAAE,QAAQ,EAAE;MACP,CAAA,GACR,kBAAC,QAAD;OAAM,WAAU;OAAiC,UAAA,EAAE;MAAW,CAAA,CAC3D;KACL,CAAA,GAAA,kBAAC,QAAD;MAAM,WAAU;MAA6C,OAAO,EAAE,oBAAoB;MACvF,UAAA,IAAI,KAAK,aAAa,EAAE,QAAQ,CAAC,CAAC,OAAO,EAAE,KAAK;KAC7C,CAAA,CACJ;IAVK,GAAA,EAAE,EAUP,CACL;GACC,CAAA,IAhBJ,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,cAAc;GAAK,CAAA;GAkB5D,EAAO,WACN,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,WAAU;IAAsB,UAAU,EAAO;IAAa,eAAe,KAAK,EAAO,SAAS;IACnI,UAAA,EAAO,cAAc,EAAE,gBAAgB,IAAI,EAAE,iBAAiB;GACzD,CAAA;EAEP;;AAET;AAEA,SAAS,EAAO,EAAE,QAAK,SAAM,WAAQ,WAAQ,cAAgB;CAC3D,OACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,kBAAC,GAAD;GAAW;GAAW;EAAO,CAAA,GAC7B,kBAAC,GAAD;GACE,IAAI;GACE;GACE;GACA;GACC;GACT,YAAY,MAAM,EAAI,GAAG,GAAG,EAAK,GAAG,EAAE,MAAM,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC;EACzE,CAAA,CACE;;AAET;AAEA,SAAS,EAAK,EAAE,QAAK,SAAM,WAAQ,WAAQ,YAAS,kBAAe,SAA4B;CAC7F,OACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,kBAAC,GAAD;GAAW;GAAW;EAAO,CAAA,GAC7B,kBAAC,GAAD;GACM;GACE;GACE;GACA;GACC;GACT,WAAW,MAAM,EAAI,GAAG,GAAG,EAAK,GAAG,EAAE,QAAQ,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC;GAEvE,WAAA,MACA,kBAAC,GAAD;IACS;IACK;IACJ;IACC;IACM;IACf,aAAa,MAAY,EAAO,OAAO,OAAO,EAAM,IAAI,EAAE,OAAO,EAAE,YAAY,EAAQ,EAAE,CAAC;GAC3F,CAAA;EAEQ,CAAA,CACV;;AAET"}
@@ -204,7 +204,7 @@ function U({ item: t, path: s, video: c, client: l, onError: u }) {
204
204
  });
205
205
  }
206
206
  function W({ item: e, path: t, video: r, preset: i, client: a, onError: o }) {
207
- let { t: s } = n(), [l, u] = N(!1), { image: m, reload: h } = c(e.kind, e.id, i, { client: a });
207
+ let { t: s } = n(), [l, u] = N(!1), { image: m, reload: h } = c(e, i, { client: a });
208
208
  return /* @__PURE__ */ I("section", {
209
209
  className: "grid gap-2",
210
210
  "data-ckui": "media-poster",
@@ -1 +1 @@
1
- {"version":3,"file":"media-item-panel.js","names":[],"sources":["../../../src/components/admin/media-item-panel.tsx"],"sourcesContent":["import { useId, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { FileInfo, RefBody } from \"../../client/generated/wire.js\";\nimport type { Progress } from \"../../client/media/client.js\";\nimport { samePath } from \"../../client/media/client.js\";\nimport { isProcessing } from \"../../client/media/windows.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useErrorReporter, useGuarded } from \"../../react/context.js\";\nimport { usePublicImage } from \"../../react/public.js\";\nimport { useMediaRead } from \"../../react/read.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\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 { Switch } from \"#ckui/ui/switch\";\nimport { EncodeProgress } from \"../encode-progress.js\";\nimport { SlotImage } from \"../slot-image.js\";\nimport { VideoPosterPicker } from \"../video-poster-picker.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, useConfirm, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\nexport interface MediaItemPanelProps extends AdminFlowProps {\n /** The video item. */\n item: RefBody;\n /** The video's upload path. Default \"source\". */\n source?: string;\n /** The subtitle uploads' path pattern; null hides them. Default \"subs/{name}\". */\n subtitles?: string | null;\n /** The poster's upload path (its frames come from the video); null hides it. Default \"poster\". */\n poster?: string | null;\n /** The poster's public preset. Default \"poster\". */\n posterPreset?: string;\n /** Subtitle languages offered: codes, or codes with labels. Without them the language is typed. */\n languages?: readonly Choice[];\n}\n\ntype ItemState = \"ready\" | \"processing\" | \"failed\" | \"empty\";\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst under = (pattern: string) => pattern.slice(0, pattern.indexOf(\"{name}\"));\n\n/**\n * One video item for its editors: its encode state and progress, the source\n * (replace it), subtitle sidecars (language, label, forced), the poster from\n * a frame or an image, and regenerate, which retries failed uploads.\n */\nexport function MediaItemPanel({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: MediaItemPanelProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"media-item-panel\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Panel {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<MediaItemPanelProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Panel({ item, client, source = \"source\", subtitles = \"subs/{name}\", poster = \"poster\", posterPreset = \"poster\", languages, onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const r = useMediaRead(item, { editor: true, client });\n const regen = useAction(\"media.save\", onError);\n if (!r.read) {\n if (r.error?.code === \"not_found\") return <NoAccess>{t(\"mediaPanel.noAccess\")}</NoAccess>;\n return r.error ? <LoadFailed error={r.error} message={t(\"mediaPanel.loadFailed\")} retry={r.reload} /> : <Rows count={2} />;\n }\n if (!r.read.uploads) return <NoAccess>{t(\"mediaPanel.noAccess\")}</NoAccess>;\n const uploads = r.read.files.filter((f) => f.upload && !f.unattached);\n const video = uploads.find((f) => samePath(f.path, source));\n const failed = uploads.filter((f) => f.failed);\n const state: ItemState = !video ? \"empty\" : failed.length ? \"failed\" : uploads.some(isProcessing) ? \"processing\" : \"ready\";\n const subs = subtitles === null ? [] : uploads.filter((f) => f.path.startsWith(under(subtitles)));\n return (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"mediaPanel.title\")}</h2>\n <Badge variant={state === \"failed\" ? \"destructive\" : state === \"ready\" ? \"outline\" : \"secondary\"} data-ckui=\"media-state\" data-state={state}>\n {t(`mediaPanel.${state}`)}\n </Badge>\n <Button size=\"sm\" variant=\"outline\" className=\"ms-auto\" disabled={regen.pending || !uploads.length} onClick={() => void regen.run(() => client.media.commit(item, [{ op: \"regenerate\" }]))}>\n {t(\"mediaPanel.regenerate\")}\n </Button>\n </div>\n <ErrorLine error={regen.error} />\n {failed.map((f) => (\n <Alert key={f.path} variant=\"destructive\" data-ckui=\"media-failure\">\n <AlertDescription>\n {t(\"mediaPanel.failedFile\", { name: f.path.split(\"/\").pop()!, reason: m.error({ code: f.failed!.code, message: f.failed!.message, status: 422 }) })}\n </AlertDescription>\n </Alert>\n ))}\n <SourceSection item={item} path={source} video={video} client={client} onError={onError} />\n {poster !== null && video && <PosterSection item={item} path={poster} video={source} preset={posterPreset} client={client} onError={onError} />}\n {subtitles !== null && <Subtitles item={item} pattern={subtitles} files={subs} languages={languages} client={client} onError={onError} />}\n </>\n );\n}\n\nfunction bytes(n?: number): string {\n if (!n) return \"\";\n const mb = n / (1 << 20);\n return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb.toFixed(mb < 10 ? 1 : 0)} MB`;\n}\n\nfunction duration(s?: number): string {\n if (!s) return \"\";\n const m = Math.floor(s / 60);\n const sec = Math.round(s - m * 60);\n return `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nfunction SourceSection({ item, path, video, client, onError }: { item: RefBody; path: string; video?: FileInfo; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n const [progress, setProgress] = useState<Progress | null>(null);\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [error, setError] = useState<ContentKitError | null>(null);\n const replace = async (f: File) => {\n setError(null);\n try {\n await guarded(() => client.media.put(f, { ref: item, path, wait: false, onProgress: setProgress }));\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"media.save\");\n } finally {\n setProgress(null);\n }\n };\n const facts = video ? [video.w && video.h ? `${video.w}×${video.h}` : \"\", duration(video.dur), bytes(video.size)].filter(Boolean).join(\" · \") : \"\";\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-source\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.source\")}</h3>\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"min-w-0 flex-1 text-muted-foreground\">{video ? facts || video.path : t(\"mediaPanel.noSource\")}</span>\n <Button size=\"sm\" variant=\"outline\" disabled={!!progress} onClick={() => file.current?.click()}>\n {t(video ? \"mediaPanel.replace\" : \"mediaPanel.upload\")}\n </Button>\n <input\n ref={file}\n type=\"file\"\n accept=\"video/*\"\n hidden\n aria-label={t(\"mediaPanel.source\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) void replace(f);\n }}\n />\n </div>\n {progress && (\n <div className=\"grid gap-1\" role=\"status\">\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full bg-primary transition-[width]\" style={{ width: `${progress.total ? Math.round((progress.loaded / progress.total) * 100) : 0}%` }} />\n </div>\n <span className=\"text-xs text-muted-foreground\">{t(`mediaPanel.phase_${progress.phase}`)}</span>\n </div>\n )}\n {video && isProcessing(video) && !progress && <EncodeProgress progress={video.progress} />}\n <ErrorLine error={error} />\n </section>\n );\n}\n\nfunction PosterSection({ item, path, video, preset, client, onError }: { item: RefBody; path: string; video: string; preset: string; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const [open, setOpen] = useState(false);\n const { image, reload } = usePublicImage(item.kind, item.id, preset, { client });\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-poster\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.poster\")}</h3>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-40 overflow-hidden rounded-md border border-border\">\n <SlotImage image={image} fallback={<div className=\"aspect-video bg-muted\" />} />\n </div>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setOpen(true)}>\n {t(\"mediaPanel.choosePoster\")}\n </Button>\n </div>\n <VideoPosterPicker open={open} onOpenChange={setOpen} item={item} video={video} path={path} image={image} client={client} onError={onError} onChange={() => reload()} />\n </section>\n );\n}\n\nfunction Subtitles({ item, pattern, files, languages: offered, client, onError }: { item: RefBody; pattern: string; files: FileInfo[]; languages?: readonly Choice[]; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const ids = useId();\n const languages = choices(offered);\n const [lang, setLang] = useState(languages[0]?.value ?? \"\");\n const [label, setLabel] = useState(\"\");\n const [forced, setForced] = useState(false);\n const [file, setFile] = useState<File | null>(null);\n const input = useRef<HTMLInputElement>(null);\n const add = useAction(\"media.save\", onError);\n const remove = useAction(\"media.save\", onError);\n const { confirm, dialog } = useConfirm();\n const save = async () => {\n if (!file || !lang.trim()) return;\n const code = lang.trim();\n const name = `${code}${forced ? \"-forced\" : \"\"}`;\n const done = await add.run(() =>\n client.media.put(file, {\n ref: item,\n path: pattern.replace(\"{name}\", name),\n wait: false,\n meta: { lang: code, label: label.trim() || (languages.find((l) => l.value === code)?.label ?? code), forced },\n }),\n );\n if (done) {\n setFile(null);\n if (input.current) input.current.value = \"\";\n setLabel(\"\");\n setForced(false);\n }\n };\n const drop = async (f: FileInfo) => {\n if (await confirm({ title: t(\"mediaPanel.removeSubtitleTitle\", { name: String(f.meta?.label ?? f.path) }), confirm: t(\"common.remove\") })) {\n await remove.run(() => client.media.commit(item, [{ op: \"remove\", path: f.path }]));\n }\n };\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-subtitles\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.subtitles\")}</h3>\n {files.length ? (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {files.map((f) => (\n <li key={f.path} className=\"flex flex-wrap items-center gap-2 p-2.5\" data-ckui=\"subtitle\">\n <Badge variant=\"outline\">{String(f.meta?.lang ?? \"?\")}</Badge>\n <span className=\"min-w-0 flex-1 break-words\">{String(f.meta?.label ?? f.path.split(\"/\").pop())}</span>\n {f.meta?.forced === true && <Badge variant=\"secondary\">{t(\"mediaPanel.forced\")}</Badge>}\n {f.failed && <Badge variant=\"destructive\">{t(\"mediaPanel.failed\")}</Badge>}\n <Button size=\"xs\" variant=\"ghost\" disabled={remove.pending} onClick={() => void drop(f)}>\n {t(\"common.remove\")}\n </Button>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"mediaPanel.noSubtitles\")}</p>\n )}\n <form\n className=\"grid gap-2 rounded-lg border border-dashed border-border p-3 sm:grid-cols-2\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-file`}>{t(\"mediaPanel.subtitleFile\")}</Label>\n <Input id={`${ids}-file`} ref={input} type=\"file\" accept=\".vtt,.srt,text/vtt,application/x-subrip\" onChange={(e) => setFile(e.target.files?.[0] ?? null)} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"mediaPanel.language\")}</Label>\n {languages.length ? (\n <Select value={lang} onValueChange={(v) => setLang(String(v))}>\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 ) : (\n <Input id={`${ids}-lang`} value={lang} placeholder=\"en\" onChange={(e) => setLang(e.target.value)} />\n )}\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-label`}>{t(\"mediaPanel.label\")}</Label>\n <Input id={`${ids}-label`} value={label} placeholder={t(\"mediaPanel.labelPlaceholder\")} onChange={(e) => setLabel(e.target.value)} />\n </div>\n <div className=\"flex items-center gap-2 self-end\">\n <Switch id={`${ids}-forced`} checked={forced} onCheckedChange={setForced} />\n <Label htmlFor={`${ids}-forced`}>{t(\"mediaPanel.forcedLabel\")}</Label>\n </div>\n <ErrorLine error={add.error} className=\"sm:col-span-2\" />\n <Button type=\"submit\" size=\"sm\" className=\"justify-self-start\" disabled={add.pending || !file || !lang.trim()}>\n {add.pending ? t(\"common.saving\") : t(\"mediaPanel.addSubtitle\")}\n </Button>\n </form>\n <ErrorLine error={remove.error} />\n {dialog}\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"media-item-panel.js","names":[],"sources":["../../../src/components/admin/media-item-panel.tsx"],"sourcesContent":["import { useId, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { FileInfo, RefBody } from \"../../client/generated/wire.js\";\nimport type { Progress } from \"../../client/media/client.js\";\nimport { samePath } from \"../../client/media/client.js\";\nimport { isProcessing } from \"../../client/media/windows.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useErrorReporter, useGuarded } from \"../../react/context.js\";\nimport { usePublicImage } from \"../../react/public.js\";\nimport { useMediaRead } from \"../../react/read.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\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 { Switch } from \"#ckui/ui/switch\";\nimport { EncodeProgress } from \"../encode-progress.js\";\nimport { SlotImage } from \"../slot-image.js\";\nimport { VideoPosterPicker } from \"../video-poster-picker.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, useConfirm, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\nexport interface MediaItemPanelProps extends AdminFlowProps {\n /** The video item. */\n item: RefBody;\n /** The video's upload path. Default \"source\". */\n source?: string;\n /** The subtitle uploads' path pattern; null hides them. Default \"subs/{name}\". */\n subtitles?: string | null;\n /** The poster's upload path (its frames come from the video); null hides it. Default \"poster\". */\n poster?: string | null;\n /** The poster's public preset. Default \"poster\". */\n posterPreset?: string;\n /** Subtitle languages offered: codes, or codes with labels. Without them the language is typed. */\n languages?: readonly Choice[];\n}\n\ntype ItemState = \"ready\" | \"processing\" | \"failed\" | \"empty\";\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst under = (pattern: string) => pattern.slice(0, pattern.indexOf(\"{name}\"));\n\n/**\n * One video item for its editors: its encode state and progress, the source\n * (replace it), subtitle sidecars (language, label, forced), the poster from\n * a frame or an image, and regenerate, which retries failed uploads.\n */\nexport function MediaItemPanel({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: MediaItemPanelProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"media-item-panel\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Panel {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<MediaItemPanelProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Panel({ item, client, source = \"source\", subtitles = \"subs/{name}\", poster = \"poster\", posterPreset = \"poster\", languages, onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const r = useMediaRead(item, { editor: true, client });\n const regen = useAction(\"media.save\", onError);\n if (!r.read) {\n if (r.error?.code === \"not_found\") return <NoAccess>{t(\"mediaPanel.noAccess\")}</NoAccess>;\n return r.error ? <LoadFailed error={r.error} message={t(\"mediaPanel.loadFailed\")} retry={r.reload} /> : <Rows count={2} />;\n }\n if (!r.read.uploads) return <NoAccess>{t(\"mediaPanel.noAccess\")}</NoAccess>;\n const uploads = r.read.files.filter((f) => f.upload && !f.unattached);\n const video = uploads.find((f) => samePath(f.path, source));\n const failed = uploads.filter((f) => f.failed);\n const state: ItemState = !video ? \"empty\" : failed.length ? \"failed\" : uploads.some(isProcessing) ? \"processing\" : \"ready\";\n const subs = subtitles === null ? [] : uploads.filter((f) => f.path.startsWith(under(subtitles)));\n return (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"mediaPanel.title\")}</h2>\n <Badge variant={state === \"failed\" ? \"destructive\" : state === \"ready\" ? \"outline\" : \"secondary\"} data-ckui=\"media-state\" data-state={state}>\n {t(`mediaPanel.${state}`)}\n </Badge>\n <Button size=\"sm\" variant=\"outline\" className=\"ms-auto\" disabled={regen.pending || !uploads.length} onClick={() => void regen.run(() => client.media.commit(item, [{ op: \"regenerate\" }]))}>\n {t(\"mediaPanel.regenerate\")}\n </Button>\n </div>\n <ErrorLine error={regen.error} />\n {failed.map((f) => (\n <Alert key={f.path} variant=\"destructive\" data-ckui=\"media-failure\">\n <AlertDescription>\n {t(\"mediaPanel.failedFile\", { name: f.path.split(\"/\").pop()!, reason: m.error({ code: f.failed!.code, message: f.failed!.message, status: 422 }) })}\n </AlertDescription>\n </Alert>\n ))}\n <SourceSection item={item} path={source} video={video} client={client} onError={onError} />\n {poster !== null && video && <PosterSection item={item} path={poster} video={source} preset={posterPreset} client={client} onError={onError} />}\n {subtitles !== null && <Subtitles item={item} pattern={subtitles} files={subs} languages={languages} client={client} onError={onError} />}\n </>\n );\n}\n\nfunction bytes(n?: number): string {\n if (!n) return \"\";\n const mb = n / (1 << 20);\n return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb.toFixed(mb < 10 ? 1 : 0)} MB`;\n}\n\nfunction duration(s?: number): string {\n if (!s) return \"\";\n const m = Math.floor(s / 60);\n const sec = Math.round(s - m * 60);\n return `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nfunction SourceSection({ item, path, video, client, onError }: { item: RefBody; path: string; video?: FileInfo; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n const [progress, setProgress] = useState<Progress | null>(null);\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [error, setError] = useState<ContentKitError | null>(null);\n const replace = async (f: File) => {\n setError(null);\n try {\n await guarded(() => client.media.put(f, { ref: item, path, wait: false, onProgress: setProgress }));\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"media.save\");\n } finally {\n setProgress(null);\n }\n };\n const facts = video ? [video.w && video.h ? `${video.w}×${video.h}` : \"\", duration(video.dur), bytes(video.size)].filter(Boolean).join(\" · \") : \"\";\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-source\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.source\")}</h3>\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"min-w-0 flex-1 text-muted-foreground\">{video ? facts || video.path : t(\"mediaPanel.noSource\")}</span>\n <Button size=\"sm\" variant=\"outline\" disabled={!!progress} onClick={() => file.current?.click()}>\n {t(video ? \"mediaPanel.replace\" : \"mediaPanel.upload\")}\n </Button>\n <input\n ref={file}\n type=\"file\"\n accept=\"video/*\"\n hidden\n aria-label={t(\"mediaPanel.source\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) void replace(f);\n }}\n />\n </div>\n {progress && (\n <div className=\"grid gap-1\" role=\"status\">\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full bg-primary transition-[width]\" style={{ width: `${progress.total ? Math.round((progress.loaded / progress.total) * 100) : 0}%` }} />\n </div>\n <span className=\"text-xs text-muted-foreground\">{t(`mediaPanel.phase_${progress.phase}`)}</span>\n </div>\n )}\n {video && isProcessing(video) && !progress && <EncodeProgress progress={video.progress} />}\n <ErrorLine error={error} />\n </section>\n );\n}\n\nfunction PosterSection({ item, path, video, preset, client, onError }: { item: RefBody; path: string; video: string; preset: string; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const [open, setOpen] = useState(false);\n const { image, reload } = usePublicImage(item, preset, { client });\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-poster\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.poster\")}</h3>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-40 overflow-hidden rounded-md border border-border\">\n <SlotImage image={image} fallback={<div className=\"aspect-video bg-muted\" />} />\n </div>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setOpen(true)}>\n {t(\"mediaPanel.choosePoster\")}\n </Button>\n </div>\n <VideoPosterPicker open={open} onOpenChange={setOpen} item={item} video={video} path={path} image={image} client={client} onError={onError} onChange={() => reload()} />\n </section>\n );\n}\n\nfunction Subtitles({ item, pattern, files, languages: offered, client, onError }: { item: RefBody; pattern: string; files: FileInfo[]; languages?: readonly Choice[]; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const ids = useId();\n const languages = choices(offered);\n const [lang, setLang] = useState(languages[0]?.value ?? \"\");\n const [label, setLabel] = useState(\"\");\n const [forced, setForced] = useState(false);\n const [file, setFile] = useState<File | null>(null);\n const input = useRef<HTMLInputElement>(null);\n const add = useAction(\"media.save\", onError);\n const remove = useAction(\"media.save\", onError);\n const { confirm, dialog } = useConfirm();\n const save = async () => {\n if (!file || !lang.trim()) return;\n const code = lang.trim();\n const name = `${code}${forced ? \"-forced\" : \"\"}`;\n const done = await add.run(() =>\n client.media.put(file, {\n ref: item,\n path: pattern.replace(\"{name}\", name),\n wait: false,\n meta: { lang: code, label: label.trim() || (languages.find((l) => l.value === code)?.label ?? code), forced },\n }),\n );\n if (done) {\n setFile(null);\n if (input.current) input.current.value = \"\";\n setLabel(\"\");\n setForced(false);\n }\n };\n const drop = async (f: FileInfo) => {\n if (await confirm({ title: t(\"mediaPanel.removeSubtitleTitle\", { name: String(f.meta?.label ?? f.path) }), confirm: t(\"common.remove\") })) {\n await remove.run(() => client.media.commit(item, [{ op: \"remove\", path: f.path }]));\n }\n };\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-subtitles\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.subtitles\")}</h3>\n {files.length ? (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {files.map((f) => (\n <li key={f.path} className=\"flex flex-wrap items-center gap-2 p-2.5\" data-ckui=\"subtitle\">\n <Badge variant=\"outline\">{String(f.meta?.lang ?? \"?\")}</Badge>\n <span className=\"min-w-0 flex-1 break-words\">{String(f.meta?.label ?? f.path.split(\"/\").pop())}</span>\n {f.meta?.forced === true && <Badge variant=\"secondary\">{t(\"mediaPanel.forced\")}</Badge>}\n {f.failed && <Badge variant=\"destructive\">{t(\"mediaPanel.failed\")}</Badge>}\n <Button size=\"xs\" variant=\"ghost\" disabled={remove.pending} onClick={() => void drop(f)}>\n {t(\"common.remove\")}\n </Button>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"mediaPanel.noSubtitles\")}</p>\n )}\n <form\n className=\"grid gap-2 rounded-lg border border-dashed border-border p-3 sm:grid-cols-2\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-file`}>{t(\"mediaPanel.subtitleFile\")}</Label>\n <Input id={`${ids}-file`} ref={input} type=\"file\" accept=\".vtt,.srt,text/vtt,application/x-subrip\" onChange={(e) => setFile(e.target.files?.[0] ?? null)} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"mediaPanel.language\")}</Label>\n {languages.length ? (\n <Select value={lang} onValueChange={(v) => setLang(String(v))}>\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 ) : (\n <Input id={`${ids}-lang`} value={lang} placeholder=\"en\" onChange={(e) => setLang(e.target.value)} />\n )}\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-label`}>{t(\"mediaPanel.label\")}</Label>\n <Input id={`${ids}-label`} value={label} placeholder={t(\"mediaPanel.labelPlaceholder\")} onChange={(e) => setLabel(e.target.value)} />\n </div>\n <div className=\"flex items-center gap-2 self-end\">\n <Switch id={`${ids}-forced`} checked={forced} onCheckedChange={setForced} />\n <Label htmlFor={`${ids}-forced`}>{t(\"mediaPanel.forcedLabel\")}</Label>\n </div>\n <ErrorLine error={add.error} className=\"sm:col-span-2\" />\n <Button type=\"submit\" size=\"sm\" className=\"justify-self-start\" disabled={add.pending || !file || !lang.trim()}>\n {add.pending ? t(\"common.saving\") : t(\"mediaPanel.addSubtitle\")}\n </Button>\n </form>\n <ErrorLine error={remove.error} />\n {dialog}\n </section>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { ReactNode } from 'react';
2
+ import { Card, CardAccess } from '../../client/generated/wire.js';
3
+ import { Translator } from '../../i18n/messages.js';
4
+ import { GroupLike, Labels } from '../groups/parts.js';
5
+ export interface ContentCardProps {
6
+ card: Card;
7
+ /** The card's link; default its canonical path. */
8
+ href?: (card: Card) => string | undefined;
9
+ /** Before the card's link is followed: a click signal. */
10
+ onOpen?: (card: Card) => void;
11
+ /** Cover shape: "portrait" (3:4, readers) or "landscape" (16:9, players). Default portrait. */
12
+ shape?: "portrait" | "landscape";
13
+ /** The group kinds whose chips show, in order; default every kind the card names. */
14
+ groupKinds?: readonly string[];
15
+ /** Chips per group kind. Default 3. */
16
+ chips?: number;
17
+ /** Tiers the site sells, so a lock names them; others show as "for sale". */
18
+ tiers?: readonly string[];
19
+ /** Display names of kinds, traits and tiers by identifier. */
20
+ labels?: Labels;
21
+ /** Show the owner group (a channel) when the card has one; default true, false on that group's own feed. */
22
+ owner?: boolean;
23
+ /** The owner group's link; default its canonical path. */
24
+ ownerHref?: (g: GroupLike) => string | undefined;
25
+ /** Below the meta line: the host's own. */
26
+ children?: ReactNode;
27
+ className?: string;
28
+ }
29
+ /** Why a card is locked, in words (its price when it is on sale); null when it opens. */
30
+ export declare function lockLabel(m: Translator, a: CardAccess, tiers?: readonly string[], labels?: Labels): string | null;
31
+ /** One content in a listing: cover, title, version, groups, counts, the reader's progress and why it is locked. */
32
+ export declare function ContentCard({ card, href, onOpen, shape, groupKinds, chips, tiers, labels, owner, ownerHref, children, className }: ContentCardProps): import('react').JSX.Element;
33
+ /** The group that owns a content (a channel): its avatar and name, linking to its page. */
34
+ export declare function OwnerChip({ group, href, className }: {
35
+ group: GroupLike;
36
+ href?: (g: GroupLike) => string | undefined;
37
+ className?: string;
38
+ }): import('react').JSX.Element;
39
+ export interface PostCardProps extends Omit<ContentCardProps, "shape" | "chips" | "groupKinds"> {
40
+ /** Above the title: the post's channel or author; default its owner group's chip. */
41
+ byline?: ReactNode;
42
+ }
43
+ /** A post in a feed: a wide cover, its title, when it was published and what unlocking it costs. */
44
+ export declare function PostCard({ card, href, onOpen, tiers, labels, byline, owner, ownerHref, children, className }: PostCardProps): import('react').JSX.Element;