@openrails/contentkit-ui 0.71.0 → 0.73.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 (134) hide show
  1. package/README.md +122 -10
  2. package/dist/client/content/bans.d.ts +2 -0
  3. package/dist/client/content/bans.js +24 -12
  4. package/dist/client/content/bans.js.map +1 -1
  5. package/dist/client/content/comments.d.ts +2 -0
  6. package/dist/client/content/comments.js.map +1 -1
  7. package/dist/client/content/polls.d.ts +9 -2
  8. package/dist/client/content/polls.js.map +1 -1
  9. package/dist/client/generated/routes.d.ts +28 -2
  10. package/dist/client/generated/routes.js +40 -0
  11. package/dist/client/generated/routes.js.map +1 -1
  12. package/dist/client/generated/wire.d.ts +4 -0
  13. package/dist/client/generated/wire.js.map +1 -1
  14. package/dist/client/index.d.ts +3 -3
  15. package/dist/client/media/queue.d.ts +1 -1
  16. package/dist/client/media/queue.js +1 -1
  17. package/dist/client/media/queue.js.map +1 -1
  18. package/dist/client/taxonomy.d.ts +5 -1
  19. package/dist/client/taxonomy.js +7 -0
  20. package/dist/client/taxonomy.js.map +1 -1
  21. package/dist/client.js +2 -2
  22. package/dist/components/admin/media-item-panel.d.ts +27 -0
  23. package/dist/components/admin/media-item-panel.js +397 -0
  24. package/dist/components/admin/media-item-panel.js.map +1 -0
  25. package/dist/components/admin/poll-manager.d.ts +28 -0
  26. package/dist/components/admin/poll-manager.js +297 -0
  27. package/dist/components/admin/poll-manager.js.map +1 -0
  28. package/dist/components/admin/post-manager.d.ts +45 -0
  29. package/dist/components/admin/post-manager.js +602 -0
  30. package/dist/components/admin/post-manager.js.map +1 -0
  31. package/dist/components/admin/shell.d.ts +73 -0
  32. package/dist/components/admin/shell.js +148 -0
  33. package/dist/components/admin/shell.js.map +1 -0
  34. package/dist/components/admin/taxonomy-manager.d.ts +54 -0
  35. package/dist/components/admin/taxonomy-manager.js +976 -0
  36. package/dist/components/admin/taxonomy-manager.js.map +1 -0
  37. package/dist/components/admin/upload-queue.d.ts +23 -0
  38. package/dist/components/admin/upload-queue.js +365 -0
  39. package/dist/components/admin/upload-queue.js.map +1 -0
  40. package/dist/components/admin/user-comment-standing.d.ts +25 -0
  41. package/dist/components/admin/user-comment-standing.js +229 -0
  42. package/dist/components/admin/user-comment-standing.js.map +1 -0
  43. package/dist/components/social/bans.d.ts +5 -2
  44. package/dist/components/social/bans.js +132 -134
  45. package/dist/components/social/bans.js.map +1 -1
  46. package/dist/components/social/index.d.ts +1 -1
  47. package/dist/components/social/moderation.d.ts +11 -13
  48. package/dist/components/social/moderation.js +299 -218
  49. package/dist/components/social/moderation.js.map +1 -1
  50. package/dist/components/social/parts.d.ts +3 -2
  51. package/dist/components/social/parts.js +74 -48
  52. package/dist/components/social/parts.js.map +1 -1
  53. package/dist/components/social/poll-editor.js +1 -0
  54. package/dist/components/social/poll-editor.js.map +1 -1
  55. package/dist/editor/index.d.ts +1 -0
  56. package/dist/editor/rich-text-editor.d.ts +13 -0
  57. package/dist/editor/rich-text-editor.js +228 -0
  58. package/dist/editor/rich-text-editor.js.map +1 -0
  59. package/dist/editor.js +2 -0
  60. package/dist/index.d.ts +8 -0
  61. package/dist/index.js +15 -8
  62. package/dist/locales/admin/de.d.ts +2 -0
  63. package/dist/locales/admin/de.js +227 -0
  64. package/dist/locales/admin/de.js.map +1 -0
  65. package/dist/locales/admin/en.d.ts +222 -0
  66. package/dist/locales/admin/en.js +227 -0
  67. package/dist/locales/admin/en.js.map +1 -0
  68. package/dist/locales/admin/es.d.ts +2 -0
  69. package/dist/locales/admin/es.js +227 -0
  70. package/dist/locales/admin/es.js.map +1 -0
  71. package/dist/locales/admin/ja.d.ts +2 -0
  72. package/dist/locales/admin/ja.js +227 -0
  73. package/dist/locales/admin/ja.js.map +1 -0
  74. package/dist/locales/admin/ko.d.ts +2 -0
  75. package/dist/locales/admin/ko.js +227 -0
  76. package/dist/locales/admin/ko.js.map +1 -0
  77. package/dist/locales/admin/zh.d.ts +2 -0
  78. package/dist/locales/admin/zh.js +227 -0
  79. package/dist/locales/admin/zh.js.map +1 -0
  80. package/dist/locales/de.js +5 -3
  81. package/dist/locales/de.js.map +1 -1
  82. package/dist/locales/en.d.ts +227 -0
  83. package/dist/locales/en.js +5 -3
  84. package/dist/locales/en.js.map +1 -1
  85. package/dist/locales/es.js +5 -3
  86. package/dist/locales/es.js.map +1 -1
  87. package/dist/locales/ja.js +5 -3
  88. package/dist/locales/ja.js.map +1 -1
  89. package/dist/locales/ko.js +5 -3
  90. package/dist/locales/ko.js.map +1 -1
  91. package/dist/locales/social/de.js +8 -1
  92. package/dist/locales/social/de.js.map +1 -1
  93. package/dist/locales/social/en.d.ts +7 -0
  94. package/dist/locales/social/en.js +8 -1
  95. package/dist/locales/social/en.js.map +1 -1
  96. package/dist/locales/social/es.js +8 -1
  97. package/dist/locales/social/es.js.map +1 -1
  98. package/dist/locales/social/ja.js +8 -1
  99. package/dist/locales/social/ja.js.map +1 -1
  100. package/dist/locales/social/ko.js +8 -1
  101. package/dist/locales/social/ko.js.map +1 -1
  102. package/dist/locales/social/zh.js +8 -1
  103. package/dist/locales/social/zh.js.map +1 -1
  104. package/dist/locales/zh.js +5 -3
  105. package/dist/locales/zh.js.map +1 -1
  106. package/dist/provider.d.ts +2 -0
  107. package/dist/provider.js +23 -19
  108. package/dist/provider.js.map +1 -1
  109. package/dist/react/admin.d.ts +55 -0
  110. package/dist/react/admin.js +97 -0
  111. package/dist/react/admin.js.map +1 -0
  112. package/dist/react/comments.d.ts +18 -2
  113. package/dist/react/comments.js +24 -8
  114. package/dist/react/comments.js.map +1 -1
  115. package/dist/react/context.d.ts +7 -1
  116. package/dist/react/context.js +6 -1
  117. package/dist/react/context.js.map +1 -1
  118. package/dist/react/folder.js.map +1 -1
  119. package/dist/react/index.d.ts +4 -3
  120. package/dist/react/polls.js +88 -87
  121. package/dist/react/polls.js.map +1 -1
  122. package/dist/react/posts.js +123 -122
  123. package/dist/react/posts.js.map +1 -1
  124. package/dist/react/resources.js +3 -0
  125. package/dist/react/resources.js.map +1 -1
  126. package/dist/react/upload.d.ts +10 -10
  127. package/dist/react/upload.js +1 -1
  128. package/dist/react/upload.js.map +1 -1
  129. package/dist/react/use-resource.d.ts +16 -0
  130. package/dist/react/use-resource.js +20 -1
  131. package/dist/react/use-resource.js.map +1 -1
  132. package/dist/react.js +21 -20
  133. package/dist/styles.css +1 -1
  134. package/package.json +37 -2
