@openrails/contentkit-ui 0.99.3 → 0.100.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.
- package/README.md +15 -2
- package/dist/client/discovery.d.ts +6 -1
- package/dist/client/discovery.js +10 -7
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/wire.d.ts +1 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/components/admin/group-manager.js +124 -117
- package/dist/components/admin/group-manager.js.map +1 -1
- package/dist/components/admin/user-comment-standing.js +8 -6
- package/dist/components/admin/user-comment-standing.js.map +1 -1
- package/dist/components/content/card.js +2 -2
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/editors.d.ts +2 -2
- package/dist/components/content/editors.js +447 -328
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/filters.js +36 -18
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/grid.js +1 -1
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/popular.d.ts +3 -3
- package/dist/components/content/popular.js +15 -9
- package/dist/components/content/popular.js.map +1 -1
- package/dist/components/groups/directory.js +13 -7
- package/dist/components/groups/directory.js.map +1 -1
- package/dist/components/groups/memberships.js +13 -7
- package/dist/components/groups/memberships.js.map +1 -1
- package/dist/components/rendition-img.js +73 -55
- package/dist/components/rendition-img.js.map +1 -1
- package/dist/components/social/moderation.js +14 -5
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/locales/discovery/de.js +1 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +1 -0
- package/dist/locales/discovery/en.js +1 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +1 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +1 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +1 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +1 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +1 -0
- package/dist/react/read.d.ts +3 -2
- package/dist/react/read.js +68 -61
- package/dist/react/read.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,81 +3,82 @@ import { useGroups as n, useKinds as r } from "../../react/groups.js";
|
|
|
3
3
|
import { Button as i } from "../../ui/button.js";
|
|
4
4
|
import { Badge as a } from "../../ui/badge.js";
|
|
5
5
|
import { Select as o, SelectContent as s, SelectItem as c, SelectTrigger as l, SelectValue as u } from "../../ui/select.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
import { contentError as d } from "../social/parts.js";
|
|
7
|
+
import { AdminRoot as f, LoadFailed as p, NoAccess as m, Rows as h } from "./shell.js";
|
|
8
|
+
import { useAdminLocation as g, useFlowClient as _, useStaffAccess as v } from "../../react/admin.js";
|
|
9
|
+
import { Tabs as y, TabsList as b, TabsTrigger as x } from "../../ui/tabs.js";
|
|
10
|
+
import { LETTERS as S } from "../../client/group-fields.js";
|
|
11
|
+
import { labelOf as C } from "../groups/parts.js";
|
|
12
|
+
import { GroupEditor as w } from "../groups/editor.js";
|
|
13
|
+
import { GroupMembers as T } from "../groups/memberships.js";
|
|
14
|
+
import { useMemo as E } from "react";
|
|
15
|
+
import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
|
|
15
16
|
//#region src/components/admin/group-manager.tsx
|
|
16
|
-
function
|
|
17
|
-
return
|
|
17
|
+
function A(e = {}) {
|
|
18
|
+
return v(["groups"], e);
|
|
18
19
|
}
|
|
19
|
-
var
|
|
20
|
+
var j = [
|
|
20
21
|
"name",
|
|
21
22
|
"count",
|
|
22
23
|
"newest"
|
|
23
24
|
];
|
|
24
|
-
function
|
|
25
|
-
let l =
|
|
25
|
+
function M({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages: s, ...c }) {
|
|
26
|
+
let l = _({
|
|
26
27
|
client: e,
|
|
27
28
|
baseUrl: t,
|
|
28
29
|
auth: n
|
|
29
30
|
});
|
|
30
|
-
return /* @__PURE__ */
|
|
31
|
+
return /* @__PURE__ */ O(f, {
|
|
31
32
|
flow: "group-manager",
|
|
32
33
|
appearance: i,
|
|
33
34
|
className: a,
|
|
34
35
|
language: o,
|
|
35
36
|
messages: s,
|
|
36
37
|
guard: r,
|
|
37
|
-
children: /* @__PURE__ */
|
|
38
|
+
children: /* @__PURE__ */ O(N, {
|
|
38
39
|
...c,
|
|
39
40
|
client: l
|
|
40
41
|
})
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
function
|
|
44
|
-
let { t: a } = t(), o =
|
|
45
|
-
if (s.loading || c.loading) return /* @__PURE__ */
|
|
46
|
-
if (!s.allowed.groups) return /* @__PURE__ */
|
|
47
|
-
if (!l) return /* @__PURE__ */
|
|
44
|
+
function N({ basePath: e, navigation: n, ...i }) {
|
|
45
|
+
let { t: a } = t(), o = g(e, n), s = A({ client: i.client }), c = r({ client: i.client }), l = c.kinds, u = E(() => i.groupKinds ?? Object.entries(l?.entity_kinds ?? {}).filter(([, e]) => e.owner !== "user").map(([e]) => e).sort(), [i.groupKinds, l]);
|
|
46
|
+
if (s.loading || c.loading) return /* @__PURE__ */ O(h, {});
|
|
47
|
+
if (!s.allowed.groups) return /* @__PURE__ */ O(m, { children: a("groupManager.noAccess") });
|
|
48
|
+
if (!l) return /* @__PURE__ */ O(p, {
|
|
48
49
|
error: c.error,
|
|
49
50
|
message: a("groupManager.kindsFailed"),
|
|
50
51
|
retry: c.reload
|
|
51
52
|
});
|
|
52
|
-
if (!u.length) return /* @__PURE__ */
|
|
53
|
+
if (!u.length) return /* @__PURE__ */ O("p", {
|
|
53
54
|
className: "text-muted-foreground",
|
|
54
55
|
children: a("groupManager.noKinds")
|
|
55
56
|
});
|
|
56
|
-
let [d,
|
|
57
|
+
let [d, f] = o.view.split("/"), _ = u.includes(d ?? "") ? d : u[0], v = {
|
|
57
58
|
...i,
|
|
58
59
|
loc: o,
|
|
59
60
|
kind: _,
|
|
60
61
|
kinds: l
|
|
61
62
|
};
|
|
62
|
-
return /* @__PURE__ */
|
|
63
|
+
return /* @__PURE__ */ k(D, { children: [u.length > 1 && /* @__PURE__ */ O(y, {
|
|
63
64
|
value: _,
|
|
64
65
|
onValueChange: (e) => o.go(String(e)),
|
|
65
|
-
children: /* @__PURE__ */
|
|
66
|
+
children: /* @__PURE__ */ O(b, {
|
|
66
67
|
className: "flex-wrap group-data-horizontal/tabs:h-auto",
|
|
67
|
-
children: u.map((e) => /* @__PURE__ */
|
|
68
|
+
children: u.map((e) => /* @__PURE__ */ O(x, {
|
|
68
69
|
value: e,
|
|
69
70
|
className: "h-7.5",
|
|
70
|
-
children:
|
|
71
|
+
children: C(i.labels, e)
|
|
71
72
|
}, e))
|
|
72
73
|
})
|
|
73
|
-
}),
|
|
74
|
-
...
|
|
75
|
-
id:
|
|
76
|
-
},
|
|
74
|
+
}), f ? f === "new" ? /* @__PURE__ */ O(I, { ...v }, _) : /* @__PURE__ */ O(L, {
|
|
75
|
+
...v,
|
|
76
|
+
id: f
|
|
77
|
+
}, f) : /* @__PURE__ */ O(F, { ...v }, _)] });
|
|
77
78
|
}
|
|
78
|
-
function
|
|
79
|
+
function P({ loc: e, kind: n }) {
|
|
79
80
|
let { t: r } = t();
|
|
80
|
-
return /* @__PURE__ */
|
|
81
|
+
return /* @__PURE__ */ O(i, {
|
|
81
82
|
variant: "ghost",
|
|
82
83
|
size: "sm",
|
|
83
84
|
className: "justify-self-start",
|
|
@@ -85,139 +86,145 @@ function N({ loc: e, kind: n }) {
|
|
|
85
86
|
children: r("groupManager.back")
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
|
-
function
|
|
89
|
-
let
|
|
90
|
-
letter:
|
|
91
|
-
sort:
|
|
92
|
-
language:
|
|
93
|
-
},
|
|
94
|
-
...
|
|
89
|
+
function F({ loc: r, kind: f, kinds: m, client: g, contentKind: _, labels: v, pageSize: y = 50 }) {
|
|
90
|
+
let b = t(), { t: x } = b, w = e(), T = m.languages, E = r.query.get("letter") ?? "", D = S.includes(E) ? E : void 0, A = r.query.get("sort") ?? "", M = j.includes(A) ? A : "name", N = r.query.get("language") ?? "", P = T.includes(N) ? N : T[0], F = {
|
|
91
|
+
letter: D,
|
|
92
|
+
sort: M === "name" ? void 0 : M,
|
|
93
|
+
language: P === T[0] ? void 0 : P
|
|
94
|
+
}, I = (e) => r.go(f, {
|
|
95
|
+
...F,
|
|
95
96
|
...e
|
|
96
|
-
}),
|
|
97
|
-
kind:
|
|
98
|
-
letter:
|
|
99
|
-
sort:
|
|
100
|
-
language:
|
|
101
|
-
contentKind:
|
|
102
|
-
pageSize:
|
|
97
|
+
}), L = n({
|
|
98
|
+
kind: f,
|
|
99
|
+
letter: D,
|
|
100
|
+
sort: M,
|
|
101
|
+
language: P,
|
|
102
|
+
contentKind: _,
|
|
103
|
+
pageSize: y
|
|
103
104
|
}, {
|
|
104
|
-
client:
|
|
105
|
+
client: g,
|
|
105
106
|
management: !0
|
|
106
|
-
}),
|
|
107
|
-
return /* @__PURE__ */
|
|
107
|
+
}), R = C(v, f);
|
|
108
|
+
return /* @__PURE__ */ k("div", {
|
|
108
109
|
className: "grid gap-4",
|
|
109
110
|
"data-ckui": "group-list",
|
|
110
|
-
"data-kind":
|
|
111
|
+
"data-kind": f,
|
|
111
112
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ k("div", {
|
|
113
114
|
className: "flex flex-wrap items-center justify-between gap-2",
|
|
114
|
-
children: [/* @__PURE__ */
|
|
115
|
+
children: [/* @__PURE__ */ O("h2", {
|
|
115
116
|
className: "text-base font-semibold",
|
|
116
|
-
children:
|
|
117
|
-
}), /* @__PURE__ */
|
|
117
|
+
children: R
|
|
118
|
+
}), /* @__PURE__ */ O(i, {
|
|
118
119
|
size: "sm",
|
|
119
|
-
onClick: () => r.go(`${
|
|
120
|
-
children:
|
|
120
|
+
onClick: () => r.go(`${f}/new`),
|
|
121
|
+
children: x("groupManager.new", { kind: R })
|
|
121
122
|
})]
|
|
122
123
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ k("div", {
|
|
124
125
|
className: "flex flex-wrap items-center gap-2",
|
|
125
|
-
children: [/* @__PURE__ */
|
|
126
|
-
value:
|
|
127
|
-
onValueChange: (e) =>
|
|
128
|
-
children: [/* @__PURE__ */
|
|
126
|
+
children: [/* @__PURE__ */ k(o, {
|
|
127
|
+
value: M,
|
|
128
|
+
onValueChange: (e) => I({ sort: e === "name" ? void 0 : String(e) }),
|
|
129
|
+
children: [/* @__PURE__ */ O(l, {
|
|
129
130
|
size: "sm",
|
|
130
|
-
"aria-label":
|
|
131
|
-
children: /* @__PURE__ */
|
|
132
|
-
}), /* @__PURE__ */
|
|
131
|
+
"aria-label": x("groupManager.sort"),
|
|
132
|
+
children: /* @__PURE__ */ O(u, { children: (e) => x(`groupManager.sort_${e}`) })
|
|
133
|
+
}), /* @__PURE__ */ O(s, { children: j.map((e) => /* @__PURE__ */ O(c, {
|
|
133
134
|
value: e,
|
|
134
|
-
children:
|
|
135
|
+
children: x(`groupManager.sort_${e}`)
|
|
135
136
|
}, e)) })]
|
|
136
|
-
}),
|
|
137
|
-
value:
|
|
138
|
-
onValueChange: (e) =>
|
|
139
|
-
children: [/* @__PURE__ */
|
|
137
|
+
}), T.length > 1 && /* @__PURE__ */ k(o, {
|
|
138
|
+
value: P,
|
|
139
|
+
onValueChange: (e) => I({ language: e === T[0] ? void 0 : String(e) }),
|
|
140
|
+
children: [/* @__PURE__ */ O(l, {
|
|
140
141
|
size: "sm",
|
|
141
|
-
"aria-label":
|
|
142
|
-
children: /* @__PURE__ */
|
|
143
|
-
}), /* @__PURE__ */
|
|
142
|
+
"aria-label": x("groupManager.language"),
|
|
143
|
+
children: /* @__PURE__ */ O(u, { children: (e) => w(e) })
|
|
144
|
+
}), /* @__PURE__ */ O(s, { children: T.map((e) => /* @__PURE__ */ O(c, {
|
|
144
145
|
value: e,
|
|
145
|
-
children:
|
|
146
|
+
children: w(e)
|
|
146
147
|
}, e)) })]
|
|
147
148
|
})]
|
|
148
149
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
150
|
-
"aria-label":
|
|
150
|
+
/* @__PURE__ */ O("nav", {
|
|
151
|
+
"aria-label": x("groups.byLetter"),
|
|
151
152
|
className: "flex flex-wrap gap-1",
|
|
152
153
|
"data-ckui": "letters",
|
|
153
|
-
children: [void 0, ...
|
|
154
|
+
children: [void 0, ...S].map((e) => /* @__PURE__ */ O(i, {
|
|
154
155
|
size: "xs",
|
|
155
|
-
variant:
|
|
156
|
-
"aria-pressed":
|
|
157
|
-
"aria-label": e === void 0 ? void 0 : e === "#" ?
|
|
156
|
+
variant: D === e ? "default" : "ghost",
|
|
157
|
+
"aria-pressed": D === e,
|
|
158
|
+
"aria-label": e === void 0 ? void 0 : e === "#" ? x("groups.otherLetters") : e.toUpperCase(),
|
|
158
159
|
className: "min-w-7",
|
|
159
|
-
onClick: () =>
|
|
160
|
-
children: e === void 0 ?
|
|
160
|
+
onClick: () => I({ letter: e }),
|
|
161
|
+
children: e === void 0 ? x("groups.all") : e.toUpperCase()
|
|
161
162
|
}, e ?? "all"))
|
|
162
163
|
}),
|
|
163
|
-
|
|
164
|
-
error:
|
|
165
|
-
message:
|
|
166
|
-
retry:
|
|
167
|
-
}) :
|
|
164
|
+
L.loading && !L.items.length ? /* @__PURE__ */ O(h, {}) : L.error && !L.items.length ? /* @__PURE__ */ O(p, {
|
|
165
|
+
error: L.error,
|
|
166
|
+
message: x("groupManager.loadFailed"),
|
|
167
|
+
retry: L.reload
|
|
168
|
+
}) : L.items.length ? /* @__PURE__ */ O("ul", {
|
|
168
169
|
className: "grid divide-y divide-border rounded-lg border border-border",
|
|
169
|
-
"aria-busy":
|
|
170
|
-
children:
|
|
170
|
+
"aria-busy": L.loading,
|
|
171
|
+
children: L.items.map((e) => /* @__PURE__ */ k("li", {
|
|
171
172
|
className: "flex flex-wrap items-center gap-3 p-3",
|
|
172
173
|
"data-ckui": "group-row",
|
|
173
174
|
"data-slug": e.slug,
|
|
174
|
-
children: [/* @__PURE__ */
|
|
175
|
+
children: [/* @__PURE__ */ k("div", {
|
|
175
176
|
className: "grid min-w-0 flex-1 gap-0.5",
|
|
176
177
|
children: [
|
|
177
|
-
/* @__PURE__ */
|
|
178
|
+
/* @__PURE__ */ O("button", {
|
|
178
179
|
type: "button",
|
|
179
180
|
className: "text-left font-medium break-words hover:underline",
|
|
180
|
-
onClick: () => r.go(`${
|
|
181
|
+
onClick: () => r.go(`${f}/${e.id}`),
|
|
181
182
|
children: e.name || e.slug
|
|
182
183
|
}),
|
|
183
|
-
/* @__PURE__ */
|
|
184
|
+
/* @__PURE__ */ O("span", {
|
|
184
185
|
className: "text-xs text-muted-foreground",
|
|
185
186
|
children: e.slug
|
|
186
187
|
}),
|
|
187
|
-
e.internal && /* @__PURE__ */
|
|
188
|
+
e.internal && /* @__PURE__ */ O(a, {
|
|
188
189
|
variant: "secondary",
|
|
189
190
|
className: "justify-self-start",
|
|
190
|
-
children:
|
|
191
|
+
children: x("groupEditor.internal")
|
|
191
192
|
})
|
|
192
193
|
]
|
|
193
|
-
}), /* @__PURE__ */
|
|
194
|
+
}), /* @__PURE__ */ O("span", {
|
|
194
195
|
className: "text-sm text-muted-foreground tabular-nums",
|
|
195
|
-
title:
|
|
196
|
-
children: new Intl.NumberFormat(
|
|
196
|
+
title: x("groupManager.count"),
|
|
197
|
+
children: new Intl.NumberFormat(b.language).format(e.count)
|
|
197
198
|
})]
|
|
198
199
|
}, e.id))
|
|
199
|
-
}) : /* @__PURE__ */
|
|
200
|
+
}) : /* @__PURE__ */ O("p", {
|
|
200
201
|
className: "text-muted-foreground",
|
|
201
|
-
children:
|
|
202
|
+
children: x("groups.empty")
|
|
202
203
|
}),
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
L.hasMore && /* @__PURE__ */ k("div", {
|
|
205
|
+
className: "grid justify-items-center gap-2",
|
|
206
|
+
children: [L.error && /* @__PURE__ */ O("p", {
|
|
207
|
+
role: "alert",
|
|
208
|
+
className: "text-destructive",
|
|
209
|
+
children: d(b, L.error)
|
|
210
|
+
}), /* @__PURE__ */ O(i, {
|
|
211
|
+
variant: "outline",
|
|
212
|
+
size: "sm",
|
|
213
|
+
disabled: L.loadingMore,
|
|
214
|
+
onClick: () => void L.loadMore(),
|
|
215
|
+
children: L.loadingMore ? x("groups.loading") : L.error ? x("common.retry") : x("groups.loadMore")
|
|
216
|
+
})]
|
|
210
217
|
})
|
|
211
218
|
]
|
|
212
219
|
});
|
|
213
220
|
}
|
|
214
|
-
function
|
|
215
|
-
return /* @__PURE__ */
|
|
221
|
+
function I({ loc: e, kind: t, client: n, labels: r, onError: i }) {
|
|
222
|
+
return /* @__PURE__ */ k("div", {
|
|
216
223
|
className: "grid gap-4",
|
|
217
|
-
children: [/* @__PURE__ */
|
|
224
|
+
children: [/* @__PURE__ */ O(P, {
|
|
218
225
|
loc: e,
|
|
219
226
|
kind: t
|
|
220
|
-
}), /* @__PURE__ */
|
|
227
|
+
}), /* @__PURE__ */ O(w, {
|
|
221
228
|
management: !0,
|
|
222
229
|
id: null,
|
|
223
230
|
kind: t,
|
|
@@ -228,13 +235,13 @@ function F({ loc: e, kind: t, client: n, labels: r, onError: i }) {
|
|
|
228
235
|
})]
|
|
229
236
|
});
|
|
230
237
|
}
|
|
231
|
-
function
|
|
232
|
-
return /* @__PURE__ */
|
|
238
|
+
function L({ loc: e, kind: t, client: n, labels: r, onError: i, renderContent: a, id: o }) {
|
|
239
|
+
return /* @__PURE__ */ k("div", {
|
|
233
240
|
className: "grid gap-4",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
241
|
+
children: [/* @__PURE__ */ O(P, {
|
|
235
242
|
loc: e,
|
|
236
243
|
kind: t
|
|
237
|
-
}), /* @__PURE__ */
|
|
244
|
+
}), /* @__PURE__ */ O(w, {
|
|
238
245
|
management: !0,
|
|
239
246
|
id: o,
|
|
240
247
|
kind: t,
|
|
@@ -242,7 +249,7 @@ function I({ loc: e, kind: t, client: n, labels: r, onError: i, renderContent: a
|
|
|
242
249
|
client: n,
|
|
243
250
|
onError: i,
|
|
244
251
|
onMerged: (n) => e.go(`${t}/${n.into}`, void 0, { replace: !0 }),
|
|
245
|
-
children: (e) => /* @__PURE__ */
|
|
252
|
+
children: (e) => /* @__PURE__ */ O(T, {
|
|
246
253
|
group: e,
|
|
247
254
|
labels: r,
|
|
248
255
|
client: n,
|
|
@@ -254,6 +261,6 @@ function I({ loc: e, kind: t, client: n, labels: r, onError: i, renderContent: a
|
|
|
254
261
|
});
|
|
255
262
|
}
|
|
256
263
|
//#endregion
|
|
257
|
-
export {
|
|
264
|
+
export { M as GroupManager, A as useGroupManagerAccess };
|
|
258
265
|
|
|
259
266
|
//# sourceMappingURL=group-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-manager.js","names":[],"sources":["../../../src/components/admin/group-manager.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { LETTERS } from \"../../client/group-fields.js\";\nimport type { ContentModel, Membership } from \"../../client/generated/wire.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useGroups, useKinds, type GroupSort } from \"../../react/groups.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Tabs, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { GroupEditor } from \"../groups/editor.js\";\nimport { GroupMembers } from \"../groups/memberships.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\nexport interface GroupManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The group kinds managed, in tab order; default the content model's site-owned kinds. */\n groupKinds?: readonly string[];\n /** Display names of kinds, attributes, roles and values by identifier. */\n labels?: Labels;\n /** Counts of this content kind; default every kind. */\n contentKind?: string;\n /** One member, e.g. a link with its title; default its content id. */\n renderContent?: (member: Membership) => ReactNode;\n pageSize?: number;\n}\n\n/** Whether the caller may manage groups (root:groups:write). */\nexport function useGroupManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"groups\"> {\n return useStaffAccess([\"groups\"] as const, o);\n}\n\nconst SORTS: readonly GroupSort[] = [\"name\", \"count\", \"newest\"];\n\n/**\n * Staff: groups per kind. A kind's groups by letter, order and language; a\n * new group; a group's editor (GroupEditor) and its members (GroupMembers).\n * Kinds are at basePath/{kind}, a group at basePath/{kind}/{id}, filters in the query.\n */\nexport function GroupManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: GroupManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"group-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Manager {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<GroupManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\ntype Ctx = Inner & { loc: AdminLocation; kind: string; kinds: ContentModel };\n\nfunction Manager({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useGroupManagerAccess({ client: p.client });\n const model = useKinds({ client: p.client });\n const kinds = model.kinds;\n const list = useMemo(\n () =>\n p.groupKinds ??\n Object.entries(kinds?.entity_kinds ?? {})\n .filter(([, k]) => k.owner !== \"user\")\n .map(([name]) => name)\n .sort(),\n [p.groupKinds, kinds],\n );\n if (access.loading || model.loading) return <Rows />;\n if (!access.allowed.groups) return <NoAccess>{t(\"groupManager.noAccess\")}</NoAccess>;\n if (!kinds) return <LoadFailed error={model.error} message={t(\"groupManager.kindsFailed\")} retry={model.reload} />;\n if (!list.length) return <p className=\"text-muted-foreground\">{t(\"groupManager.noKinds\")}</p>;\n const [k, sub] = loc.view.split(\"/\");\n const kind = list.includes(k ?? \"\") ? k! : list[0]!;\n const ctx: Ctx = { ...p, loc, kind, kinds };\n return (\n <>\n {list.length > 1 && (\n <Tabs value={kind} onValueChange={(v) => loc.go(String(v))}>\n <TabsList className=\"flex-wrap group-data-horizontal/tabs:h-auto\">\n {list.map((x) => (\n <TabsTrigger key={x} value={x} className=\"h-7.5\">\n {labelOf(p.labels, x)}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )}\n {!sub ? <List key={kind} {...ctx} /> : sub === \"new\" ? <Create key={kind} {...ctx} /> : <Edit key={sub} {...ctx} id={sub} />}\n </>\n );\n}\n\nfunction Back({ loc, kind }: { loc: AdminLocation; kind: string }) {\n const { t } = useMessages();\n return (\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind)}>\n {t(\"groupManager.back\")}\n </Button>\n );\n}\n\nfunction List({ loc, kind, kinds, client, contentKind, labels, pageSize = 50 }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const languages = kinds.languages;\n const letterParam = loc.query.get(\"letter\") ?? \"\";\n const letter = LETTERS.includes(letterParam) ? letterParam : undefined;\n const sortParam = loc.query.get(\"sort\") ?? \"\";\n const sort = (SORTS as readonly string[]).includes(sortParam) ? (sortParam as GroupSort) : \"name\";\n const languageParam = loc.query.get(\"language\") ?? \"\";\n const language = languages.includes(languageParam) ? languageParam : languages[0];\n const filters: { letter?: string; sort?: GroupSort; language?: string } = { letter, sort: sort === \"name\" ? undefined : sort, language: language === languages[0] ? undefined : language };\n const filter = (next: Partial<typeof filters>) => loc.go(kind, { ...filters, ...next });\n const groups = useGroups({ kind, letter, sort, language, contentKind, pageSize }, { client, management: true });\n const label = labelOf(labels, kind);\n return (\n <div className=\"grid gap-4\" data-ckui=\"group-list\" data-kind={kind}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{label}</h2>\n <Button size=\"sm\" onClick={() => loc.go(`${kind}/new`)}>\n {t(\"groupManager.new\", { kind: label })}\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Select value={sort} onValueChange={(v) => filter({ sort: v === \"name\" ? undefined : (String(v) as GroupSort) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.sort\")}>\n <SelectValue>{(v: string) => t(`groupManager.sort_${v as GroupSort}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {SORTS.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`groupManager.sort_${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languages.length > 1 && (\n <Select value={language} onValueChange={(v) => filter({ language: v === languages[0] ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.language\")}>\n <SelectValue>{(v: string) => languageName(v)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <nav aria-label={t(\"groups.byLetter\")} className=\"flex flex-wrap gap-1\" data-ckui=\"letters\">\n {[undefined, ...LETTERS].map((l) => (\n <Button\n key={l ?? \"all\"}\n size=\"xs\"\n variant={letter === l ? \"default\" : \"ghost\"}\n aria-pressed={letter === l}\n aria-label={l === undefined ? undefined : l === \"#\" ? t(\"groups.otherLetters\") : l.toUpperCase()}\n className=\"min-w-7\"\n onClick={() => filter({ letter: l })}\n >\n {l === undefined ? t(\"groups.all\") : l.toUpperCase()}\n </Button>\n ))}\n </nav>\n {groups.loading && !groups.items.length ? (\n <Rows />\n ) : groups.error && !groups.items.length ? (\n <LoadFailed error={groups.error} message={t(\"groupManager.loadFailed\")} retry={groups.reload} />\n ) : !groups.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"groups.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={groups.loading}>\n {groups.items.map((g) => (\n <li key={g.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"group-row\" data-slug={g.slug}>\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(`${kind}/${g.id}`)}>\n {g.name || g.slug}\n </button>\n <span className=\"text-xs text-muted-foreground\">{g.slug}</span>\n {g.internal && <Badge variant=\"secondary\" className=\"justify-self-start\">{t(\"groupEditor.internal\")}</Badge>}\n </div>\n <span className=\"text-sm text-muted-foreground tabular-nums\" title={t(\"groupManager.count\")}>\n {new Intl.NumberFormat(m.language).format(g.count)}\n </span>\n </li>\n ))}\n </ul>\n )}\n {groups.hasMore && (\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-center\" disabled={groups.loadingMore} onClick={() => void groups.loadMore()}>\n {groups.loadingMore ? t(\"groups.loading\") : t(\"groups.loadMore\")}\n </Button>\n )}\n </div>\n );\n}\n\nfunction Create({ loc, kind, client, labels, onError }: Ctx) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n management\n id={null}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onCreated={(g) => loc.go(`${kind}/${g.id}`, undefined, { replace: true })}\n />\n </div>\n );\n}\n\nfunction Edit({ loc, kind, client, labels, onError, renderContent, id }: Ctx & { id: string }) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n management\n id={id}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onMerged={(r) => loc.go(`${kind}/${r.into}`, undefined, { replace: true })}\n >\n {(group) => (\n <GroupMembers\n group={group}\n labels={labels}\n client={client}\n onError={onError}\n renderContent={renderContent}\n onUseCover={(content) => client.entities.update(group.id, { cover: { content_id: content } })}\n />\n )}\n </GroupEditor>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"group-manager.js","names":[],"sources":["../../../src/components/admin/group-manager.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { LETTERS } from \"../../client/group-fields.js\";\nimport type { ContentModel, Membership } from \"../../client/generated/wire.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useGroups, useKinds, type GroupSort } from \"../../react/groups.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Tabs, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { GroupEditor } from \"../groups/editor.js\";\nimport { GroupMembers } from \"../groups/memberships.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport { contentError } from \"../social/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\nexport interface GroupManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The group kinds managed, in tab order; default the content model's site-owned kinds. */\n groupKinds?: readonly string[];\n /** Display names of kinds, attributes, roles and values by identifier. */\n labels?: Labels;\n /** Counts of this content kind; default every kind. */\n contentKind?: string;\n /** One member, e.g. a link with its title; default its content id. */\n renderContent?: (member: Membership) => ReactNode;\n pageSize?: number;\n}\n\n/** Whether the caller may manage groups (root:groups:write). */\nexport function useGroupManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"groups\"> {\n return useStaffAccess([\"groups\"] as const, o);\n}\n\nconst SORTS: readonly GroupSort[] = [\"name\", \"count\", \"newest\"];\n\n/**\n * Staff: groups per kind. A kind's groups by letter, order and language; a\n * new group; a group's editor (GroupEditor) and its members (GroupMembers).\n * Kinds are at basePath/{kind}, a group at basePath/{kind}/{id}, filters in the query.\n */\nexport function GroupManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: GroupManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"group-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Manager {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<GroupManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\ntype Ctx = Inner & { loc: AdminLocation; kind: string; kinds: ContentModel };\n\nfunction Manager({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useGroupManagerAccess({ client: p.client });\n const model = useKinds({ client: p.client });\n const kinds = model.kinds;\n const list = useMemo(\n () =>\n p.groupKinds ??\n Object.entries(kinds?.entity_kinds ?? {})\n .filter(([, k]) => k.owner !== \"user\")\n .map(([name]) => name)\n .sort(),\n [p.groupKinds, kinds],\n );\n if (access.loading || model.loading) return <Rows />;\n if (!access.allowed.groups) return <NoAccess>{t(\"groupManager.noAccess\")}</NoAccess>;\n if (!kinds) return <LoadFailed error={model.error} message={t(\"groupManager.kindsFailed\")} retry={model.reload} />;\n if (!list.length) return <p className=\"text-muted-foreground\">{t(\"groupManager.noKinds\")}</p>;\n const [k, sub] = loc.view.split(\"/\");\n const kind = list.includes(k ?? \"\") ? k! : list[0]!;\n const ctx: Ctx = { ...p, loc, kind, kinds };\n return (\n <>\n {list.length > 1 && (\n <Tabs value={kind} onValueChange={(v) => loc.go(String(v))}>\n <TabsList className=\"flex-wrap group-data-horizontal/tabs:h-auto\">\n {list.map((x) => (\n <TabsTrigger key={x} value={x} className=\"h-7.5\">\n {labelOf(p.labels, x)}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )}\n {!sub ? <List key={kind} {...ctx} /> : sub === \"new\" ? <Create key={kind} {...ctx} /> : <Edit key={sub} {...ctx} id={sub} />}\n </>\n );\n}\n\nfunction Back({ loc, kind }: { loc: AdminLocation; kind: string }) {\n const { t } = useMessages();\n return (\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(kind)}>\n {t(\"groupManager.back\")}\n </Button>\n );\n}\n\nfunction List({ loc, kind, kinds, client, contentKind, labels, pageSize = 50 }: Ctx) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const languages = kinds.languages;\n const letterParam = loc.query.get(\"letter\") ?? \"\";\n const letter = LETTERS.includes(letterParam) ? letterParam : undefined;\n const sortParam = loc.query.get(\"sort\") ?? \"\";\n const sort = (SORTS as readonly string[]).includes(sortParam) ? (sortParam as GroupSort) : \"name\";\n const languageParam = loc.query.get(\"language\") ?? \"\";\n const language = languages.includes(languageParam) ? languageParam : languages[0];\n const filters: { letter?: string; sort?: GroupSort; language?: string } = { letter, sort: sort === \"name\" ? undefined : sort, language: language === languages[0] ? undefined : language };\n const filter = (next: Partial<typeof filters>) => loc.go(kind, { ...filters, ...next });\n const groups = useGroups({ kind, letter, sort, language, contentKind, pageSize }, { client, management: true });\n const label = labelOf(labels, kind);\n return (\n <div className=\"grid gap-4\" data-ckui=\"group-list\" data-kind={kind}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{label}</h2>\n <Button size=\"sm\" onClick={() => loc.go(`${kind}/new`)}>\n {t(\"groupManager.new\", { kind: label })}\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Select value={sort} onValueChange={(v) => filter({ sort: v === \"name\" ? undefined : (String(v) as GroupSort) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.sort\")}>\n <SelectValue>{(v: string) => t(`groupManager.sort_${v as GroupSort}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {SORTS.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`groupManager.sort_${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {languages.length > 1 && (\n <Select value={language} onValueChange={(v) => filter({ language: v === languages[0] ? undefined : String(v) })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"groupManager.language\")}>\n <SelectValue>{(v: string) => languageName(v)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <nav aria-label={t(\"groups.byLetter\")} className=\"flex flex-wrap gap-1\" data-ckui=\"letters\">\n {[undefined, ...LETTERS].map((l) => (\n <Button\n key={l ?? \"all\"}\n size=\"xs\"\n variant={letter === l ? \"default\" : \"ghost\"}\n aria-pressed={letter === l}\n aria-label={l === undefined ? undefined : l === \"#\" ? t(\"groups.otherLetters\") : l.toUpperCase()}\n className=\"min-w-7\"\n onClick={() => filter({ letter: l })}\n >\n {l === undefined ? t(\"groups.all\") : l.toUpperCase()}\n </Button>\n ))}\n </nav>\n {groups.loading && !groups.items.length ? (\n <Rows />\n ) : groups.error && !groups.items.length ? (\n <LoadFailed error={groups.error} message={t(\"groupManager.loadFailed\")} retry={groups.reload} />\n ) : !groups.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"groups.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={groups.loading}>\n {groups.items.map((g) => (\n <li key={g.id} className=\"flex flex-wrap items-center gap-3 p-3\" data-ckui=\"group-row\" data-slug={g.slug}>\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(`${kind}/${g.id}`)}>\n {g.name || g.slug}\n </button>\n <span className=\"text-xs text-muted-foreground\">{g.slug}</span>\n {g.internal && <Badge variant=\"secondary\" className=\"justify-self-start\">{t(\"groupEditor.internal\")}</Badge>}\n </div>\n <span className=\"text-sm text-muted-foreground tabular-nums\" title={t(\"groupManager.count\")}>\n {new Intl.NumberFormat(m.language).format(g.count)}\n </span>\n </li>\n ))}\n </ul>\n )}\n {groups.hasMore && (\n <div className=\"grid justify-items-center gap-2\">\n {groups.error && <p role=\"alert\" className=\"text-destructive\">{contentError(m, groups.error)}</p>}\n <Button variant=\"outline\" size=\"sm\" disabled={groups.loadingMore} onClick={() => void groups.loadMore()}>\n {groups.loadingMore ? t(\"groups.loading\") : groups.error ? t(\"common.retry\") : t(\"groups.loadMore\")}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nfunction Create({ loc, kind, client, labels, onError }: Ctx) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n management\n id={null}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onCreated={(g) => loc.go(`${kind}/${g.id}`, undefined, { replace: true })}\n />\n </div>\n );\n}\n\nfunction Edit({ loc, kind, client, labels, onError, renderContent, id }: Ctx & { id: string }) {\n return (\n <div className=\"grid gap-4\">\n <Back loc={loc} kind={kind} />\n <GroupEditor\n management\n id={id}\n kind={kind}\n labels={labels}\n client={client}\n onError={onError}\n onMerged={(r) => loc.go(`${kind}/${r.into}`, undefined, { replace: true })}\n >\n {(group) => (\n <GroupMembers\n group={group}\n labels={labels}\n client={client}\n onError={onError}\n renderContent={renderContent}\n onUseCover={(content) => client.entities.update(group.id, { cover: { content_id: content } })}\n />\n )}\n </GroupEditor>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -213,12 +213,14 @@ function k({ user: r, client: a, itemHref: o, pageSize: l = 10, onError: d }) {
|
|
|
213
213
|
className: "text-muted-foreground",
|
|
214
214
|
children: p("standing.noComments")
|
|
215
215
|
}),
|
|
216
|
-
h.hasMore && /* @__PURE__ */
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
h.hasMore && /* @__PURE__ */ x("div", {
|
|
217
|
+
className: "grid justify-items-center gap-2",
|
|
218
|
+
children: [/* @__PURE__ */ b(s, { error: h.error }), /* @__PURE__ */ b(t, {
|
|
219
|
+
variant: "outline",
|
|
220
|
+
disabled: h.loadingMore,
|
|
221
|
+
onClick: () => void h.loadMore(),
|
|
222
|
+
children: h.error ? p("common.retry") : p("moderation.loadMore")
|
|
223
|
+
})]
|
|
222
224
|
})
|
|
223
225
|
]
|
|
224
226
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-comment-standing.js","names":[],"sources":["../../../src/components/admin/user-comment-standing.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { AdminComment } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient, useStaffAccess, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useAdminComments, useGlobalCommentBan } from \"../../react/comments.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { CommentBanDialog } from \"../social/bans.js\";\nimport { ConfirmDialog, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\nexport interface UserCommentStandingProps extends AdminFlowProps {\n /** The user's id. */\n user: string;\n /** Their name in headings and dialogs; else their username once known. */\n name?: string;\n /** A link to a commented item. */\n itemHref?: (item: { kind: string; id: string }) => string | undefined;\n /** Comments per page. Default 10. */\n pageSize?: number;\n}\n\ntype StandingArea = \"bans\" | \"comments\";\nconst AREAS: readonly StandingArea[] = [\"bans\", \"comments\"];\n\n/** Whether the caller may manage site-wide comment bans and moderate comments: what UserCommentStanding shows. */\nexport function useUserCommentStandingAccess(o: FlowClientOptions = {}): StaffAccess<StandingArea> {\n return useStaffAccess(AREAS, o);\n}\n\n/**\n * A user page's comment panel: the person's site-wide comment ban (ban,\n * replace, lift) and their recent comments (delete, restore), each as the\n * caller's permissions allow.\n */\nexport function UserCommentStanding({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UserCommentStandingProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"user-comment-standing\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Standing {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<UserCommentStandingProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Standing(p: Inner) {\n const { t } = useMessages();\n const access = useUserCommentStandingAccess({ client: p.client });\n const title = <h3 className=\"text-base font-semibold\">{t(\"standing.title\")}</h3>;\n if (access.loading)\n return (\n <>\n {title}\n <Rows count={2} />\n </>\n );\n if (!access.any)\n return (\n <>\n {title}\n <NoAccess>{t(\"standing.noAccess\")}</NoAccess>\n </>\n );\n return (\n <>\n {title}\n {access.allowed.bans && <BanCard {...p} />}\n {access.allowed.comments && <Recent {...p} />}\n </>\n );\n}\n\nfunction BanCard({ user, name, client, onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const g = useGlobalCommentBan(user, { client });\n const [editing, setEditing] = useState(false);\n const [lifting, setLifting] = useState(false);\n const lift = useAction(\"ban.lift\", onError);\n const who = name ?? g.ban?.user?.username ?? user;\n const ban = g.ban;\n return (\n <section className=\"grid gap-2 rounded-lg border border-border p-3\" data-ckui=\"standing-ban\" aria-busy={g.loading}>\n <h4 className=\"font-medium\">{t(\"standing.ban\")}</h4>\n {g.loading && !ban ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : g.error ? (\n <LoadFailed error={g.error} message={t(\"standing.banLoadFailed\")} retry={g.reload} />\n ) : !ban ? (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-muted-foreground\">{t(\"standing.notBanned\")}</p>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => setEditing(true)}>\n {t(\"comments.ban\")}\n </Button>\n </div>\n ) : (\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"grid min-w-0 gap-0.5\">\n <div>\n <Badge variant={ban.expired ? \"outline\" : \"destructive\"}>{t(ban.expired ? \"bans.expired\" : \"bans.active\")}</Badge>\n </div>\n {ban.reason && <p className=\"break-words\">{t(\"bans.reason\", { reason: ban.reason })}</p>}\n <p className=\"text-muted-foreground\">\n {ban.until ? t(\"bans.until\", { date: formatDate(m, ban.until) }) : t(\"bans.untilLifted\")} · {t(\"bans.bannedAt\", { date: formatDate(m, ban.banned_at, false) })}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setEditing(true)}>\n {t(\"bans.update\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => setLifting(true)}>\n {t(\"bans.lift\")}\n </Button>\n </div>\n </div>\n )}\n <ErrorLine error={lift.error} />\n {editing && <CommentBanDialog open onOpenChange={setEditing} user={{ id: user, name: who }} scopes={[\"global\"]} ban={ban} client={client} onError={onError} />}\n <ConfirmDialog\n open={lifting}\n onOpenChange={setLifting}\n title={t(\"bans.liftTitle\", { name: who })}\n description={t(\"bans.liftBody\")}\n confirm={t(\"bans.lift\")}\n onConfirm={() => void lift.run(g.lift)}\n />\n </section>\n );\n}\n\nfunction status(c: AdminComment): \"deleted\" | \"held\" | \"rejected\" | \"published\" {\n if (c.deleted) return \"deleted\";\n return c.moderation === \"held\" ? \"held\" : c.moderation === \"rejected\" ? \"rejected\" : \"published\";\n}\n\nfunction Recent({ user, client, itemHref, pageSize = 10, onError }: Inner) {\n const { t } = useMessages();\n const list = useAdminComments({ author: user, pageSize, client });\n const del = useAction(\"comment.delete\", onError);\n const restore = useAction(\"comment.restore\", onError);\n return (\n <section className=\"grid gap-2\" data-ckui=\"standing-comments\">\n <h4 className=\"font-medium\">{t(\"standing.comments\")}</h4>\n <ErrorLine error={del.error ?? restore.error} />\n {list.loading && !list.items.length ? (\n <Rows count={2} />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"moderation.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"standing.noComments\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {list.items.map((c) => {\n const s = status(c);\n const href = itemHref?.({ kind: c.content_kind, id: c.content_id });\n return (\n <li key={c.id} className=\"grid gap-1.5 p-3\" data-ckui=\"admin-comment\" data-status={s}>\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n <RelativeTime at={c.created_at} />\n <Badge variant={s === \"published\" ? \"outline\" : s === \"held\" ? \"secondary\" : \"destructive\"}>{t(`moderation.${s}`)}</Badge>\n <span>\n {t(\"moderation.on\")}{\" \"}\n {href ? (\n <a className=\"hover:underline\" href={href}>\n {c.content_kind}/{c.content_id}\n </a>\n ) : (\n `${c.content_kind}/${c.content_id}`\n )}\n </span>\n </div>\n <p className={cn(\"line-clamp-3 break-words whitespace-pre-wrap\", c.deleted && \"text-muted-foreground line-through\")}>{c.body}</p>\n <div>\n {c.deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={restore.pending} onClick={() => void restore.run(() => client.comments.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void del.run(() => client.comments.delete(c.id))}>\n {t(\"moderation.delete\")}\n </Button>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n {list.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {t(\"moderation.loadMore\")}\n </Button>\n )}\n </section>\n );\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user-comment-standing.js","names":[],"sources":["../../../src/components/admin/user-comment-standing.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { AdminComment } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient, useStaffAccess, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useAdminComments, useGlobalCommentBan } from \"../../react/comments.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { CommentBanDialog } from \"../social/bans.js\";\nimport { ConfirmDialog, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\nexport interface UserCommentStandingProps extends AdminFlowProps {\n /** The user's id. */\n user: string;\n /** Their name in headings and dialogs; else their username once known. */\n name?: string;\n /** A link to a commented item. */\n itemHref?: (item: { kind: string; id: string }) => string | undefined;\n /** Comments per page. Default 10. */\n pageSize?: number;\n}\n\ntype StandingArea = \"bans\" | \"comments\";\nconst AREAS: readonly StandingArea[] = [\"bans\", \"comments\"];\n\n/** Whether the caller may manage site-wide comment bans and moderate comments: what UserCommentStanding shows. */\nexport function useUserCommentStandingAccess(o: FlowClientOptions = {}): StaffAccess<StandingArea> {\n return useStaffAccess(AREAS, o);\n}\n\n/**\n * A user page's comment panel: the person's site-wide comment ban (ban,\n * replace, lift) and their recent comments (delete, restore), each as the\n * caller's permissions allow.\n */\nexport function UserCommentStanding({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UserCommentStandingProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"user-comment-standing\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Standing {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<UserCommentStandingProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Standing(p: Inner) {\n const { t } = useMessages();\n const access = useUserCommentStandingAccess({ client: p.client });\n const title = <h3 className=\"text-base font-semibold\">{t(\"standing.title\")}</h3>;\n if (access.loading)\n return (\n <>\n {title}\n <Rows count={2} />\n </>\n );\n if (!access.any)\n return (\n <>\n {title}\n <NoAccess>{t(\"standing.noAccess\")}</NoAccess>\n </>\n );\n return (\n <>\n {title}\n {access.allowed.bans && <BanCard {...p} />}\n {access.allowed.comments && <Recent {...p} />}\n </>\n );\n}\n\nfunction BanCard({ user, name, client, onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const g = useGlobalCommentBan(user, { client });\n const [editing, setEditing] = useState(false);\n const [lifting, setLifting] = useState(false);\n const lift = useAction(\"ban.lift\", onError);\n const who = name ?? g.ban?.user?.username ?? user;\n const ban = g.ban;\n return (\n <section className=\"grid gap-2 rounded-lg border border-border p-3\" data-ckui=\"standing-ban\" aria-busy={g.loading}>\n <h4 className=\"font-medium\">{t(\"standing.ban\")}</h4>\n {g.loading && !ban ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : g.error ? (\n <LoadFailed error={g.error} message={t(\"standing.banLoadFailed\")} retry={g.reload} />\n ) : !ban ? (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-muted-foreground\">{t(\"standing.notBanned\")}</p>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => setEditing(true)}>\n {t(\"comments.ban\")}\n </Button>\n </div>\n ) : (\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"grid min-w-0 gap-0.5\">\n <div>\n <Badge variant={ban.expired ? \"outline\" : \"destructive\"}>{t(ban.expired ? \"bans.expired\" : \"bans.active\")}</Badge>\n </div>\n {ban.reason && <p className=\"break-words\">{t(\"bans.reason\", { reason: ban.reason })}</p>}\n <p className=\"text-muted-foreground\">\n {ban.until ? t(\"bans.until\", { date: formatDate(m, ban.until) }) : t(\"bans.untilLifted\")} · {t(\"bans.bannedAt\", { date: formatDate(m, ban.banned_at, false) })}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setEditing(true)}>\n {t(\"bans.update\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" onClick={() => setLifting(true)}>\n {t(\"bans.lift\")}\n </Button>\n </div>\n </div>\n )}\n <ErrorLine error={lift.error} />\n {editing && <CommentBanDialog open onOpenChange={setEditing} user={{ id: user, name: who }} scopes={[\"global\"]} ban={ban} client={client} onError={onError} />}\n <ConfirmDialog\n open={lifting}\n onOpenChange={setLifting}\n title={t(\"bans.liftTitle\", { name: who })}\n description={t(\"bans.liftBody\")}\n confirm={t(\"bans.lift\")}\n onConfirm={() => void lift.run(g.lift)}\n />\n </section>\n );\n}\n\nfunction status(c: AdminComment): \"deleted\" | \"held\" | \"rejected\" | \"published\" {\n if (c.deleted) return \"deleted\";\n return c.moderation === \"held\" ? \"held\" : c.moderation === \"rejected\" ? \"rejected\" : \"published\";\n}\n\nfunction Recent({ user, client, itemHref, pageSize = 10, onError }: Inner) {\n const { t } = useMessages();\n const list = useAdminComments({ author: user, pageSize, client });\n const del = useAction(\"comment.delete\", onError);\n const restore = useAction(\"comment.restore\", onError);\n return (\n <section className=\"grid gap-2\" data-ckui=\"standing-comments\">\n <h4 className=\"font-medium\">{t(\"standing.comments\")}</h4>\n <ErrorLine error={del.error ?? restore.error} />\n {list.loading && !list.items.length ? (\n <Rows count={2} />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"moderation.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(\"standing.noComments\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {list.items.map((c) => {\n const s = status(c);\n const href = itemHref?.({ kind: c.content_kind, id: c.content_id });\n return (\n <li key={c.id} className=\"grid gap-1.5 p-3\" data-ckui=\"admin-comment\" data-status={s}>\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n <RelativeTime at={c.created_at} />\n <Badge variant={s === \"published\" ? \"outline\" : s === \"held\" ? \"secondary\" : \"destructive\"}>{t(`moderation.${s}`)}</Badge>\n <span>\n {t(\"moderation.on\")}{\" \"}\n {href ? (\n <a className=\"hover:underline\" href={href}>\n {c.content_kind}/{c.content_id}\n </a>\n ) : (\n `${c.content_kind}/${c.content_id}`\n )}\n </span>\n </div>\n <p className={cn(\"line-clamp-3 break-words whitespace-pre-wrap\", c.deleted && \"text-muted-foreground line-through\")}>{c.body}</p>\n <div>\n {c.deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={restore.pending} onClick={() => void restore.run(() => client.comments.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void del.run(() => client.comments.delete(c.id))}>\n {t(\"moderation.delete\")}\n </Button>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n {list.hasMore && (\n <div className=\"grid justify-items-center gap-2\">\n <ErrorLine error={list.error} />\n <Button variant=\"outline\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.error ? t(\"common.retry\") : t(\"moderation.loadMore\")}\n </Button>\n </div>\n )}\n </section>\n );\n}\n\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAM,IAAiC,CAAC,QAAQ,UAAU;AAG1D,SAAgB,EAA6B,IAAuB,CAAC,GAA8B;CACjG,OAAO,EAAe,GAAO,CAAC;AAChC;AAOA,SAAgB,EAAoB,EAAE,QAAQ,GAAK,YAAS,SAAM,UAAO,eAAY,cAAW,aAAU,aAAU,GAAG,KAA+B;CACpJ,IAAM,IAAS,EAAc;EAAE,QAAQ;EAAK;EAAS;CAAK,CAAC;CAC3D,OACE,kBAAC,GAAD;EAAW,MAAK;EAAoC;EAAuB;EAAqB;EAAoB;EAAiB;EACnI,UAAA,kBAAC,GAAD;GAAU,GAAI;GAAW;EAAS,CAAA;CACzB,CAAA;AAEf;AAIA,SAAS,EAAS,GAAU;CAC1B,IAAM,EAAE,SAAM,EAAY,GACpB,IAAS,EAA6B,EAAE,QAAQ,EAAE,OAAO,CAAC,GAC1D,IAAQ,kBAAC,MAAD;EAAI,WAAU;EAA2B,UAAA,EAAE,gBAAgB;CAAM,CAAA;CAe/E,OAdI,EAAO,UAEP,kBAAA,GAAA,EAAA,UAAA,CACG,GACD,kBAAC,GAAD,EAAM,OAAO,EAAI,CAAA,CACjB,EAAA,CAAA,IAED,EAAO,MAQV,kBAAA,GAAA,EAAA,UAAA;EACG;EACA,EAAO,QAAQ,QAAQ,kBAAC,GAAD,EAAS,GAAI,EAAI,CAAA;EACxC,EAAO,QAAQ,YAAY,kBAAC,GAAD,EAAQ,GAAI,EAAI,CAAA;CAC5C,EAAA,CAAA,IAVA,kBAAA,GAAA,EAAA,UAAA,CACG,GACD,kBAAC,GAAD,EAAA,UAAW,EAAE,mBAAmB,EAAY,CAAA,CAC5C,EAAA,CAAA;AASR;AAEA,SAAS,EAAQ,EAAE,SAAM,SAAM,WAAQ,cAAkB;CACvD,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAI,EAAoB,GAAM,EAAE,UAAO,CAAC,GACxC,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,IAAO,EAAU,YAAY,CAAO,GACpC,IAAM,KAAQ,EAAE,KAAK,MAAM,YAAY,GACvC,IAAM,EAAE;CACd,OACE,kBAAC,WAAD;EAAS,WAAU;EAAiD,aAAU;EAAe,aAAW,EAAE;EAA1G,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;IAAe,UAAA,EAAE,cAAc;GAAM,CAAA;GAClD,EAAE,WAAW,CAAC,IACb,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,IACjC,EAAE,QACJ,kBAAC,GAAD;IAAY,OAAO,EAAE;IAAO,SAAS,EAAE,wBAAwB;IAAG,OAAO,EAAE;GAAS,CAAA,IACjF,IAQH,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD;OAAO,SAAS,EAAI,UAAU,YAAY;OAAgB,UAAA,EAAE,EAAI,UAAU,iBAAiB,aAAa;MAAS,CAAA,EAC9G,CAAA;MACJ,EAAI,UAAU,kBAAC,KAAD;OAAG,WAAU;OAAe,UAAA,EAAE,eAAe,EAAE,QAAQ,EAAI,OAAO,CAAC;MAAK,CAAA;MACvF,kBAAC,KAAD;OAAG,WAAU;OAAb,UAAA;QACG,EAAI,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAW,GAAG,EAAI,KAAK,EAAE,CAAC,IAAI,EAAE,kBAAkB;QAAE;QAAI,EAAE,iBAAiB,EAAE,MAAM,EAAW,GAAG,EAAI,WAAW,EAAK,EAAE,CAAC;OAC5J;;KACA;IACL,CAAA,GAAA,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAQ;MAAU,eAAe,EAAW,EAAI;MAC/D,UAAA,EAAE,aAAa;KACV,CAAA,GACR,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAQ;MAAc,eAAe,EAAW,EAAI;MACnE,UAAA,EAAE,WAAW;KACR,CAAA,CACL;IACF,CAAA,CAAA;QAzBL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;KAAyB,UAAA,EAAE,oBAAoB;IAAK,CAAA,GACjE,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAc,eAAe,EAAW,EAAI;KACnE,UAAA,EAAE,cAAc;IACX,CAAA,CACL;GAEL,CAAA;GAoBF,kBAAC,GAAD,EAAW,OAAO,EAAK,MAAQ,CAAA;GAC9B,KAAW,kBAAC,GAAD;IAAkB,MAAA;IAAK,cAAc;IAAY,MAAM;KAAE,IAAI;KAAM,MAAM;IAAI;IAAG,QAAQ,CAAC,QAAQ;IAAQ;IAAa;IAAiB;GAAU,CAAA;GAC7J,kBAAC,GAAD;IACE,MAAM;IACN,cAAc;IACd,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAI,CAAC;IACxC,aAAa,EAAE,eAAe;IAC9B,SAAS,EAAE,WAAW;IACtB,iBAAiB,KAAK,EAAK,IAAI,EAAE,IAAI;GACtC,CAAA;EACM;;AAEb;AAEA,SAAS,EAAO,GAAgE;CAE9E,OADI,EAAE,UAAgB,YACf,EAAE,eAAe,SAAS,SAAS,EAAE,eAAe,aAAa,aAAa;AACvF;AAEA,SAAS,EAAO,EAAE,SAAM,WAAQ,aAAU,cAAW,IAAI,cAAkB;CACzE,IAAM,EAAE,SAAM,EAAY,GACpB,IAAO,EAAiB;EAAE,QAAQ;EAAM;EAAU;CAAO,CAAC,GAC1D,IAAM,EAAU,kBAAkB,CAAO,GACzC,IAAU,EAAU,mBAAmB,CAAO;CACpD,OACE,kBAAC,WAAD;EAAS,WAAU;EAAa,aAAU;EAA1C,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;IAAe,UAAA,EAAE,mBAAmB;GAAM,CAAA;GACxD,kBAAC,GAAD,EAAW,OAAO,EAAI,SAAS,EAAQ,MAAQ,CAAA;GAC9C,EAAK,WAAW,CAAC,EAAK,MAAM,SAC3B,kBAAC,GAAD,EAAM,OAAO,EAAI,CAAA,IACf,EAAK,SAAS,CAAC,EAAK,MAAM,SAC5B,kBAAC,GAAD;IAAY,OAAO,EAAK;IAAO,SAAS,EAAE,uBAAuB;IAAG,OAAO,EAAK;GAAS,CAAA,IACtF,EAAK,MAAM,SAGd,kBAAC,MAAD;IAAI,WAAU;IACX,UAAA,EAAK,MAAM,KAAK,MAAM;KACrB,IAAM,IAAI,EAAO,CAAC,GACZ,IAAO,IAAW;MAAE,MAAM,EAAE;MAAc,IAAI,EAAE;KAAW,CAAC;KAClE,OACE,kBAAC,MAAD;MAAe,WAAU;MAAmB,aAAU;MAAgB,eAAa;MAAnF,UAAA;OACE,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA;SACE,kBAAC,GAAD,EAAc,IAAI,EAAE,WAAa,CAAA;SACjC,kBAAC,GAAD;UAAO,SAAS,MAAM,cAAc,YAAY,MAAM,SAAS,cAAc;UAAgB,UAAA,EAAE,cAAc,GAAG;SAAS,CAAA;SACzH,kBAAC,QAAD,EAAA,UAAA;UACG,EAAE,eAAe;UAAG;UACpB,IACC,kBAAC,KAAD;WAAG,WAAU;WAAwB;WAArC,UAAA;YACG,EAAE;YAAa;YAAE,EAAE;WACnB;UAEH,CAAA,IAAA,GAAG,EAAE,aAAa,GAAG,EAAE;SAErB,EAAA,CAAA;QACH;;OACL,kBAAC,KAAD;QAAG,WAAW,EAAG,gDAAgD,EAAE,WAAW,oCAAoC;QAAI,UAAA,EAAE;OAAQ,CAAA;OAChI,kBAAC,OAAD,EAAA,UACG,EAAE,UACD,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAU,UAAU,EAAQ;QAAS,eAAe,KAAK,EAAQ,UAAU,EAAO,SAAS,QAAQ,EAAE,EAAE,CAAC;QAC/H,UAAA,EAAE,oBAAoB;OACjB,CAAA,IAER,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAc,UAAU,EAAI;QAAS,eAAe,KAAK,EAAI,UAAU,EAAO,SAAS,OAAO,EAAE,EAAE,CAAC;QAC1H,UAAA,EAAE,mBAAmB;OAChB,CAAA,EAEP,CAAA;MACH;KA3BK,GAAA,EAAE,EA2BP;IAER,CAAC;GACC,CAAA,IArCJ,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,qBAAqB;GAAK,CAAA;GAuCnE,EAAK,WACJ,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD,EAAW,OAAO,EAAK,MAAQ,CAAA,GAC/B,kBAAC,GAAD;KAAQ,SAAQ;KAAU,UAAU,EAAK;KAAa,eAAe,KAAK,EAAK,SAAS;KACrF,UAAA,EAAK,QAAQ,EAAE,cAAc,IAAI,EAAE,qBAAqB;IACnD,CAAA,CACL;;EAEA;;AAEb"}
|
|
@@ -94,7 +94,7 @@ function D({ card: i, client: c, href: u, onOpen: d, shape: p = "portrait", aspe
|
|
|
94
94
|
}), [fe, pe] = g(null);
|
|
95
95
|
return /* @__PURE__ */ v("article", {
|
|
96
96
|
ref: pe,
|
|
97
|
-
className: y("group/card grid content-start gap-2", le),
|
|
97
|
+
className: y("group/card relative grid content-start gap-2", le),
|
|
98
98
|
"data-ckui": "content-card",
|
|
99
99
|
"data-id": i.id,
|
|
100
100
|
"data-locked": V ? "" : void 0,
|
|
@@ -276,7 +276,7 @@ function k({ url: e, name: t, variant: n = "chips" }) {
|
|
|
276
276
|
function A({ card: t, href: n, onOpen: i, tiers: a, labels: o, byline: s, owner: l = !0, ownerHref: u, loadMedia: d = !0, showCounts: f = !0, decorate: p, children: h, className: g }) {
|
|
277
277
|
let x = r(), D = n ? n(t) : t.path, k = E(D, m(() => i?.(t), [i, t])), A = T(x, t.access, a, o), j = t.title || t.code;
|
|
278
278
|
return /* @__PURE__ */ v("article", {
|
|
279
|
-
className: y("grid gap-3 rounded-lg border border-border p-3", g),
|
|
279
|
+
className: y("relative grid gap-3 rounded-lg border border-border p-3", g),
|
|
280
280
|
"data-ckui": "post-card",
|
|
281
281
|
"data-id": t.id,
|
|
282
282
|
"data-locked": A ? "" : void 0,
|