@openrails/contentkit-ui 0.0.0-stage → 0.68.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/README.md +470 -2
  2. package/dist/appearance.d.ts +38 -0
  3. package/dist/appearance.js +15 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/assets/hash.worker-BYfo-7MQ.js.map +1 -0
  6. package/dist/client/aspect.d.ts +6 -0
  7. package/dist/client/aspect.js +17 -0
  8. package/dist/client/aspect.js.map +1 -0
  9. package/dist/client/client.d.ts +53 -0
  10. package/dist/client/client.js +36 -0
  11. package/dist/client/client.js.map +1 -0
  12. package/dist/client/codes.d.ts +12 -0
  13. package/dist/client/codes.js +19 -0
  14. package/dist/client/codes.js.map +1 -0
  15. package/dist/client/content/bans.d.ts +15 -0
  16. package/dist/client/content/bans.js +40 -0
  17. package/dist/client/content/bans.js.map +1 -0
  18. package/dist/client/content/comments.d.ts +36 -0
  19. package/dist/client/content/comments.js +95 -0
  20. package/dist/client/content/comments.js.map +1 -0
  21. package/dist/client/content/favorites.d.ts +14 -0
  22. package/dist/client/content/favorites.js +42 -0
  23. package/dist/client/content/favorites.js.map +1 -0
  24. package/dist/client/content/moderation.d.ts +18 -0
  25. package/dist/client/content/moderation.js +37 -0
  26. package/dist/client/content/moderation.js.map +1 -0
  27. package/dist/client/content/polls.d.ts +47 -0
  28. package/dist/client/content/polls.js +151 -0
  29. package/dist/client/content/polls.js.map +1 -0
  30. package/dist/client/content/posts.d.ts +44 -0
  31. package/dist/client/content/posts.js +92 -0
  32. package/dist/client/content/posts.js.map +1 -0
  33. package/dist/client/content/reactions.d.ts +12 -0
  34. package/dist/client/content/reactions.js +36 -0
  35. package/dist/client/content/reactions.js.map +1 -0
  36. package/dist/client/content/types.d.ts +91 -0
  37. package/dist/client/content/types.js +8 -0
  38. package/dist/client/content/types.js.map +1 -0
  39. package/dist/client/crop.d.ts +32 -0
  40. package/dist/client/crop.js +103 -0
  41. package/dist/client/crop.js.map +1 -0
  42. package/dist/client/errors.d.ts +54 -0
  43. package/dist/client/errors.js +80 -0
  44. package/dist/client/errors.js.map +1 -0
  45. package/dist/client/gallery.d.ts +43 -0
  46. package/dist/client/gallery.js +86 -0
  47. package/dist/client/gallery.js.map +1 -0
  48. package/dist/client/generated/error-codes.d.ts +5 -0
  49. package/dist/client/generated/error-codes.js +131 -0
  50. package/dist/client/generated/error-codes.js.map +1 -0
  51. package/dist/client/generated/routes.d.ts +1086 -0
  52. package/dist/client/generated/routes.js +1400 -0
  53. package/dist/client/generated/routes.js.map +1 -0
  54. package/dist/client/generated/wire.d.ts +630 -0
  55. package/dist/client/generated/wire.js +6 -0
  56. package/dist/client/generated/wire.js.map +1 -0
  57. package/dist/client/http.d.ts +83 -0
  58. package/dist/client/http.js +82 -0
  59. package/dist/client/http.js.map +1 -0
  60. package/dist/client/image.d.ts +24 -0
  61. package/dist/client/image.js +65 -0
  62. package/dist/client/image.js.map +1 -0
  63. package/dist/client/index.d.ts +33 -0
  64. package/dist/client/media/api.d.ts +36 -0
  65. package/dist/client/media/api.js +61 -0
  66. package/dist/client/media/api.js.map +1 -0
  67. package/dist/client/media/client.d.ts +247 -0
  68. package/dist/client/media/client.js +407 -0
  69. package/dist/client/media/client.js.map +1 -0
  70. package/dist/client/media/encode.d.ts +3 -0
  71. package/dist/client/media/encode.js +8 -0
  72. package/dist/client/media/encode.js.map +1 -0
  73. package/dist/client/media/hash-stream.d.ts +6 -0
  74. package/dist/client/media/hash-stream.js +16 -0
  75. package/dist/client/media/hash-stream.js.map +1 -0
  76. package/dist/client/media/hash.d.ts +12 -0
  77. package/dist/client/media/hash.js +48 -0
  78. package/dist/client/media/hash.js.map +1 -0
  79. package/dist/client/media/hash.worker-inline.js +18 -0
  80. package/dist/client/media/hash.worker-inline.js.map +1 -0
  81. package/dist/client/media/pacer.d.ts +21 -0
  82. package/dist/client/media/pacer.js +25 -0
  83. package/dist/client/media/pacer.js.map +1 -0
  84. package/dist/client/media/queue.d.ts +112 -0
  85. package/dist/client/media/queue.js +274 -0
  86. package/dist/client/media/queue.js.map +1 -0
  87. package/dist/client/media/ref.d.ts +6 -0
  88. package/dist/client/media/ref.js +10 -0
  89. package/dist/client/media/ref.js.map +1 -0
  90. package/dist/client/media/rules.d.ts +54 -0
  91. package/dist/client/media/rules.js +143 -0
  92. package/dist/client/media/rules.js.map +1 -0
  93. package/dist/client/media/transport.d.ts +13 -0
  94. package/dist/client/media/transport.js +33 -0
  95. package/dist/client/media/transport.js.map +1 -0
  96. package/dist/client/media/windows.d.ts +15 -0
  97. package/dist/client/media/windows.js +39 -0
  98. package/dist/client/media/windows.js.map +1 -0
  99. package/dist/client/playback.d.ts +200 -0
  100. package/dist/client/playback.js +142 -0
  101. package/dist/client/playback.js.map +1 -0
  102. package/dist/client/player.d.ts +102 -0
  103. package/dist/client/player.js +155 -0
  104. package/dist/client/player.js.map +1 -0
  105. package/dist/client/public.d.ts +25 -0
  106. package/dist/client/public.js +18 -0
  107. package/dist/client/public.js.map +1 -0
  108. package/dist/client/rendition.d.ts +19 -0
  109. package/dist/client/rendition.js +16 -0
  110. package/dist/client/rendition.js.map +1 -0
  111. package/dist/client/route.d.ts +39 -0
  112. package/dist/client/route.js +31 -0
  113. package/dist/client/route.js.map +1 -0
  114. package/dist/client/taxonomy.d.ts +30 -0
  115. package/dist/client/taxonomy.js +77 -0
  116. package/dist/client/taxonomy.js.map +1 -0
  117. package/dist/client.js +33 -0
  118. package/dist/components/encode-progress.d.ts +15 -0
  119. package/dist/components/encode-progress.js +49 -0
  120. package/dist/components/encode-progress.js.map +1 -0
  121. package/dist/components/hover-preview.d.ts +29 -0
  122. package/dist/components/hover-preview.js +49 -0
  123. package/dist/components/hover-preview.js.map +1 -0
  124. package/dist/components/image-crop-dialog.d.ts +48 -0
  125. package/dist/components/image-crop-dialog.js +359 -0
  126. package/dist/components/image-crop-dialog.js.map +1 -0
  127. package/dist/components/lazy-mount.d.ts +10 -0
  128. package/dist/components/lazy-mount.js +22 -0
  129. package/dist/components/lazy-mount.js.map +1 -0
  130. package/dist/components/media-folder-editor.d.ts +59 -0
  131. package/dist/components/media-folder-editor.js +611 -0
  132. package/dist/components/media-folder-editor.js.map +1 -0
  133. package/dist/components/media-gallery.d.ts +54 -0
  134. package/dist/components/media-gallery.js +597 -0
  135. package/dist/components/media-gallery.js.map +1 -0
  136. package/dist/components/media-readiness-notice.d.ts +24 -0
  137. package/dist/components/media-readiness-notice.js +61 -0
  138. package/dist/components/media-readiness-notice.js.map +1 -0
  139. package/dist/components/player-controls.d.ts +71 -0
  140. package/dist/components/player-controls.js +264 -0
  141. package/dist/components/player-controls.js.map +1 -0
  142. package/dist/components/progress.d.ts +5 -0
  143. package/dist/components/progress.js +14 -0
  144. package/dist/components/progress.js.map +1 -0
  145. package/dist/components/rendition-img.d.ts +34 -0
  146. package/dist/components/rendition-img.js +78 -0
  147. package/dist/components/rendition-img.js.map +1 -0
  148. package/dist/components/slot-editor.d.ts +95 -0
  149. package/dist/components/slot-editor.js +182 -0
  150. package/dist/components/slot-editor.js.map +1 -0
  151. package/dist/components/slot-image.d.ts +23 -0
  152. package/dist/components/slot-image.js +48 -0
  153. package/dist/components/slot-image.js.map +1 -0
  154. package/dist/components/slot-upload.d.ts +41 -0
  155. package/dist/components/slot-upload.js +215 -0
  156. package/dist/components/slot-upload.js.map +1 -0
  157. package/dist/components/social/bans.d.ts +42 -0
  158. package/dist/components/social/bans.js +300 -0
  159. package/dist/components/social/bans.js.map +1 -0
  160. package/dist/components/social/comments.d.ts +42 -0
  161. package/dist/components/social/comments.js +517 -0
  162. package/dist/components/social/comments.js.map +1 -0
  163. package/dist/components/social/engagement.d.ts +45 -0
  164. package/dist/components/social/engagement.js +129 -0
  165. package/dist/components/social/engagement.js.map +1 -0
  166. package/dist/components/social/index.d.ts +6 -0
  167. package/dist/components/social/moderation.d.ts +32 -0
  168. package/dist/components/social/moderation.js +383 -0
  169. package/dist/components/social/moderation.js.map +1 -0
  170. package/dist/components/social/parts.d.ts +50 -0
  171. package/dist/components/social/parts.js +106 -0
  172. package/dist/components/social/parts.js.map +1 -0
  173. package/dist/components/social/poll-editor.d.ts +34 -0
  174. package/dist/components/social/poll-editor.js +567 -0
  175. package/dist/components/social/poll-editor.js.map +1 -0
  176. package/dist/components/social/poll.d.ts +30 -0
  177. package/dist/components/social/poll.js +252 -0
  178. package/dist/components/social/poll.js.map +1 -0
  179. package/dist/components/sortable-list.d.ts +29 -0
  180. package/dist/components/sortable-list.js +121 -0
  181. package/dist/components/sortable-list.js.map +1 -0
  182. package/dist/components/video-mini-player.d.ts +35 -0
  183. package/dist/components/video-mini-player.js +236 -0
  184. package/dist/components/video-mini-player.js.map +1 -0
  185. package/dist/components/video-player.d.ts +146 -0
  186. package/dist/components/video-player.js +630 -0
  187. package/dist/components/video-player.js.map +1 -0
  188. package/dist/components/video-poster-picker.d.ts +59 -0
  189. package/dist/components/video-poster-picker.js +335 -0
  190. package/dist/components/video-poster-picker.js.map +1 -0
  191. package/dist/components/video-poster.d.ts +24 -0
  192. package/dist/components/video-poster.js +46 -0
  193. package/dist/components/video-poster.js.map +1 -0
  194. package/dist/i18n/context.d.ts +4 -0
  195. package/dist/i18n/context.js +11 -0
  196. package/dist/i18n/context.js.map +1 -0
  197. package/dist/i18n/messages.d.ts +40 -0
  198. package/dist/i18n/messages.js +94 -0
  199. package/dist/i18n/messages.js.map +1 -0
  200. package/dist/index.d.ts +23 -0
  201. package/dist/index.js +41 -0
  202. package/dist/locales/de.d.ts +2 -0
  203. package/dist/locales/de.js +271 -0
  204. package/dist/locales/de.js.map +1 -0
  205. package/dist/locales/en.d.ts +457 -0
  206. package/dist/locales/en.js +271 -0
  207. package/dist/locales/en.js.map +1 -0
  208. package/dist/locales/es.d.ts +2 -0
  209. package/dist/locales/es.js +271 -0
  210. package/dist/locales/es.js.map +1 -0
  211. package/dist/locales/ja.d.ts +2 -0
  212. package/dist/locales/ja.js +271 -0
  213. package/dist/locales/ja.js.map +1 -0
  214. package/dist/locales/ko.d.ts +2 -0
  215. package/dist/locales/ko.js +271 -0
  216. package/dist/locales/ko.js.map +1 -0
  217. package/dist/locales/social/de.d.ts +2 -0
  218. package/dist/locales/social/de.js +198 -0
  219. package/dist/locales/social/de.js.map +1 -0
  220. package/dist/locales/social/en.d.ts +195 -0
  221. package/dist/locales/social/en.js +198 -0
  222. package/dist/locales/social/en.js.map +1 -0
  223. package/dist/locales/social/es.d.ts +2 -0
  224. package/dist/locales/social/es.js +198 -0
  225. package/dist/locales/social/es.js.map +1 -0
  226. package/dist/locales/social/ja.d.ts +2 -0
  227. package/dist/locales/social/ja.js +198 -0
  228. package/dist/locales/social/ja.js.map +1 -0
  229. package/dist/locales/social/ko.d.ts +2 -0
  230. package/dist/locales/social/ko.js +198 -0
  231. package/dist/locales/social/ko.js.map +1 -0
  232. package/dist/locales/social/zh.d.ts +2 -0
  233. package/dist/locales/social/zh.js +198 -0
  234. package/dist/locales/social/zh.js.map +1 -0
  235. package/dist/locales/zh.d.ts +2 -0
  236. package/dist/locales/zh.js +271 -0
  237. package/dist/locales/zh.js.map +1 -0
  238. package/dist/provider.d.ts +22 -0
  239. package/dist/provider.js +59 -0
  240. package/dist/provider.js.map +1 -0
  241. package/dist/react/canonical.d.ts +33 -0
  242. package/dist/react/canonical.js +78 -0
  243. package/dist/react/canonical.js.map +1 -0
  244. package/dist/react/comments.d.ts +82 -0
  245. package/dist/react/comments.js +155 -0
  246. package/dist/react/comments.js.map +1 -0
  247. package/dist/react/config.d.ts +13 -0
  248. package/dist/react/config.js +14 -0
  249. package/dist/react/config.js.map +1 -0
  250. package/dist/react/context.d.ts +55 -0
  251. package/dist/react/context.js +42 -0
  252. package/dist/react/context.js.map +1 -0
  253. package/dist/react/crop.d.ts +49 -0
  254. package/dist/react/crop.js +107 -0
  255. package/dist/react/crop.js.map +1 -0
  256. package/dist/react/engagement.d.ts +38 -0
  257. package/dist/react/engagement.js +88 -0
  258. package/dist/react/engagement.js.map +1 -0
  259. package/dist/react/folder.d.ts +103 -0
  260. package/dist/react/folder.js +240 -0
  261. package/dist/react/folder.js.map +1 -0
  262. package/dist/react/gallery.d.ts +140 -0
  263. package/dist/react/gallery.js +477 -0
  264. package/dist/react/gallery.js.map +1 -0
  265. package/dist/react/index.d.ts +21 -0
  266. package/dist/react/inline-preview.d.ts +24 -0
  267. package/dist/react/inline-preview.js +76 -0
  268. package/dist/react/inline-preview.js.map +1 -0
  269. package/dist/react/player.d.ts +70 -0
  270. package/dist/react/player.js +214 -0
  271. package/dist/react/player.js.map +1 -0
  272. package/dist/react/polls.d.ts +74 -0
  273. package/dist/react/polls.js +176 -0
  274. package/dist/react/polls.js.map +1 -0
  275. package/dist/react/posts.d.ts +59 -0
  276. package/dist/react/posts.js +149 -0
  277. package/dist/react/posts.js.map +1 -0
  278. package/dist/react/provider.d.ts +27 -0
  279. package/dist/react/provider.js +49 -0
  280. package/dist/react/provider.js.map +1 -0
  281. package/dist/react/public.d.ts +39 -0
  282. package/dist/react/public.js +70 -0
  283. package/dist/react/public.js.map +1 -0
  284. package/dist/react/read.d.ts +52 -0
  285. package/dist/react/read.js +144 -0
  286. package/dist/react/read.js.map +1 -0
  287. package/dist/react/resources.d.ts +73 -0
  288. package/dist/react/resources.js +247 -0
  289. package/dist/react/resources.js.map +1 -0
  290. package/dist/react/slot.d.ts +104 -0
  291. package/dist/react/slot.js +185 -0
  292. package/dist/react/slot.js.map +1 -0
  293. package/dist/react/store.d.ts +51 -0
  294. package/dist/react/store.js +165 -0
  295. package/dist/react/store.js.map +1 -0
  296. package/dist/react/upload.d.ts +86 -0
  297. package/dist/react/upload.js +155 -0
  298. package/dist/react/upload.js.map +1 -0
  299. package/dist/react/use-resource.d.ts +44 -0
  300. package/dist/react/use-resource.js +74 -0
  301. package/dist/react/use-resource.js.map +1 -0
  302. package/dist/react/video.d.ts +118 -0
  303. package/dist/react/video.js +184 -0
  304. package/dist/react/video.js.map +1 -0
  305. package/dist/react/viewport.d.ts +19 -0
  306. package/dist/react/viewport.js +30 -0
  307. package/dist/react/viewport.js.map +1 -0
  308. package/dist/react.js +21 -0
  309. package/dist/scope.d.ts +14 -0
  310. package/dist/scope.js +30 -0
  311. package/dist/scope.js.map +1 -0
  312. package/dist/styles.css +3 -0
  313. package/dist/ui/alert-dialog.d.ts +18 -0
  314. package/dist/ui/alert-dialog.js +86 -0
  315. package/dist/ui/alert-dialog.js.map +1 -0
  316. package/dist/ui/alert.d.ts +10 -0
  317. package/dist/ui/alert.js +38 -0
  318. package/dist/ui/alert.js.map +1 -0
  319. package/dist/ui/avatar.d.ts +11 -0
  320. package/dist/ui/avatar.js +31 -0
  321. package/dist/ui/avatar.js.map +1 -0
  322. package/dist/ui/badge.d.ts +7 -0
  323. package/dist/ui/badge.js +31 -0
  324. package/dist/ui/badge.js.map +1 -0
  325. package/dist/ui/button.d.ts +8 -0
  326. package/dist/ui/button.js +46 -0
  327. package/dist/ui/button.js.map +1 -0
  328. package/dist/ui/dialog.d.ts +17 -0
  329. package/dist/ui/dialog.js +91 -0
  330. package/dist/ui/dialog.js.map +1 -0
  331. package/dist/ui/dropdown-menu.d.ts +15 -0
  332. package/dist/ui/dropdown-menu.js +105 -0
  333. package/dist/ui/dropdown-menu.js.map +1 -0
  334. package/dist/ui/input.d.ts +3 -0
  335. package/dist/ui/input.js +17 -0
  336. package/dist/ui/input.js.map +1 -0
  337. package/dist/ui/label.d.ts +3 -0
  338. package/dist/ui/label.js +15 -0
  339. package/dist/ui/label.js.map +1 -0
  340. package/dist/ui/progress.d.ts +7 -0
  341. package/dist/ui/progress.js +31 -0
  342. package/dist/ui/progress.js.map +1 -0
  343. package/dist/ui/select.d.ts +15 -0
  344. package/dist/ui/select.js +98 -0
  345. package/dist/ui/select.js.map +1 -0
  346. package/dist/ui/skeleton.d.ts +2 -0
  347. package/dist/ui/skeleton.js +14 -0
  348. package/dist/ui/skeleton.js.map +1 -0
  349. package/dist/ui/slider.d.ts +3 -0
  350. package/dist/ui/slider.js +35 -0
  351. package/dist/ui/slider.js.map +1 -0
  352. package/dist/ui/switch.d.ts +5 -0
  353. package/dist/ui/switch.js +20 -0
  354. package/dist/ui/switch.js.map +1 -0
  355. package/dist/ui/tabs.d.ts +10 -0
  356. package/dist/ui/tabs.js +47 -0
  357. package/dist/ui/tabs.js.map +1 -0
  358. package/dist/ui/textarea.d.ts +3 -0
  359. package/dist/ui/textarea.js +15 -0
  360. package/dist/ui/textarea.js.map +1 -0
  361. package/dist/ui/toggle-group.d.ts +5 -0
  362. package/dist/ui/toggle-group.js +23 -0
  363. package/dist/ui/toggle-group.js.map +1 -0
  364. package/dist/urls/index.d.ts +100 -0
  365. package/dist/urls.js +118 -0
  366. package/dist/urls.js.map +1 -0
  367. package/package.json +120 -3