@@ -4,94 +4,95 @@ import { useContentKitClient as n } from "../../react/context.js";
4
4
  import { Alert as r, AlertDescription as i } from "../../ui/alert.js";
5
5
  import { Button as a } from "../../ui/button.js";
6
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";
7
+ import { keyOf as f, useContentScope as p, usePaged as m } from "../../react/use-resource.js";
8
+ import { Badge as h } from "../../ui/badge.js";
9
+ import { Input as g } from "../../ui/input.js";
10
+ import { Label as _ } from "../../ui/label.js";
11
+ import { Textarea as v } from "../../ui/textarea.js";
12
+ import { ConfirmDialog as y, ErrorLine as b, UserAvatar as x, UserName as S, formatDate as C, useAction as w } from "./parts.js";
13
+ import { LoadFailed as T, Pager as E } from "../admin/shell.js";
14
+ import { useEffect as D, useId as O, useState as k } from "react";
15
+ import { jsx as A, jsxs as j } from "react/jsx-runtime";
16
+ import { cn as M } from "cn";
16
17
  //#region src/components/social/bans.tsx
17
- function k({ ban: t, className: n }) {
18
+ function N({ ban: t, className: n }) {
18
19
  let a = e(), { t: o } = a;
19
- return /* @__PURE__ */ E(r, {
20
+ return /* @__PURE__ */ A(r, {
20
21
  variant: "destructive",
21
22
  className: n,
22
23
  "data-ckui": "ban-notice",
23
- children: /* @__PURE__ */ D(i, {
24
+ children: /* @__PURE__ */ j(i, {
24
25
  className: "grid gap-1",
25
26
  children: [
26
- /* @__PURE__ */ E("p", {
27
+ /* @__PURE__ */ A("p", {
27
28
  className: "font-medium",
28
29
  children: o(t.scope === "global" ? "bans.globalNotice" : "bans.ownerNotice")
29
30
  }),
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") })
31
+ t.reason && /* @__PURE__ */ A("p", { children: o("bans.reason", { reason: t.reason }) }),
32
+ /* @__PURE__ */ A("p", { children: t.until ? o("bans.until", { date: C(a, t.until) }) : o("bans.untilLifted") })
32
33
  ]
33
34
  })
34
35
  });
35
36
  }
36
- var A = 864e5, j = {
37
- day: A,
38
- week: 7 * A,
39
- month: 30 * A,
37
+ var P = 864e5, F = {
38
+ day: P,
39
+ week: 7 * P,
40
+ month: 30 * P,
40
41
  forever: 0,
41
42
  custom: 0
42
43
  };
43
- function M(e) {
44
+ function I(e) {
44
45
  let t = (e) => String(e).padStart(2, "0");
45
46
  return `${e.getFullYear()}-${t(e.getMonth() + 1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}`;
46
47
  }
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));
48
+ function L({ open: t, onOpenChange: r, user: i, scopes: f, ban: p, onSaved: m, client: h, onError: y }) {
49
+ let { t: x } = e(), S = n(h), C = O(), T = p ? p.scope === "global" ? "global" : "owner" : f[0] ?? "owner", [E, M] = k(T), [N, L] = k(p ? p.until ? "custom" : "forever" : "week"), [R, z] = k(p?.until ? I(new Date(p.until)) : I(new Date(Date.now() + 7 * P))), [B, V] = k(p?.reason ?? ""), { run: H, pending: U, error: W, setError: G } = w("ban.save", y);
50
+ D(() => {
51
+ t && (M(T), L(p ? p.until ? "custom" : "forever" : "week"), z(p?.until ? I(new Date(p.until)) : I(new Date(Date.now() + 7 * P))), V(p?.reason ?? ""), G(null));
51
52
  }, [t]);
52
53
  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
+ let e = N === "forever" ? void 0 : N === "custom" ? new Date(R) : new Date(Date.now() + F[N]), t = await H(() => S.bans.ban(E, i.id, {
54
55
  reason: B.trim() || void 0,
55
56
  until: e?.toISOString()
56
57
  }));
57
- t && (_?.(t), r(!1));
58
+ t && (m?.(t), r(!1));
58
59
  };
59
- return /* @__PURE__ */ E(o, {
60
+ return /* @__PURE__ */ A(o, {
60
61
  open: t,
61
62
  onOpenChange: r,
62
- children: /* @__PURE__ */ D(s, {
63
+ children: /* @__PURE__ */ j(s, {
63
64
  "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
+ children: [/* @__PURE__ */ j(u, { children: [/* @__PURE__ */ A(d, { children: x("bans.title", { name: K }) }), /* @__PURE__ */ A(c, { children: x(E === "global" ? "bans.globalHelp" : "bans.ownerHelp") })] }), /* @__PURE__ */ j("form", {
65
66
  className: "grid gap-4",
66
67
  onSubmit: (e) => {
67
68
  e.preventDefault(), q();
68
69
  },
69
70
  children: [
70
- f.length > 1 && !p && /* @__PURE__ */ D("fieldset", {
71
+ f.length > 1 && !p && /* @__PURE__ */ j("fieldset", {
71
72
  className: "grid gap-2",
72
- children: [/* @__PURE__ */ E("legend", {
73
+ children: [/* @__PURE__ */ A("legend", {
73
74
  className: "mb-1 text-sm font-medium",
74
75
  children: x("bans.scope")
75
- }), /* @__PURE__ */ E("div", {
76
+ }), /* @__PURE__ */ A("div", {
76
77
  className: "flex flex-wrap gap-2",
77
- children: f.map((e) => /* @__PURE__ */ E(a, {
78
+ children: f.map((e) => /* @__PURE__ */ A(a, {
78
79
  type: "button",
79
80
  size: "sm",
80
- variant: P === e ? "default" : "outline",
81
- "aria-pressed": P === e,
82
- onClick: () => F(e),
81
+ variant: E === e ? "default" : "outline",
82
+ "aria-pressed": E === e,
83
+ onClick: () => M(e),
83
84
  children: x(e === "global" ? "bans.scopeGlobal" : "bans.scopeOwner")
84
85
  }, e))
85
86
  })]
86
87
  }),
87
- /* @__PURE__ */ D("fieldset", {
88
+ /* @__PURE__ */ j("fieldset", {
88
89
  className: "grid gap-2",
89
90
  children: [
90
- /* @__PURE__ */ E("legend", {
91
+ /* @__PURE__ */ A("legend", {
91
92
  className: "mb-1 text-sm font-medium",
92
93
  children: x("bans.duration")
93
94
  }),
94
- /* @__PURE__ */ E("div", {
95
+ /* @__PURE__ */ A("div", {
95
96
  className: "flex flex-wrap gap-2",
96
97
  children: [
97
98
  "day",
@@ -99,38 +100,38 @@ function N({ open: t, onOpenChange: r, user: i, scopes: f, ban: p, onSaved: _, c
99
100
  "month",
100
101
  "forever",
101
102
  "custom"
102
- ].map((e) => /* @__PURE__ */ E(a, {
103
+ ].map((e) => /* @__PURE__ */ A(a, {
103
104
  type: "button",
104
105
  size: "sm",
105
- variant: I === e ? "default" : "outline",
106
- "aria-pressed": I === e,
106
+ variant: N === e ? "default" : "outline",
107
+ "aria-pressed": N === e,
107
108
  onClick: () => L(e),
108
109
  children: x(`bans.${e}`)
109
110
  }, e))
110
111
  }),
111
- I === "custom" && /* @__PURE__ */ D("div", {
112
+ N === "custom" && /* @__PURE__ */ j("div", {
112
113
  className: "grid gap-1.5",
113
- children: [/* @__PURE__ */ E(h, {
114
- htmlFor: `${k}-until`,
114
+ children: [/* @__PURE__ */ A(_, {
115
+ htmlFor: `${C}-until`,
115
116
  children: x("bans.endsAt")
116
- }), /* @__PURE__ */ E(m, {
117
- id: `${k}-until`,
117
+ }), /* @__PURE__ */ A(g, {
118
+ id: `${C}-until`,
118
119
  type: "datetime-local",
119
120
  required: !0,
120
121
  value: R,
121
- min: M(/* @__PURE__ */ new Date()),
122
+ min: I(/* @__PURE__ */ new Date()),
122
123
  onChange: (e) => z(e.target.value)
123
124
  })]
124
125
  })
125
126
  ]
126
127
  }),
127
- /* @__PURE__ */ D("div", {
128
+ /* @__PURE__ */ j("div", {
128
129
  className: "grid gap-1.5",
129
- children: [/* @__PURE__ */ E(h, {
130
- htmlFor: `${k}-reason`,
130
+ children: [/* @__PURE__ */ A(_, {
131
+ htmlFor: `${C}-reason`,
131
132
  children: x("bans.reasonLabel")
132
- }), /* @__PURE__ */ E(g, {
133
- id: `${k}-reason`,
133
+ }), /* @__PURE__ */ A(v, {
134
+ id: `${C}-reason`,
134
135
  maxLength: 500,
135
136
  rows: 3,
136
137
  placeholder: x("bans.reasonPlaceholder"),
@@ -138,13 +139,13 @@ function N({ open: t, onOpenChange: r, user: i, scopes: f, ban: p, onSaved: _, c
138
139
  onChange: (e) => V(e.target.value)
139
140
  })]
140
141
  }),
141
- /* @__PURE__ */ E(v, { error: W }),
142
- /* @__PURE__ */ D(l, { children: [/* @__PURE__ */ E(a, {
142
+ /* @__PURE__ */ A(b, { error: W }),
143
+ /* @__PURE__ */ j(l, { children: [/* @__PURE__ */ A(a, {
143
144
  type: "button",
144
145
  variant: "outline",
145
146
  onClick: () => r(!1),
146
147
  children: x("common.cancel")
147
- }), /* @__PURE__ */ E(a, {
148
+ }), /* @__PURE__ */ A(a, {
148
149
  type: "submit",
149
150
  variant: "destructive",
150
151
  disabled: U,
@@ -155,146 +156,143 @@ function N({ open: t, onOpenChange: r, user: i, scopes: f, ban: p, onSaved: _, c
155
156
  })
156
157
  });
157
158
  }
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),
159
+ function R({ scope: n = "global", pageSize: r = 25, page: i, onPageChange: o, userHref: s, client: c, onError: l, className: u, appearance: d }) {
160
+ let v = e(), { t: D } = v, { client: N, store: P, scope: F } = p(c), [I, R] = k(1), z = i ?? I, B = (e) => o ? o(e) : R(e), V = m(P, f("bans-page", F, n, r), {
161
+ type: "bans",
162
+ scope: n
163
+ }, z, r, async (e, t) => (await N.bans.list(n, e, t)).map((e) => ({
164
+ ...e,
165
+ id: e.user_id
166
+ }))), H = O(), [U, W] = k(null), [G, K] = k(null), [q, J] = k(""), Y = w("ban.lift", l);
167
+ return /* @__PURE__ */ j(t, {
168
+ appearance: d,
169
+ className: M("grid gap-4 text-sm", u),
167
170
  "data-ckui": "comment-bans",
168
171
  children: [
169
- /* @__PURE__ */ D("form", {
172
+ /* @__PURE__ */ j("form", {
170
173
  className: "flex flex-wrap items-end gap-2",
171
174
  onSubmit: (e) => {
172
- e.preventDefault(), P.trim() && A({ user: { id: P.trim() } });
175
+ e.preventDefault(), q.trim() && W({ user: { id: q.trim() } });
173
176
  },
174
- children: [/* @__PURE__ */ D("div", {
177
+ children: [/* @__PURE__ */ j("div", {
175
178
  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)
179
+ children: [/* @__PURE__ */ A(_, {
180
+ htmlFor: `${H}-user`,
181
+ children: D("bans.user")
182
+ }), /* @__PURE__ */ A(g, {
183
+ id: `${H}-user`,
184
+ value: q,
185
+ placeholder: D("bans.userPlaceholder"),
186
+ onChange: (e) => J(e.target.value)
184
187
  })]
185
- }), /* @__PURE__ */ E(a, {
188
+ }), /* @__PURE__ */ A(a, {
186
189
  type: "submit",
187
190
  variant: "outline",
188
- disabled: !P.trim(),
189
- children: d("bans.add")
191
+ disabled: !q.trim(),
192
+ children: D("bans.add")
190
193
  })]
191
194
  }),
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", {
195
+ /* @__PURE__ */ A(b, { error: Y.error }),
196
+ V.error && !V.items.length ? /* @__PURE__ */ A(T, {
197
+ error: V.error,
198
+ message: D("bans.loadFailed"),
199
+ retry: V.reload
200
+ }) : !V.loading && !V.items.length ? /* @__PURE__ */ A("p", {
202
201
  className: "text-muted-foreground",
203
- children: d("bans.empty")
204
- }) : /* @__PURE__ */ E("ul", {
202
+ children: D("bans.empty")
203
+ }) : /* @__PURE__ */ A("ul", {
205
204
  className: "grid divide-y divide-border rounded-lg border border-border",
206
- "aria-busy": g.loading,
207
- children: g.items.map((e) => {
205
+ "aria-busy": V.loading,
206
+ children: V.items.map((e) => {
208
207
  let t = e.user?.username ?? e.user_id;
209
- return /* @__PURE__ */ D("li", {
208
+ return /* @__PURE__ */ j("li", {
210
209
  className: "flex flex-wrap items-start gap-3 p-3",
211
210
  "data-ckui": "comment-ban",
212
211
  children: [
213
- /* @__PURE__ */ E(y, {
212
+ /* @__PURE__ */ A(x, {
214
213
  user: e.user,
215
214
  name: t
216
215
  }),
217
- /* @__PURE__ */ D("div", {
216
+ /* @__PURE__ */ j("div", {
218
217
  className: "grid min-w-0 flex-1 gap-0.5",
219
218
  children: [
220
- /* @__PURE__ */ D("div", {
219
+ /* @__PURE__ */ j("div", {
221
220
  className: "flex flex-wrap items-center gap-2",
222
- children: [/* @__PURE__ */ E(b, {
221
+ children: [/* @__PURE__ */ A(S, {
223
222
  user: e.user,
224
223
  name: t,
225
- href: i
226
- }), /* @__PURE__ */ E(p, {
224
+ href: s
225
+ }), /* @__PURE__ */ A(h, {
227
226
  variant: e.expired ? "outline" : "destructive",
228
- children: d(e.expired ? "bans.expired" : "bans.active")
227
+ children: D(e.expired ? "bans.expired" : "bans.active")
229
228
  })]
230
229
  }),
231
- e.reason && /* @__PURE__ */ E("p", {
230
+ e.reason && /* @__PURE__ */ A("p", {
232
231
  className: "break-words",
233
- children: d("bans.reason", { reason: e.reason })
232
+ children: D("bans.reason", { reason: e.reason })
234
233
  }),
235
- /* @__PURE__ */ D("p", {
234
+ /* @__PURE__ */ j("p", {
236
235
  className: "text-muted-foreground",
237
236
  children: [
238
- e.until ? d("bans.until", { date: x(u, e.until) }) : d("bans.untilLifted"),
237
+ e.until ? D("bans.until", { date: C(v, e.until) }) : D("bans.untilLifted"),
239
238
  " · ",
240
- d("bans.bannedAt", { date: x(u, e.banned_at, !1) })
239
+ D("bans.bannedAt", { date: C(v, e.banned_at, !1) })
241
240
  ]
242
241
  })
243
242
  ]
244
243
  }),
245
- /* @__PURE__ */ D("div", {
244
+ /* @__PURE__ */ j("div", {
246
245
  className: "flex gap-2",
247
- children: [/* @__PURE__ */ E(a, {
246
+ children: [/* @__PURE__ */ A(a, {
248
247
  size: "sm",
249
248
  variant: "outline",
250
- onClick: () => A({
249
+ onClick: () => W({
251
250
  user: {
252
251
  id: e.user_id,
253
252
  name: t
254
253
  },
255
254
  ban: e
256
255
  }),
257
- children: d("bans.update")
258
- }), /* @__PURE__ */ E(a, {
256
+ children: D("bans.update")
257
+ }), /* @__PURE__ */ A(a, {
259
258
  size: "sm",
260
259
  variant: "destructive",
261
- onClick: () => M(e),
262
- children: d("bans.lift")
260
+ onClick: () => K(e),
261
+ children: D("bans.lift")
263
262
  })]
264
263
  })
265
264
  ]
266
265
  }, e.user_id);
267
266
  })
268
267
  }),
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")
268
+ /* @__PURE__ */ A(E, {
269
+ page: z,
270
+ hasNext: V.hasNext,
271
+ onPage: B,
272
+ disabled: V.loading
275
273
  }),
276
- k && /* @__PURE__ */ E(N, {
274
+ U && /* @__PURE__ */ A(L, {
277
275
  open: !0,
278
- onOpenChange: (e) => !e && A(null),
279
- user: k.user,
276
+ onOpenChange: (e) => !e && W(null),
277
+ user: U.user,
280
278
  scopes: [n],
281
- ban: k.ban,
282
- client: o,
283
- onError: s,
284
- onSaved: () => F("")
279
+ ban: U.ban,
280
+ client: N,
281
+ onError: l,
282
+ onSaved: () => J("")
285
283
  }),
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))
284
+ /* @__PURE__ */ A(y, {
285
+ open: !!G,
286
+ onOpenChange: (e) => !e && K(null),
287
+ title: D("bans.liftTitle", { name: G?.user?.username ?? G?.user_id ?? "" }),
288
+ description: D("bans.liftBody"),
289
+ confirm: D("bans.lift"),
290
+ onConfirm: () => G && void Y.run(() => N.bans.lift(n, G.user_id))
293
291
  })
294
292
  ]
295
293
  });
296
294
  }
297
295
  //#endregion
298
- export { k as BanNotice, N as CommentBanDialog, P as CommentBans, M as localInput };
296
+ export { N as BanNotice, L as CommentBanDialog, R as CommentBans, I as localInput };
299
297
 
300
298
  //# sourceMappingURL=bans.js.map
@@ -1 +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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"}
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 { useContentKitClient } from \"../../react/context.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.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 { LoadFailed, Pager } from \"../admin/shell.js\";\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 /** The page shown (from 1), when the host keeps it, e.g. in the URL; with onPageChange. */\n page?: number;\n onPageChange?: (page: number) => void;\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, a page at a time: who, why, until when; ban a user by id, update or lift a ban. */\nexport function CommentBans({ scope = \"global\", pageSize = 25, page: shown, onPageChange, userHref, client: own, onError, className, appearance }: CommentBansProps) {\n const m = useMessages();\n const { t } = m;\n const { client, store, scope: readScope } = useContentScope(own);\n const [local, setLocal] = useState(1);\n const page = shown ?? local;\n const setPage = (n: number) => (onPageChange ? onPageChange(n) : setLocal(n));\n const bans = usePaged(store, keyOf(\"bans-page\", readScope, scope, pageSize), { type: \"bans\", scope }, page, pageSize, async (q, signal) =>\n (await client.bans.list(scope, q, signal)).map((b) => ({ ...b, id: b.user_id })),\n );\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 <LoadFailed error={bans.error} message={t(\"bans.loadFailed\")} retry={bans.reload} />\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 <Pager page={page} hasNext={bans.hasNext} onPage={setPage} disabled={bans.loading} />\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(() => client.bans.lift(scope, lifting.user_id))}\n />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
@@ -3,4 +3,4 @@ export { BanNotice, CommentBanDialog, CommentBans, type CommentBanDialogProps, t
3
3
  export { FavoriteButton, ReactionButtons, type FavoriteButtonProps, type ReactionButtonsProps } from './engagement.js';
4
4
  export { Poll, type PollProps } from './poll.js';
5
5
  export { PollEditor, type PollEditorProps } from './poll-editor.js';
6
- export { CommentModeration, type CommentModerationProps, type ModerationSection } from './moderation.js';
6
+ export { CommentModeration, useCommentModerationAccess, type CommentModerationProps, type ModerationSection } from './moderation.js';
@@ -1,9 +1,8 @@
1
- import { ContentKitClient } from '../../client/client.js';
2
1
  import { PublicUser } from '../../client/generated/wire.js';
3
- import { ContentKitUiAppearance } from '../../appearance.js';
4
- import { ContentKitErrorHandler } from '../../react/context.js';
2
+ import { FlowClientOptions, StaffAccess } from '../../react/admin.js';
3
+ import { AdminFlowProps, AdminRouteProps } from '../admin/shell.js';
5
4
  export type ModerationSection = "comments" | "queue" | "bans";
6
- export interface CommentModerationProps {
5
+ export interface CommentModerationProps extends AdminFlowProps, AdminRouteProps {
7
6
  /** Content kinds offered as a filter of the comment list: codes, or codes with labels. */
8
7
  contentKinds?: readonly (string | {
9
8
  value: string;
@@ -15,18 +14,17 @@ export interface CommentModerationProps {
15
14
  id: string;
16
15
  }) => string | undefined;
17
16
  userHref?: (user: PublicUser) => string | undefined;
18
- /** The sections, in order. Default all: every comment, the review queue, the site's bans. */
17
+ /** The sections, in order; the first is at basePath, the others at basePath/{section}. Default all. */
19
18
  sections?: readonly ModerationSection[];
20
19
  pageSize?: number;
21
- client?: ContentKitClient;
22
- onError?: ContentKitErrorHandler;
23
- className?: string;
24
- appearance?: ContentKitUiAppearance;
25
20
  }
21
+ /** Which moderation sections the caller may use: every comment, the review queue, the site's bans. */
22
+ export declare function useCommentModerationAccess(o?: FlowClientOptions): StaffAccess<ModerationSection>;
26
23
  /**
27
24
  * Staff: every comment (deleted, held and rejected ones with their text) to
28
- * delete, restore or ban its author; the review queue of held comments and
29
- * posts to approve or reject; and the site's comment bans. Each section
30
- * shows what the caller's permissions allow and says so when they don't.
25
+ * delete, restore or ban its author, by content kind or author; the review
26
+ * queue of held comments and posts to approve or reject; and the site's
27
+ * comment bans. Sections, filters and pages live in the URL under basePath;
28
+ * the caller sees only the sections its permissions allow.
31
29
  */
32
- export declare function CommentModeration({ sections, className, appearance, ...p }: CommentModerationProps): import('react').JSX.Element;
30
+ export declare function CommentModeration({ client: own, baseUrl, auth, guard, appearance, className, language, messages, sections, ...p }: CommentModerationProps): import('react').JSX.Element;