@@ -0,0 +1,215 @@
1
+ import { ratio as e } from "../client/aspect.js";
2
+ import { presetFor as t } from "../client/media/rules.js";
3
+ import { useMessages as n } from "../i18n/context.js";
4
+ import { ContentKitUiRoot as r } from "../scope.js";
5
+ import { usePresets as i } from "../react/public.js";
6
+ import { Button as a } from "../ui/button.js";
7
+ import { SlotImage as o } from "./slot-image.js";
8
+ import { SlotEditError as s, SlotEditor as c, useSlotEditor as l } from "./slot-editor.js";
9
+ import { useState as u } from "react";
10
+ import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
11
+ import { cn as m } from "cn";
12
+ import { Camera01Icon as h, CropIcon as g, Delete02Icon as _, ImageUpload01Icon as v, Loading03Icon as y } from "@hugeicons/core-free-icons";
13
+ import { HugeiconsIcon as b } from "@hugeicons/react";
14
+ //#region src/components/slot-upload.tsx
15
+ function x(e) {
16
+ return /* @__PURE__ */ f(C, {
17
+ ...e,
18
+ variant: "avatar"
19
+ });
20
+ }
21
+ function S(e) {
22
+ return /* @__PURE__ */ f(C, {
23
+ ...e,
24
+ variant: "cover"
25
+ });
26
+ }
27
+ function C({ variant: e, ...n }) {
28
+ let a = n.path ?? e, { presets: o } = i({ client: n.client }), s = t(o, n.item.kind, a, n.image?.preset);
29
+ return /* @__PURE__ */ f(r, {
30
+ className: m("text-sm", n.className),
31
+ "data-ckui": e === "avatar" ? "avatar-upload" : "cover-upload",
32
+ children: /* @__PURE__ */ f(c, {
33
+ item: n.item,
34
+ path: a,
35
+ image: n.image,
36
+ client: n.client,
37
+ read: n.read,
38
+ onChange: n.onChange,
39
+ aspect: n.aspect || n.image?.aspect || s?.aspect || (e === "avatar" ? "1:1" : "3:1"),
40
+ minWidth: n.minWidth,
41
+ animation: n.animation,
42
+ targetWidth: n.targetWidth ?? (e === "avatar" ? 512 : 3e3),
43
+ round: e === "avatar",
44
+ accept: n.accept,
45
+ disabled: n.disabled,
46
+ removable: n.removable ?? e === "avatar",
47
+ decode: n.decode,
48
+ children: /* @__PURE__ */ f(w, {
49
+ ...n,
50
+ variant: e
51
+ })
52
+ })
53
+ });
54
+ }
55
+ function w({ variant: t, ...r }) {
56
+ let { t: i } = n(), c = l(), { crop: x, has: S, busy: C, disabled: w } = c, E = {
57
+ preset: r.image?.preset ?? t,
58
+ aspect: c.image.aspect,
59
+ renditions: c.image.renditions
60
+ }, [D, O] = u(!1), k = x.aspect, A = r.targetWidth ?? (t === "avatar" ? 512 : 3e3), j = (e) => {
61
+ e.preventDefault(), O(!1);
62
+ let t = [...e.dataTransfer.files].find((e) => e.type.startsWith("image/")) ?? e.dataTransfer.files[0];
63
+ t && c.pick(t);
64
+ }, M = r.hint ?? (t === "avatar" ? i("avatar.hint", { size: A }) : i("cover.hint", {
65
+ width: A,
66
+ height: Math.round(A / (e(k) ?? 3))
67
+ })), N = r.label ?? i(t === "avatar" ? "avatar.label" : "cover.label"), P = (e) => /* @__PURE__ */ p(d, { children: [
68
+ /* @__PURE__ */ p(a, {
69
+ variant: e && S ? "secondary" : "outline",
70
+ size: "sm",
71
+ disabled: w,
72
+ onClick: c.choose,
73
+ "data-ckui": "change",
74
+ children: [/* @__PURE__ */ f(b, {
75
+ icon: C ? y : h,
76
+ strokeWidth: 2,
77
+ "data-icon": "inline-start",
78
+ className: C ? "animate-spin" : void 0
79
+ }), i(S ? "common.change" : "common.upload")]
80
+ }),
81
+ S && x.canRecrop && /* @__PURE__ */ p(a, {
82
+ variant: e ? "secondary" : "ghost",
83
+ size: "sm",
84
+ disabled: w,
85
+ onClick: c.recrop,
86
+ "data-ckui": "edit-crop",
87
+ children: [/* @__PURE__ */ f(b, {
88
+ icon: g,
89
+ strokeWidth: 2,
90
+ "data-icon": "inline-start"
91
+ }), i("common.editCrop")]
92
+ }),
93
+ S && c.removable && /* @__PURE__ */ p(a, {
94
+ variant: e ? "secondary" : "ghost",
95
+ size: "sm",
96
+ disabled: w,
97
+ onClick: c.remove,
98
+ "data-ckui": "remove",
99
+ children: [/* @__PURE__ */ f(b, {
100
+ icon: _,
101
+ strokeWidth: 2,
102
+ "data-icon": "inline-start"
103
+ }), i("common.remove")]
104
+ })
105
+ ] });
106
+ return t === "avatar" ? /* @__PURE__ */ p("div", {
107
+ className: "flex items-center gap-4",
108
+ children: [/* @__PURE__ */ p("div", {
109
+ className: "relative size-20 shrink-0",
110
+ children: [/* @__PURE__ */ f(o, {
111
+ image: S ? E : null,
112
+ round: !0,
113
+ emptyLabel: i("avatar.empty"),
114
+ className: "size-full ring-1 ring-foreground/10"
115
+ }), C && /* @__PURE__ */ f(T, {
116
+ round: !0,
117
+ label: i(x.status === "decoding" ? "progress.decoding" : "common.saving")
118
+ })]
119
+ }), /* @__PURE__ */ p("div", {
120
+ className: "grid min-w-0 gap-1",
121
+ children: [
122
+ /* @__PURE__ */ f("div", {
123
+ className: "font-medium",
124
+ children: N
125
+ }),
126
+ /* @__PURE__ */ f("div", {
127
+ className: "text-xs text-muted-foreground",
128
+ children: M
129
+ }),
130
+ /* @__PURE__ */ f("div", {
131
+ className: "mt-1.5 flex flex-wrap gap-2",
132
+ children: P(!1)
133
+ }),
134
+ /* @__PURE__ */ f(s, {})
135
+ ]
136
+ })]
137
+ }) : /* @__PURE__ */ p("div", {
138
+ className: "grid gap-2",
139
+ children: [
140
+ /* @__PURE__ */ p("div", {
141
+ className: m("group/cover relative overflow-hidden rounded-xl ring-1 ring-foreground/10 transition-shadow", D && "ring-2 ring-ring"),
142
+ onDragOver: (e) => {
143
+ e.preventDefault(), w || O(!0);
144
+ },
145
+ onDragLeave: () => O(!1),
146
+ onDrop: j,
147
+ children: [
148
+ /* @__PURE__ */ f(o, {
149
+ image: S ? E : null,
150
+ aspect: k,
151
+ className: "w-full rounded-none",
152
+ fallback: /* @__PURE__ */ p("div", {
153
+ "aria-label": i("cover.empty"),
154
+ role: "group",
155
+ className: "absolute inset-0 flex flex-col items-center justify-center gap-1.5 bg-[repeating-linear-gradient(135deg,transparent_0_10px,color-mix(in_oklch,var(--ckui-foreground)_3%,transparent)_10px_20px)] p-4 text-center",
156
+ children: [
157
+ /* @__PURE__ */ f(b, {
158
+ icon: v,
159
+ strokeWidth: 1.5,
160
+ className: "size-7 text-muted-foreground"
161
+ }),
162
+ /* @__PURE__ */ f("span", {
163
+ className: "hidden text-xs text-muted-foreground sm:block",
164
+ children: i("cover.drop")
165
+ }),
166
+ /* @__PURE__ */ f("div", {
167
+ className: "mt-1 flex gap-2",
168
+ children: P(!1)
169
+ })
170
+ ]
171
+ })
172
+ }),
173
+ C && /* @__PURE__ */ f(T, { label: i(x.status === "decoding" ? "progress.decoding" : "common.saving") }),
174
+ S && /* @__PURE__ */ f("div", {
175
+ className: "absolute right-2 bottom-2 flex gap-2 max-sm:hidden [&_button]:bg-background/80 [&_button]:backdrop-blur-sm",
176
+ children: P(!0)
177
+ })
178
+ ]
179
+ }),
180
+ /* @__PURE__ */ p("div", {
181
+ className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5",
182
+ children: [/* @__PURE__ */ p("div", {
183
+ className: "grid min-w-0 gap-0.5 sm:flex sm:flex-1 sm:items-baseline sm:justify-between sm:gap-3",
184
+ children: [/* @__PURE__ */ f("div", {
185
+ className: "font-medium",
186
+ children: N
187
+ }), /* @__PURE__ */ f("div", {
188
+ className: "text-xs text-muted-foreground sm:truncate",
189
+ children: M
190
+ })]
191
+ }), S && /* @__PURE__ */ f("div", {
192
+ className: "flex gap-2 sm:hidden",
193
+ children: P(!1)
194
+ })]
195
+ }),
196
+ /* @__PURE__ */ f(s, {})
197
+ ]
198
+ });
199
+ }
200
+ function T({ round: e, label: t }) {
201
+ return /* @__PURE__ */ f("div", {
202
+ role: "status",
203
+ "aria-label": t,
204
+ className: m("absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[1px]", e && "rounded-full"),
205
+ children: /* @__PURE__ */ f(b, {
206
+ icon: y,
207
+ strokeWidth: 2,
208
+ className: "size-5 animate-spin text-foreground"
209
+ })
210
+ });
211
+ }
212
+ //#endregion
213
+ export { x as AvatarUpload, S as CoverUpload };
214
+
215
+ //# sourceMappingURL=slot-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-upload.js","names":[],"sources":["../../src/components/slot-upload.tsx"],"sourcesContent":["import { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport { Camera01Icon, CropIcon, Delete02Icon, ImageUpload01Icon, Loading03Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useState, type DragEvent, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport type { CropSource } from \"../client/image.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { presetFor } from \"../client/media/rules.js\";\nimport { usePresets } from \"../react/public.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { SlotEditError, SlotEditor, useSlotEditor } from \"./slot-editor.js\";\nimport { SlotImage } from \"./slot-image.js\";\n\nexport interface SlotUploadProps {\n /** The item that owns the upload. */\n item: RefBody;\n /** The upload path; default \"avatar\" or \"cover\". */\n path?: string;\n /** The public preset showing it. */\n image?: PublicPreset | null;\n client?: ContentKitClient;\n /** An editor read of the item from the host; otherwise fetched. */\n read?: ReadResult | null;\n /** Called with the processed upload after every save, null after a removal. */\n onChange?: (file: FileInfo | null) => void;\n /** The output's \"W:H\"; default the preset's aspect, else \"1:1\" (avatar) or \"3:1\" (cover). */\n aspect?: AspectRatio;\n /** The narrowest edit the server accepts (the preset's Image.MinWidth). */\n minWidth?: number;\n /** \"reject\" refuses animated images before uploading. */\n animation?: \"reject\";\n /** Crops narrower than this many source pixels get a sharpness warning; default 512 (avatar) or 3000 (cover). */\n targetWidth?: number;\n /** `accept` of the file input. Default \"image/*\". */\n accept?: string;\n disabled?: boolean;\n /** Offers Remove once set; default true for avatars, false for covers. */\n removable?: boolean;\n label?: ReactNode;\n hint?: ReactNode;\n className?: string;\n /** Replaces decodeImage (tests). */\n decode?: (file: File) => Promise<CropSource>;\n}\n\ntype Variant = \"avatar\" | \"cover\";\n\n/** Round 1:1 avatar field: image, label, hint, Change / Edit crop, crop dialog, progress, errors. For custom layouts use SlotEditor. */\nexport function AvatarUpload(p: SlotUploadProps) {\n return <SlotUpload {...p} variant=\"avatar\" />;\n}\n\n/** Wide cover field at the preset's aspect (e.g. 3:1), with drop-to-upload. For custom layouts use SlotEditor. */\nexport function CoverUpload(p: SlotUploadProps) {\n return <SlotUpload {...p} variant=\"cover\" />;\n}\n\nfunction SlotUpload({ variant, ...p }: SlotUploadProps & { variant: Variant }) {\n const path = p.path ?? variant;\n const { presets } = usePresets({ client: p.client });\n const rule = presetFor(presets, p.item.kind, path, p.image?.preset);\n return (\n <ContentKitUiRoot className={cn(\"text-sm\", p.className)} data-ckui={variant === \"avatar\" ? \"avatar-upload\" : \"cover-upload\"}>\n <SlotEditor\n item={p.item}\n path={path}\n image={p.image}\n client={p.client}\n read={p.read}\n onChange={p.onChange}\n aspect={p.aspect || p.image?.aspect || rule?.aspect || (variant === \"avatar\" ? \"1:1\" : \"3:1\")}\n minWidth={p.minWidth}\n animation={p.animation}\n targetWidth={p.targetWidth ?? (variant === \"avatar\" ? 512 : 3000)}\n round={variant === \"avatar\"}\n accept={p.accept}\n disabled={p.disabled}\n removable={p.removable ?? variant === \"avatar\"}\n decode={p.decode}\n >\n <SlotUploadLayout {...p} variant={variant} />\n </SlotEditor>\n </ContentKitUiRoot>\n );\n}\n\nfunction SlotUploadLayout({ variant, ...p }: SlotUploadProps & { variant: Variant }) {\n const { t } = useMessages();\n const s = useSlotEditor();\n const { crop, has, busy, disabled } = s;\n const image = { preset: p.image?.preset ?? variant, aspect: s.image.aspect, renditions: s.image.renditions };\n const [dragging, setDragging] = useState(false);\n const aspect = crop.aspect;\n const target = p.targetWidth ?? (variant === \"avatar\" ? 512 : 3000);\n const onDrop = (e: DragEvent) => {\n e.preventDefault();\n setDragging(false);\n const f = [...e.dataTransfer.files].find((f) => f.type.startsWith(\"image/\")) ?? e.dataTransfer.files[0];\n if (f) s.pick(f);\n };\n\n const hint =\n p.hint ?? (variant === \"avatar\" ? t(\"avatar.hint\", { size: target }) : t(\"cover.hint\", { width: target, height: Math.round(target / (ratio(aspect) ?? 3)) }));\n const label = p.label ?? t(variant === \"avatar\" ? \"avatar.label\" : \"cover.label\");\n\n // overlay: buttons on top of the cover image; otherwise a plain row.\n const actions = (overlay: boolean) => (\n <>\n <Button variant={overlay && has ? \"secondary\" : \"outline\"} size=\"sm\" disabled={disabled} onClick={s.choose} data-ckui=\"change\">\n <HugeiconsIcon icon={busy ? Loading03Icon : Camera01Icon} strokeWidth={2} data-icon=\"inline-start\" className={busy ? \"animate-spin\" : undefined} />\n {has ? t(\"common.change\") : t(\"common.upload\")}\n </Button>\n {has && crop.canRecrop && (\n <Button variant={overlay ? \"secondary\" : \"ghost\"} size=\"sm\" disabled={disabled} onClick={s.recrop} data-ckui=\"edit-crop\">\n <HugeiconsIcon icon={CropIcon} strokeWidth={2} data-icon=\"inline-start\" />\n {t(\"common.editCrop\")}\n </Button>\n )}\n {has && s.removable && (\n <Button variant={overlay ? \"secondary\" : \"ghost\"} size=\"sm\" disabled={disabled} onClick={s.remove} data-ckui=\"remove\">\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} data-icon=\"inline-start\" />\n {t(\"common.remove\")}\n </Button>\n )}\n </>\n );\n\n return variant === \"avatar\" ? (\n <div className=\"flex items-center gap-4\">\n <div className=\"relative size-20 shrink-0\">\n <SlotImage image={has ? image : null} round emptyLabel={t(\"avatar.empty\")} className=\"size-full ring-1 ring-foreground/10\" />\n {busy && <BusyOverlay round label={t(crop.status === \"decoding\" ? \"progress.decoding\" : \"common.saving\")} />}\n </div>\n <div className=\"grid min-w-0 gap-1\">\n <div className=\"font-medium\">{label}</div>\n <div className=\"text-xs text-muted-foreground\">{hint}</div>\n <div className=\"mt-1.5 flex flex-wrap gap-2\">{actions(false)}</div>\n <SlotEditError />\n </div>\n </div>\n ) : (\n <div className=\"grid gap-2\">\n <div\n className={cn(\n \"group/cover relative overflow-hidden rounded-xl ring-1 ring-foreground/10 transition-shadow\",\n dragging && \"ring-2 ring-ring\",\n )}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n <SlotImage\n image={has ? image : null}\n aspect={aspect}\n className=\"w-full rounded-none\"\n fallback={\n <div aria-label={t(\"cover.empty\")} role=\"group\" className=\"absolute inset-0 flex flex-col items-center justify-center gap-1.5 bg-[repeating-linear-gradient(135deg,transparent_0_10px,color-mix(in_oklch,var(--ckui-foreground)_3%,transparent)_10px_20px)] p-4 text-center\">\n <HugeiconsIcon icon={ImageUpload01Icon} strokeWidth={1.5} className=\"size-7 text-muted-foreground\" />\n <span className=\"hidden text-xs text-muted-foreground sm:block\">{t(\"cover.drop\")}</span>\n <div className=\"mt-1 flex gap-2\">{actions(false)}</div>\n </div>\n }\n />\n {busy && <BusyOverlay label={t(crop.status === \"decoding\" ? \"progress.decoding\" : \"common.saving\")} />}\n {has && (\n <div className=\"absolute right-2 bottom-2 flex gap-2 max-sm:hidden [&_button]:bg-background/80 [&_button]:backdrop-blur-sm\">\n {actions(true)}\n </div>\n )}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5\">\n <div className=\"grid min-w-0 gap-0.5 sm:flex sm:flex-1 sm:items-baseline sm:justify-between sm:gap-3\">\n <div className=\"font-medium\">{label}</div>\n <div className=\"text-xs text-muted-foreground sm:truncate\">{hint}</div>\n </div>\n {has && <div className=\"flex gap-2 sm:hidden\">{actions(false)}</div>}\n </div>\n <SlotEditError />\n </div>\n );\n}\n\nfunction BusyOverlay({ round, label }: { round?: boolean; label: string }) {\n return (\n <div role=\"status\" aria-label={label} className={cn(\"absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[1px]\", round && \"rounded-full\")}>\n <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"size-5 animate-spin text-foreground\" />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { ContentKitClient } from '../../client/client.js';
2
+ import { BanScope } from '../../client/content/types.js';
3
+ import { BanNotice as BanNoticeData, CommentBan, PublicUser } from '../../client/generated/wire.js';
4
+ import { ContentKitUiAppearance } from '../../appearance.js';
5
+ import { ContentKitErrorHandler } from '../../react/context.js';
6
+ /** What a banned user is told: where, why and until when (never who). */
7
+ export declare function BanNotice({ ban, className }: {
8
+ ban: BanNoticeData;
9
+ className?: string;
10
+ }): import('react').JSX.Element;
11
+ /** "YYYY-MM-DDTHH:mm" in local time, for a datetime-local input. */
12
+ export declare function localInput(d: Date): string;
13
+ export interface CommentBanDialogProps {
14
+ open: boolean;
15
+ onOpenChange: (open: boolean) => void;
16
+ /** Who to ban: their id and the name to show. */
17
+ user: {
18
+ id: string;
19
+ name?: string;
20
+ };
21
+ /** The scopes the caller may ban in (CommentStanding.ban_scopes); the first is preselected. */
22
+ scopes: readonly BanScope[];
23
+ /** The current ban, when updating one. */
24
+ ban?: CommentBan | null;
25
+ onSaved?: (ban: CommentBan) => void;
26
+ client?: ContentKitClient;
27
+ onError?: ContentKitErrorHandler;
28
+ }
29
+ /** Bans a user from commenting, or replaces their ban: scope, duration presets or an end date, and a reason they see. */
30
+ export declare function CommentBanDialog({ open, onOpenChange, user, scopes, ban, onSaved, client: own, onError }: CommentBanDialogProps): import('react').JSX.Element;
31
+ export interface CommentBansProps {
32
+ /** "global": the site's bans (CommentBan); "owner": the caller's own. Default "global". */
33
+ scope?: BanScope;
34
+ pageSize?: number;
35
+ userHref?: (user: PublicUser) => string | undefined;
36
+ client?: ContentKitClient;
37
+ onError?: ContentKitErrorHandler;
38
+ className?: string;
39
+ appearance?: ContentKitUiAppearance;
40
+ }
41
+ /** A scope's comment bans, newest first: who, why, until when; ban a user by id, update or lift a ban. */
42
+ export declare function CommentBans({ scope, pageSize, userHref, client, onError, className, appearance }: CommentBansProps): import('react').JSX.Element;
@@ -0,0 +1,300 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { ContentKitUiRoot as t } from "../../scope.js";
3
+ import { useContentKitClient as n } from "../../react/context.js";
4
+ import { Alert as r, AlertDescription as i } from "../../ui/alert.js";
5
+ import { Button as a } from "../../ui/button.js";
6
+ import { Dialog as o, DialogContent as s, DialogDescription as c, DialogFooter as l, DialogHeader as u, DialogTitle as d } from "../../ui/dialog.js";
7
+ import { useCommentBans as f } from "../../react/comments.js";
8
+ import { Badge as p } from "../../ui/badge.js";
9
+ import { Input as m } from "../../ui/input.js";
10
+ import { Label as h } from "../../ui/label.js";
11
+ import { Textarea as g } from "../../ui/textarea.js";
12
+ import { ConfirmDialog as _, ErrorLine as v, UserAvatar as y, UserName as b, formatDate as x, useAction as S } from "./parts.js";
13
+ import { useEffect as C, useId as w, useState as T } from "react";
14
+ import { jsx as E, jsxs as D } from "react/jsx-runtime";
15
+ import { cn as O } from "cn";
16
+ //#region src/components/social/bans.tsx
17
+ function k({ ban: t, className: n }) {
18
+ let a = e(), { t: o } = a;
19
+ return /* @__PURE__ */ E(r, {
20
+ variant: "destructive",
21
+ className: n,
22
+ "data-ckui": "ban-notice",
23
+ children: /* @__PURE__ */ D(i, {
24
+ className: "grid gap-1",
25
+ children: [
26
+ /* @__PURE__ */ E("p", {
27
+ className: "font-medium",
28
+ children: o(t.scope === "global" ? "bans.globalNotice" : "bans.ownerNotice")
29
+ }),
30
+ t.reason && /* @__PURE__ */ E("p", { children: o("bans.reason", { reason: t.reason }) }),
31
+ /* @__PURE__ */ E("p", { children: t.until ? o("bans.until", { date: x(a, t.until) }) : o("bans.untilLifted") })
32
+ ]
33
+ })
34
+ });
35
+ }
36
+ var A = 864e5, j = {
37
+ day: A,
38
+ week: 7 * A,
39
+ month: 30 * A,
40
+ forever: 0,
41
+ custom: 0
42
+ };
43
+ function M(e) {
44
+ let t = (e) => String(e).padStart(2, "0");
45
+ return `${e.getFullYear()}-${t(e.getMonth() + 1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}`;
46
+ }
47
+ function N({ open: t, onOpenChange: r, user: i, scopes: f, ban: p, onSaved: _, client: y, onError: b }) {
48
+ let { t: x } = e(), O = n(y), k = w(), N = p ? p.scope === "global" ? "global" : "owner" : f[0] ?? "owner", [P, F] = T(N), [I, L] = T(p ? p.until ? "custom" : "forever" : "week"), [R, z] = T(p?.until ? M(new Date(p.until)) : M(new Date(Date.now() + 7 * A))), [B, V] = T(p?.reason ?? ""), { run: H, pending: U, error: W, setError: G } = S("ban.save", b);
49
+ C(() => {
50
+ t && (F(N), L(p ? p.until ? "custom" : "forever" : "week"), z(p?.until ? M(new Date(p.until)) : M(new Date(Date.now() + 7 * A))), V(p?.reason ?? ""), G(null));
51
+ }, [t]);
52
+ let K = i.name ?? i.id, q = async () => {
53
+ let e = I === "forever" ? void 0 : I === "custom" ? new Date(R) : new Date(Date.now() + j[I]), t = await H(() => O.bans.ban(P, i.id, {
54
+ reason: B.trim() || void 0,
55
+ until: e?.toISOString()
56
+ }));
57
+ t && (_?.(t), r(!1));
58
+ };
59
+ return /* @__PURE__ */ E(o, {
60
+ open: t,
61
+ onOpenChange: r,
62
+ children: /* @__PURE__ */ D(s, {
63
+ "data-ckui": "comment-ban-dialog",
64
+ children: [/* @__PURE__ */ D(u, { children: [/* @__PURE__ */ E(d, { children: x("bans.title", { name: K }) }), /* @__PURE__ */ E(c, { children: x(P === "global" ? "bans.globalHelp" : "bans.ownerHelp") })] }), /* @__PURE__ */ D("form", {
65
+ className: "grid gap-4",
66
+ onSubmit: (e) => {
67
+ e.preventDefault(), q();
68
+ },
69
+ children: [
70
+ f.length > 1 && !p && /* @__PURE__ */ D("fieldset", {
71
+ className: "grid gap-2",
72
+ children: [/* @__PURE__ */ E("legend", {
73
+ className: "mb-1 text-sm font-medium",
74
+ children: x("bans.scope")
75
+ }), /* @__PURE__ */ E("div", {
76
+ className: "flex flex-wrap gap-2",
77
+ children: f.map((e) => /* @__PURE__ */ E(a, {
78
+ type: "button",
79
+ size: "sm",
80
+ variant: P === e ? "default" : "outline",
81
+ "aria-pressed": P === e,
82
+ onClick: () => F(e),
83
+ children: x(e === "global" ? "bans.scopeGlobal" : "bans.scopeOwner")
84
+ }, e))
85
+ })]
86
+ }),
87
+ /* @__PURE__ */ D("fieldset", {
88
+ className: "grid gap-2",
89
+ children: [
90
+ /* @__PURE__ */ E("legend", {
91
+ className: "mb-1 text-sm font-medium",
92
+ children: x("bans.duration")
93
+ }),
94
+ /* @__PURE__ */ E("div", {
95
+ className: "flex flex-wrap gap-2",
96
+ children: [
97
+ "day",
98
+ "week",
99
+ "month",
100
+ "forever",
101
+ "custom"
102
+ ].map((e) => /* @__PURE__ */ E(a, {
103
+ type: "button",
104
+ size: "sm",
105
+ variant: I === e ? "default" : "outline",
106
+ "aria-pressed": I === e,
107
+ onClick: () => L(e),
108
+ children: x(`bans.${e}`)
109
+ }, e))
110
+ }),
111
+ I === "custom" && /* @__PURE__ */ D("div", {
112
+ className: "grid gap-1.5",
113
+ children: [/* @__PURE__ */ E(h, {
114
+ htmlFor: `${k}-until`,
115
+ children: x("bans.endsAt")
116
+ }), /* @__PURE__ */ E(m, {
117
+ id: `${k}-until`,
118
+ type: "datetime-local",
119
+ required: !0,
120
+ value: R,
121
+ min: M(/* @__PURE__ */ new Date()),
122
+ onChange: (e) => z(e.target.value)
123
+ })]
124
+ })
125
+ ]
126
+ }),
127
+ /* @__PURE__ */ D("div", {
128
+ className: "grid gap-1.5",
129
+ children: [/* @__PURE__ */ E(h, {
130
+ htmlFor: `${k}-reason`,
131
+ children: x("bans.reasonLabel")
132
+ }), /* @__PURE__ */ E(g, {
133
+ id: `${k}-reason`,
134
+ maxLength: 500,
135
+ rows: 3,
136
+ placeholder: x("bans.reasonPlaceholder"),
137
+ value: B,
138
+ onChange: (e) => V(e.target.value)
139
+ })]
140
+ }),
141
+ /* @__PURE__ */ E(v, { error: W }),
142
+ /* @__PURE__ */ D(l, { children: [/* @__PURE__ */ E(a, {
143
+ type: "button",
144
+ variant: "outline",
145
+ onClick: () => r(!1),
146
+ children: x("common.cancel")
147
+ }), /* @__PURE__ */ E(a, {
148
+ type: "submit",
149
+ variant: "destructive",
150
+ disabled: U,
151
+ children: x(U ? "common.saving" : p ? "bans.update" : "bans.save")
152
+ })] })
153
+ ]
154
+ })]
155
+ })
156
+ });
157
+ }
158
+ function P({ scope: n = "global", pageSize: r = 25, userHref: i, client: o, onError: s, className: c, appearance: l }) {
159
+ let u = e(), { t: d } = u, g = f({
160
+ scope: n,
161
+ pageSize: r,
162
+ client: o
163
+ }), C = w(), [k, A] = T(null), [j, M] = T(null), [P, F] = T(""), I = S("ban.lift", s);
164
+ return /* @__PURE__ */ D(t, {
165
+ appearance: l,
166
+ className: O("grid gap-4 text-sm", c),
167
+ "data-ckui": "comment-bans",
168
+ children: [
169
+ /* @__PURE__ */ D("form", {
170
+ className: "flex flex-wrap items-end gap-2",
171
+ onSubmit: (e) => {
172
+ e.preventDefault(), P.trim() && A({ user: { id: P.trim() } });
173
+ },
174
+ children: [/* @__PURE__ */ D("div", {
175
+ className: "grid min-w-48 flex-1 gap-1.5",
176
+ children: [/* @__PURE__ */ E(h, {
177
+ htmlFor: `${C}-user`,
178
+ children: d("bans.user")
179
+ }), /* @__PURE__ */ E(m, {
180
+ id: `${C}-user`,
181
+ value: P,
182
+ placeholder: d("bans.userPlaceholder"),
183
+ onChange: (e) => F(e.target.value)
184
+ })]
185
+ }), /* @__PURE__ */ E(a, {
186
+ type: "submit",
187
+ variant: "outline",
188
+ disabled: !P.trim(),
189
+ children: d("bans.add")
190
+ })]
191
+ }),
192
+ /* @__PURE__ */ E(v, { error: I.error }),
193
+ g.error && !g.items.length ? /* @__PURE__ */ D("div", {
194
+ className: "grid justify-items-start gap-2",
195
+ children: [/* @__PURE__ */ E(v, { children: d("bans.loadFailed") }), /* @__PURE__ */ E(a, {
196
+ size: "sm",
197
+ variant: "outline",
198
+ onClick: g.reload,
199
+ children: d("common.retry")
200
+ })]
201
+ }) : !g.loading && !g.items.length ? /* @__PURE__ */ E("p", {
202
+ className: "text-muted-foreground",
203
+ children: d("bans.empty")
204
+ }) : /* @__PURE__ */ E("ul", {
205
+ className: "grid divide-y divide-border rounded-lg border border-border",
206
+ "aria-busy": g.loading,
207
+ children: g.items.map((e) => {
208
+ let t = e.user?.username ?? e.user_id;
209
+ return /* @__PURE__ */ D("li", {
210
+ className: "flex flex-wrap items-start gap-3 p-3",
211
+ "data-ckui": "comment-ban",
212
+ children: [
213
+ /* @__PURE__ */ E(y, {
214
+ user: e.user,
215
+ name: t
216
+ }),
217
+ /* @__PURE__ */ D("div", {
218
+ className: "grid min-w-0 flex-1 gap-0.5",
219
+ children: [
220
+ /* @__PURE__ */ D("div", {
221
+ className: "flex flex-wrap items-center gap-2",
222
+ children: [/* @__PURE__ */ E(b, {
223
+ user: e.user,
224
+ name: t,
225
+ href: i
226
+ }), /* @__PURE__ */ E(p, {
227
+ variant: e.expired ? "outline" : "destructive",
228
+ children: d(e.expired ? "bans.expired" : "bans.active")
229
+ })]
230
+ }),
231
+ e.reason && /* @__PURE__ */ E("p", {
232
+ className: "break-words",
233
+ children: d("bans.reason", { reason: e.reason })
234
+ }),
235
+ /* @__PURE__ */ D("p", {
236
+ className: "text-muted-foreground",
237
+ children: [
238
+ e.until ? d("bans.until", { date: x(u, e.until) }) : d("bans.untilLifted"),
239
+ " · ",
240
+ d("bans.bannedAt", { date: x(u, e.banned_at, !1) })
241
+ ]
242
+ })
243
+ ]
244
+ }),
245
+ /* @__PURE__ */ D("div", {
246
+ className: "flex gap-2",
247
+ children: [/* @__PURE__ */ E(a, {
248
+ size: "sm",
249
+ variant: "outline",
250
+ onClick: () => A({
251
+ user: {
252
+ id: e.user_id,
253
+ name: t
254
+ },
255
+ ban: e
256
+ }),
257
+ children: d("bans.update")
258
+ }), /* @__PURE__ */ E(a, {
259
+ size: "sm",
260
+ variant: "destructive",
261
+ onClick: () => M(e),
262
+ children: d("bans.lift")
263
+ })]
264
+ })
265
+ ]
266
+ }, e.user_id);
267
+ })
268
+ }),
269
+ g.hasMore && /* @__PURE__ */ E(a, {
270
+ variant: "outline",
271
+ className: "justify-self-center",
272
+ disabled: g.loadingMore,
273
+ onClick: () => void g.loadMore(),
274
+ children: d("moderation.loadMore")
275
+ }),
276
+ k && /* @__PURE__ */ E(N, {
277
+ open: !0,
278
+ onOpenChange: (e) => !e && A(null),
279
+ user: k.user,
280
+ scopes: [n],
281
+ ban: k.ban,
282
+ client: o,
283
+ onError: s,
284
+ onSaved: () => F("")
285
+ }),
286
+ /* @__PURE__ */ E(_, {
287
+ open: !!j,
288
+ onOpenChange: (e) => !e && M(null),
289
+ title: d("bans.liftTitle", { name: j?.user?.username ?? j?.user_id ?? "" }),
290
+ description: d("bans.liftBody"),
291
+ confirm: d("bans.lift"),
292
+ onConfirm: () => j && void I.run(() => g.lift(j.user_id))
293
+ })
294
+ ]
295
+ });
296
+ }
297
+ //#endregion
298
+ export { k as BanNotice, N as CommentBanDialog, P as CommentBans, M as localInput };
299
+
300
+ //# sourceMappingURL=bans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bans.js","names":[],"sources":["../../../src/components/social/bans.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useEffect, useId, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { BanScope } from \"../../client/content/types.js\";\nimport type { BanNotice as BanNoticeData, CommentBan, PublicUser } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useCommentBans } from \"../../react/comments.js\";\nimport { useContentKitClient } from \"../../react/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from \"#ckui/ui/dialog\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { ConfirmDialog, ErrorLine, formatDate, UserAvatar, UserName, useAction } from \"./parts.js\";\n\n/** What a banned user is told: where, why and until when (never who). */\nexport function BanNotice({ ban, className }: { ban: BanNoticeData; className?: string }) {\n const m = useMessages();\n const { t } = m;\n return (\n <Alert variant=\"destructive\" className={className} data-ckui=\"ban-notice\">\n <AlertDescription className=\"grid gap-1\">\n <p className=\"font-medium\">{t(ban.scope === \"global\" ? \"bans.globalNotice\" : \"bans.ownerNotice\")}</p>\n {ban.reason && <p>{t(\"bans.reason\", { reason: ban.reason })}</p>}\n <p>{ban.until ? t(\"bans.until\", { date: formatDate(m, ban.until) }) : t(\"bans.untilLifted\")}</p>\n </AlertDescription>\n </Alert>\n );\n}\n\nconst DAY = 86_400_000;\ntype Duration = \"day\" | \"week\" | \"month\" | \"forever\" | \"custom\";\nconst span: Record<Duration, number> = { day: DAY, week: 7 * DAY, month: 30 * DAY, forever: 0, custom: 0 };\n\n/** \"YYYY-MM-DDTHH:mm\" in local time, for a datetime-local input. */\nexport function localInput(d: Date): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nexport interface CommentBanDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Who to ban: their id and the name to show. */\n user: { id: string; name?: string };\n /** The scopes the caller may ban in (CommentStanding.ban_scopes); the first is preselected. */\n scopes: readonly BanScope[];\n /** The current ban, when updating one. */\n ban?: CommentBan | null;\n onSaved?: (ban: CommentBan) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n}\n\n/** Bans a user from commenting, or replaces their ban: scope, duration presets or an end date, and a reason they see. */\nexport function CommentBanDialog({ open, onOpenChange, user, scopes, ban, onSaved, client: own, onError }: CommentBanDialogProps) {\n const m = useMessages();\n const { t } = m;\n const client = useContentKitClient(own);\n const ids = useId();\n const initialScope: BanScope = ban ? (ban.scope === \"global\" ? \"global\" : \"owner\") : (scopes[0] ?? \"owner\");\n const [scope, setScope] = useState<BanScope>(initialScope);\n const [duration, setDuration] = useState<Duration>(ban ? (ban.until ? \"custom\" : \"forever\") : \"week\");\n const [until, setUntil] = useState(ban?.until ? localInput(new Date(ban.until)) : localInput(new Date(Date.now() + 7 * DAY)));\n const [reason, setReason] = useState(ban?.reason ?? \"\");\n const { run, pending, error, setError } = useAction(\"ban.save\", onError);\n useEffect(() => {\n if (!open) return;\n setScope(initialScope);\n setDuration(ban ? (ban.until ? \"custom\" : \"forever\") : \"week\");\n setUntil(ban?.until ? localInput(new Date(ban.until)) : localInput(new Date(Date.now() + 7 * DAY)));\n setReason(ban?.reason ?? \"\");\n setError(null);\n // Reset when it opens.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n const name = user.name ?? user.id;\n const save = async () => {\n const end = duration === \"forever\" ? undefined : duration === \"custom\" ? new Date(until) : new Date(Date.now() + span[duration]);\n const saved = await run(() => client.bans.ban(scope, user.id, { reason: reason.trim() || undefined, until: end?.toISOString() }));\n if (saved) {\n onSaved?.(saved);\n onOpenChange(false);\n }\n };\n const durations: Duration[] = [\"day\", \"week\", \"month\", \"forever\", \"custom\"];\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent data-ckui=\"comment-ban-dialog\">\n <DialogHeader>\n <DialogTitle>{t(\"bans.title\", { name })}</DialogTitle>\n <DialogDescription>{t(scope === \"global\" ? \"bans.globalHelp\" : \"bans.ownerHelp\")}</DialogDescription>\n </DialogHeader>\n <form\n className=\"grid gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n {scopes.length > 1 && !ban && (\n <fieldset className=\"grid gap-2\">\n <legend className=\"mb-1 text-sm font-medium\">{t(\"bans.scope\")}</legend>\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((s) => (\n <Button key={s} type=\"button\" size=\"sm\" variant={scope === s ? \"default\" : \"outline\"} aria-pressed={scope === s} onClick={() => setScope(s)}>\n {t(s === \"global\" ? \"bans.scopeGlobal\" : \"bans.scopeOwner\")}\n </Button>\n ))}\n </div>\n </fieldset>\n )}\n <fieldset className=\"grid gap-2\">\n <legend className=\"mb-1 text-sm font-medium\">{t(\"bans.duration\")}</legend>\n <div className=\"flex flex-wrap gap-2\">\n {durations.map((d) => (\n <Button key={d} type=\"button\" size=\"sm\" variant={duration === d ? \"default\" : \"outline\"} aria-pressed={duration === d} onClick={() => setDuration(d)}>\n {t(`bans.${d}`)}\n </Button>\n ))}\n </div>\n {duration === \"custom\" && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-until`}>{t(\"bans.endsAt\")}</Label>\n <Input id={`${ids}-until`} type=\"datetime-local\" required value={until} min={localInput(new Date())} onChange={(e) => setUntil(e.target.value)} />\n </div>\n )}\n </fieldset>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-reason`}>{t(\"bans.reasonLabel\")}</Label>\n <Textarea id={`${ids}-reason`} maxLength={500} rows={3} placeholder={t(\"bans.reasonPlaceholder\")} value={reason} onChange={(e) => setReason(e.target.value)} />\n </div>\n <ErrorLine error={error} />\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t(\"common.cancel\")}\n </Button>\n <Button type=\"submit\" variant=\"destructive\" disabled={pending}>\n {pending ? t(\"common.saving\") : t(ban ? \"bans.update\" : \"bans.save\")}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport interface CommentBansProps {\n /** \"global\": the site's bans (CommentBan); \"owner\": the caller's own. Default \"global\". */\n scope?: BanScope;\n pageSize?: number;\n userHref?: (user: PublicUser) => string | undefined;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A scope's comment bans, newest first: who, why, until when; ban a user by id, update or lift a ban. */\nexport function CommentBans({ scope = \"global\", pageSize = 25, userHref, client, onError, className, appearance }: CommentBansProps) {\n const m = useMessages();\n const { t } = m;\n const bans = useCommentBans({ scope, pageSize, client });\n const ids = useId();\n const [editing, setEditing] = useState<{ user: { id: string; name?: string }; ban?: CommentBan } | null>(null);\n const [lifting, setLifting] = useState<CommentBan | null>(null);\n const [userId, setUserId] = useState(\"\");\n const lift = useAction(\"ban.lift\", onError);\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-4 text-sm\", className)} data-ckui=\"comment-bans\">\n <form\n className=\"flex flex-wrap items-end gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n if (userId.trim()) setEditing({ user: { id: userId.trim() } });\n }}\n >\n <div className=\"grid min-w-48 flex-1 gap-1.5\">\n <Label htmlFor={`${ids}-user`}>{t(\"bans.user\")}</Label>\n <Input id={`${ids}-user`} value={userId} placeholder={t(\"bans.userPlaceholder\")} onChange={(e) => setUserId(e.target.value)} />\n </div>\n <Button type=\"submit\" variant=\"outline\" disabled={!userId.trim()}>\n {t(\"bans.add\")}\n </Button>\n </form>\n <ErrorLine error={lift.error} />\n {bans.error && !bans.items.length ? (\n <div className=\"grid justify-items-start gap-2\">\n <ErrorLine>{t(\"bans.loadFailed\")}</ErrorLine>\n <Button size=\"sm\" variant=\"outline\" onClick={bans.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n ) : !bans.loading && !bans.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"bans.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={bans.loading}>\n {bans.items.map((b) => {\n const name = b.user?.username ?? b.user_id;\n return (\n <li key={b.user_id} className=\"flex flex-wrap items-start gap-3 p-3\" data-ckui=\"comment-ban\">\n <UserAvatar user={b.user} name={name} />\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <UserName user={b.user} name={name} href={userHref} />\n <Badge variant={b.expired ? \"outline\" : \"destructive\"}>{t(b.expired ? \"bans.expired\" : \"bans.active\")}</Badge>\n </div>\n {b.reason && <p className=\"break-words\">{t(\"bans.reason\", { reason: b.reason })}</p>}\n <p className=\"text-muted-foreground\">\n {b.until ? t(\"bans.until\", { date: formatDate(m, b.until) }) : t(\"bans.untilLifted\")} · {t(\"bans.bannedAt\", { date: formatDate(m, b.banned_at, false) })}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setEditing({ user: { id: b.user_id, name }, ban: b })}>\n {t(\"bans.update\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => setLifting(b)}>\n {t(\"bans.lift\")}\n </Button>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n {bans.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={bans.loadingMore} onClick={() => void bans.loadMore()}>\n {t(\"moderation.loadMore\")}\n </Button>\n )}\n {editing && (\n <CommentBanDialog\n open\n onOpenChange={(o) => !o && setEditing(null)}\n user={editing.user}\n scopes={[scope]}\n ban={editing.ban}\n client={client}\n onError={onError}\n onSaved={() => setUserId(\"\")}\n />\n )}\n <ConfirmDialog\n open={!!lifting}\n onOpenChange={(o) => !o && setLifting(null)}\n title={t(\"bans.liftTitle\", { name: lifting?.user?.username ?? lifting?.user_id ?? \"\" })}\n description={t(\"bans.liftBody\")}\n confirm={t(\"bans.lift\")}\n onConfirm={() => lifting && void lift.run(() => bans.lift(lifting.user_id))}\n />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}