@openrails/contentkit-ui 0.68.0 → 0.68.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -10
- package/dist/client/client.d.ts +4 -2
- package/dist/client/client.js +1 -0
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/bans.js +7 -7
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.js +16 -14
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.js +5 -5
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.js +5 -5
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/polls.js +34 -31
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.js +21 -20
- package/dist/client/content/posts.js.map +1 -1
- package/dist/client/content/reactions.js +4 -4
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/http.d.ts +6 -2
- package/dist/client/http.js +11 -3
- package/dist/client/http.js.map +1 -1
- package/dist/client/media/client.js +24 -24
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +2 -0
- package/dist/client/media/rules.js +6 -2
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/taxonomy.js +2 -2
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/media-folder-editor.d.ts +17 -3
- package/dist/components/media-folder-editor.js +146 -99
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/slot-upload.js +102 -100
- package/dist/components/slot-upload.js.map +1 -1
- package/dist/react/canonical.d.ts +2 -2
- package/dist/react/canonical.js +2 -12
- package/dist/react/canonical.js.map +1 -1
- package/dist/react/comments.js +35 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -0
- package/dist/react/context.js +20 -15
- package/dist/react/context.js.map +1 -1
- package/dist/react/engagement.js +2 -2
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.d.ts +29 -9
- package/dist/react/folder.js +170 -113
- package/dist/react/folder.js.map +1 -1
- package/dist/react/polls.js +3 -3
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +2 -2
- package/dist/react/posts.js.map +1 -1
- package/dist/react/provider.d.ts +7 -3
- package/dist/react/provider.js +34 -20
- package/dist/react/provider.js.map +1 -1
- package/dist/react/public.js +11 -11
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +93 -92
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +6 -3
- package/dist/react/resources.js +40 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/slot.d.ts +4 -2
- package/dist/react/slot.js +96 -92
- package/dist/react/slot.js.map +1 -1
- package/dist/react/store.d.ts +1 -1
- package/dist/react/store.js +21 -20
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.d.ts +3 -2
- package/dist/react/upload.js +20 -15
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -0
- package/dist/react/use-resource.js +38 -30
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +15 -15
- package/package.json +7 -5
package/dist/react/folder.js
CHANGED
|
@@ -1,183 +1,240 @@
|
|
|
1
1
|
import { ContentKitError as e, failureError as t, toContentKitError as n } from "../client/errors.js";
|
|
2
2
|
import { stem as r } from "../client/media/client.js";
|
|
3
|
-
import { filesFor as i, isPattern as a, ruleDir as o, ruleFor as s, screenFiles as
|
|
4
|
-
import { useContentKitClient as
|
|
5
|
-
import { useUploadQueue as
|
|
6
|
-
import { useMediaRead as
|
|
7
|
-
import { useCallback as
|
|
3
|
+
import { filesFor as i, isPattern as a, ruleDir as o, ruleFor as s, screenFiles as ee, uniqueName as te, uploadRules as c } from "../client/media/rules.js";
|
|
4
|
+
import { useContentKitClient as l, useErrorReporter as ne } from "./context.js";
|
|
5
|
+
import { useUploadQueue as u } from "./upload.js";
|
|
6
|
+
import { useMediaRead as re } from "./read.js";
|
|
7
|
+
import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
|
|
8
8
|
//#region src/react/folder.ts
|
|
9
|
-
var
|
|
9
|
+
var ie = {
|
|
10
10
|
start: 0,
|
|
11
11
|
end: 2 ** 53 - 1,
|
|
12
12
|
chunk: 200
|
|
13
|
-
},
|
|
14
|
-
function
|
|
13
|
+
}, g = (e, t) => t.slice(o(e.path).length);
|
|
14
|
+
function _(e, t) {
|
|
15
15
|
let n;
|
|
16
16
|
for (let r of e?.files ?? []) !r.upload && r.from === t.path && r.url && r.type.startsWith("image/") && (!n || (r.w ?? Infinity) < (n.w ?? Infinity)) && (n = r);
|
|
17
17
|
return n;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
var ae = {
|
|
20
|
+
kind: "",
|
|
21
|
+
id: ""
|
|
22
|
+
}, oe = (e) => e ? `${e.kind}/${e.id}` : "", v = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
function se(t) {
|
|
24
|
+
let n = v.get(t);
|
|
25
|
+
return n || v.set(t, n = new e("forbidden", "the editor read states no upload rules", { status: 403 })), n;
|
|
26
|
+
}
|
|
27
|
+
function ce(e, t) {
|
|
28
|
+
return e.filter((e) => e.status !== "failed" && !(e.status === "committed" && t.has(e.path)));
|
|
29
|
+
}
|
|
30
|
+
function y(_, v = {}) {
|
|
31
|
+
let y = l(v.client), b = ne(v.onError), x = _ ?? null, S = oe(x), C = x ?? ae, w = re(C, {
|
|
21
32
|
editor: !0,
|
|
22
|
-
window:
|
|
23
|
-
poll:
|
|
24
|
-
client:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
window: ie,
|
|
34
|
+
poll: v.poll,
|
|
35
|
+
client: y,
|
|
36
|
+
read: x ? void 0 : null
|
|
37
|
+
}), T = c(w.read), E = v.paths?.join("\n"), D = p(() => E === void 0 ? T.filter((e) => a(e.path) && !e.named).map((e) => e.path) : E.split("\n").filter(Boolean), [E, T]), O = p(() => {
|
|
38
|
+
let e = T.length ? T : D.map((e) => ({ path: e }));
|
|
39
|
+
return D.map((t) => ({
|
|
28
40
|
path: t,
|
|
29
|
-
rule:
|
|
30
|
-
files: i(
|
|
41
|
+
rule: T.find((e) => e.path === t),
|
|
42
|
+
files: i(w.read, { path: t }, e)
|
|
31
43
|
}));
|
|
32
44
|
}, [
|
|
33
|
-
k,
|
|
34
45
|
D,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
T,
|
|
47
|
+
w.read
|
|
48
|
+
]), k = p(() => O.flatMap((e) => e.files), [O]), A = p(() => new Set(k.map((e) => e.path)), [k]), j = u({
|
|
49
|
+
client: y,
|
|
50
|
+
ref: C,
|
|
51
|
+
path: `${o(D[0] ?? "{name}")}{name}`,
|
|
52
|
+
concurrency: v.concurrency
|
|
53
|
+
}), M = j.queue, N = p(() => j.items.filter((e) => !(e.status === "committed" && A.has(e.path))), [j.items, A]), le = {
|
|
54
|
+
...j,
|
|
55
|
+
items: N
|
|
56
|
+
}, [ue, P] = h([]), [F, I] = h([]), L = m([]), [R, de] = h(0), [z, fe] = h(!1), [pe, B] = h(), [me, V] = h(), H = me?.key === S ? me.error : void 0, U = m(v);
|
|
57
|
+
U.current = v;
|
|
58
|
+
let W = !!x && !!w.read?.uploads, G = x ? w.read ? w.read.uploads ? void 0 : se(w.read) : w.error : void 0, K = m({
|
|
59
|
+
groups: O,
|
|
60
|
+
ref: C,
|
|
61
|
+
listed: A,
|
|
62
|
+
ready: W,
|
|
63
|
+
loadError: G
|
|
46
64
|
});
|
|
47
|
-
|
|
48
|
-
groups:
|
|
49
|
-
ref:
|
|
65
|
+
K.current = {
|
|
66
|
+
groups: O,
|
|
67
|
+
ref: C,
|
|
68
|
+
listed: A,
|
|
69
|
+
ready: W,
|
|
70
|
+
loadError: G
|
|
50
71
|
};
|
|
51
|
-
let
|
|
52
|
-
let { groups: t } =
|
|
72
|
+
let he = d((e) => {
|
|
73
|
+
let { groups: t, listed: n } = K.current, r = t.map((e) => e.rule ?? {
|
|
53
74
|
path: e.path,
|
|
54
75
|
types: [],
|
|
55
76
|
max_bytes: 0
|
|
56
|
-
}),
|
|
57
|
-
let n =
|
|
77
|
+
}), i = ce(M.getSnapshot().items, n), a = (e, t) => s(r, e)?.path === t.path, c = ee(e, r, (e) => t.find((t) => t.path === e.path).files.length + i.filter((t) => a(t.path, e)).length), l = /* @__PURE__ */ new Map(), ne = (e) => {
|
|
78
|
+
let n = l.get(e.path);
|
|
58
79
|
if (!n) {
|
|
59
|
-
let
|
|
60
|
-
n = [...
|
|
80
|
+
let r = t.find((t) => t.path === e.path);
|
|
81
|
+
n = [...r.files.map((e) => g(r, e.path)), ...i.filter((t) => a(t.path, e)).map((e) => g(r, e.path))], l.set(e.path, n);
|
|
61
82
|
}
|
|
62
83
|
return n;
|
|
63
|
-
},
|
|
64
|
-
for (let { file: e, rule: t } of
|
|
65
|
-
let n =
|
|
66
|
-
n.push(r),
|
|
84
|
+
}, u = /* @__PURE__ */ new Map();
|
|
85
|
+
for (let { file: e, rule: t } of c.accepted) {
|
|
86
|
+
let n = ne(t), r = U.current.name?.(e) ?? te(e.name, n);
|
|
87
|
+
n.push(r), u.set(e, o(t.path) + r);
|
|
67
88
|
}
|
|
68
|
-
|
|
69
|
-
for (let e of
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
L((e)
|
|
89
|
+
u.size && (M.add(u.keys(), { path: (e) => u.get(e) }), M.start(), V(void 0)), P(c.refused);
|
|
90
|
+
for (let e of c.refused) b(e.error, "upload", e.file.name);
|
|
91
|
+
}, [M, b]), q = d(() => {
|
|
92
|
+
let e = L.current, { ready: t, loadError: n } = K.current;
|
|
93
|
+
return !e.length || !t && !n ? !1 : (L.current = [], I([]), t ? he(e) : P(e.map((e) => ({
|
|
94
|
+
file: e,
|
|
95
|
+
error: n
|
|
96
|
+
}))), !0);
|
|
97
|
+
}, [he]), ge = d((e) => {
|
|
98
|
+
let t = [...e];
|
|
99
|
+
t.length && (L.current = [...L.current, ...t], q() || I(L.current));
|
|
100
|
+
}, [q]);
|
|
101
|
+
f(() => void q(), [
|
|
102
|
+
q,
|
|
103
|
+
W,
|
|
104
|
+
G
|
|
105
|
+
]);
|
|
106
|
+
let J = d(async (e, t) => {
|
|
107
|
+
de((e) => e + 1);
|
|
73
108
|
try {
|
|
74
|
-
t ? await t() : await
|
|
109
|
+
t ? await t() : await y.media.commit(K.current.ref, e), B(void 0);
|
|
75
110
|
} catch (e) {
|
|
76
111
|
let t = n(e);
|
|
77
|
-
throw t.code !== "aborted" &&
|
|
112
|
+
throw t.code !== "aborted" && B(t), b(t, "folder.update"), t;
|
|
78
113
|
} finally {
|
|
79
|
-
|
|
114
|
+
de((e) => e - 1);
|
|
80
115
|
}
|
|
81
|
-
}, [
|
|
82
|
-
|
|
116
|
+
}, [y, b]), Y = d(async () => {
|
|
117
|
+
fe(!0);
|
|
83
118
|
try {
|
|
84
|
-
let e = await
|
|
85
|
-
|
|
86
|
-
return V(void 0), e;
|
|
119
|
+
let e = await M.commit(void 0, { head: U.current.commit === "auto" });
|
|
120
|
+
return B(void 0), e;
|
|
87
121
|
} catch (e) {
|
|
88
122
|
let t = n(e);
|
|
89
|
-
throw
|
|
123
|
+
throw B(t), b(t, "folder.commit"), t;
|
|
90
124
|
} finally {
|
|
91
|
-
|
|
125
|
+
fe(!1);
|
|
92
126
|
}
|
|
93
|
-
}, [
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
}, [M, b]);
|
|
128
|
+
f(() => {
|
|
129
|
+
for (let e of j.items) e.status === "committed" && (A.has(e.path) || !w.loading) && M.remove(e.id);
|
|
130
|
+
}, [
|
|
131
|
+
j.items,
|
|
132
|
+
A,
|
|
133
|
+
w.loading,
|
|
134
|
+
M
|
|
135
|
+
]);
|
|
136
|
+
let X = v.commit === "auto", Z = N.find((e) => e.status !== "committed")?.status === "uploaded";
|
|
137
|
+
f(() => {
|
|
138
|
+
X && Z && !z && !H && Y().catch((e) => V({
|
|
139
|
+
key: S,
|
|
140
|
+
error: n(e)
|
|
141
|
+
}));
|
|
96
142
|
}, [
|
|
97
|
-
Y,
|
|
98
143
|
X,
|
|
99
|
-
|
|
144
|
+
Z,
|
|
145
|
+
z,
|
|
100
146
|
H,
|
|
101
|
-
|
|
147
|
+
Y,
|
|
148
|
+
S
|
|
102
149
|
]);
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
for (let e of
|
|
106
|
-
|
|
150
|
+
let _e = d(async () => (V(void 0), Y()), [Y]), Q = m(/* @__PURE__ */ new Set());
|
|
151
|
+
f(() => {
|
|
152
|
+
for (let e of j.items) e.status === "failed" && e.error && !Q.current.has(e.error) && (Q.current.add(e.error), b(e.error, "upload", e.file.name));
|
|
153
|
+
j.blocked && !Q.current.has(j.blocked) && (Q.current.add(j.blocked), b(j.blocked, "upload"));
|
|
107
154
|
}, [
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
155
|
+
j.items,
|
|
156
|
+
j.blocked,
|
|
157
|
+
b
|
|
111
158
|
]);
|
|
112
|
-
let $ =
|
|
113
|
-
|
|
114
|
-
if (!
|
|
115
|
-
let e =
|
|
116
|
-
if (
|
|
117
|
-
$.current =
|
|
159
|
+
let $ = m(null);
|
|
160
|
+
f(() => {
|
|
161
|
+
if (!w.read) return;
|
|
162
|
+
let e = k.filter((e) => e.failed);
|
|
163
|
+
if ($.current?.key !== S) {
|
|
164
|
+
$.current = {
|
|
165
|
+
key: S,
|
|
166
|
+
seen: new Set(e.map((e) => `${e.path}:${e.failed.of}`))
|
|
167
|
+
};
|
|
118
168
|
return;
|
|
119
169
|
}
|
|
120
170
|
for (let n of e) {
|
|
121
171
|
let e = `${n.path}:${n.failed.of}`;
|
|
122
|
-
$.current.has(e) || ($.current.add(e),
|
|
172
|
+
$.current.seen.has(e) || ($.current.seen.add(e), b(t(n.failed), "folder.process", n.path.split("/").pop()));
|
|
123
173
|
}
|
|
124
174
|
}, [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
175
|
+
w.read,
|
|
176
|
+
k,
|
|
177
|
+
b,
|
|
178
|
+
S
|
|
179
|
+
]), f(() => void (w.error && b(w.error, "folder.load")), [w.error, b]);
|
|
180
|
+
let ve = F.length > 0 || z || R > 0 || X && Z && !H || N.some((e) => e.status === "uploading" || e.status === "queued" && !j.blocked), ye = k.length + F.length + ce(N, A).length, be = (e) => K.current.groups.find((t) => s([{ path: t.path }], e));
|
|
130
181
|
return {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
182
|
+
ref: x,
|
|
183
|
+
options: v,
|
|
184
|
+
read: w,
|
|
185
|
+
groups: O,
|
|
186
|
+
uploads: k,
|
|
187
|
+
queue: le,
|
|
188
|
+
waiting: F,
|
|
189
|
+
add: ge,
|
|
190
|
+
refused: ue,
|
|
191
|
+
dismiss: d(() => P([]), []),
|
|
192
|
+
move: d((e, t) => J([{
|
|
139
193
|
op: "move",
|
|
140
194
|
path: e,
|
|
141
195
|
index: t
|
|
142
|
-
}]), [
|
|
143
|
-
remove:
|
|
196
|
+
}]), [J]),
|
|
197
|
+
remove: d((e) => J(e.map((e) => ({
|
|
144
198
|
op: "remove",
|
|
145
199
|
path: e
|
|
146
|
-
}))), [
|
|
147
|
-
rename:
|
|
148
|
-
let i =
|
|
200
|
+
}))), [J]),
|
|
201
|
+
rename: d((t, n) => {
|
|
202
|
+
let i = be(t), a = (i ? o(i.path) : t.slice(0, t.lastIndexOf("/") + 1)) + n.trim();
|
|
149
203
|
if (r(a) === r(t)) return Promise.resolve();
|
|
150
204
|
if (i?.files.some((e) => r(e.path) === r(a))) {
|
|
151
205
|
let t = new e("conflict", `${n} exists`, { status: 409 });
|
|
152
|
-
return
|
|
206
|
+
return B(t), Promise.reject(t);
|
|
153
207
|
}
|
|
154
|
-
return
|
|
208
|
+
return J([{
|
|
155
209
|
op: "rename",
|
|
156
210
|
path: t,
|
|
157
211
|
to: a
|
|
158
212
|
}]);
|
|
159
|
-
}, [
|
|
160
|
-
edit:
|
|
213
|
+
}, [J]),
|
|
214
|
+
edit: d((e, t) => J([{
|
|
161
215
|
op: "edit",
|
|
162
216
|
path: e,
|
|
163
217
|
...t ? { edit: t } : {}
|
|
164
|
-
}]), [
|
|
165
|
-
replace:
|
|
166
|
-
ref:
|
|
218
|
+
}]), [J]),
|
|
219
|
+
replace: d((e, t) => J([], () => y.media.put(t, {
|
|
220
|
+
ref: K.current.ref,
|
|
167
221
|
path: r(e),
|
|
168
222
|
wait: !1
|
|
169
|
-
})), [
|
|
170
|
-
commit:
|
|
171
|
-
discard:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
223
|
+
})), [J, y]),
|
|
224
|
+
commit: _e,
|
|
225
|
+
discard: d(() => {
|
|
226
|
+
L.current = [], I([]);
|
|
227
|
+
for (let e of M.getSnapshot().items) M.remove(e.id);
|
|
228
|
+
M.pause();
|
|
229
|
+
}, [M]),
|
|
230
|
+
updating: R > 0,
|
|
231
|
+
committing: z,
|
|
232
|
+
busy: ve,
|
|
233
|
+
fileCount: ye,
|
|
234
|
+
error: pe ?? H
|
|
178
235
|
};
|
|
179
236
|
}
|
|
180
237
|
//#endregion
|
|
181
|
-
export {
|
|
238
|
+
export { g as nameIn, _ as thumbnailOf, y as useMediaFolder };
|
|
182
239
|
|
|
183
240
|
//# sourceMappingURL=folder.js.map
|
package/dist/react/folder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type; before the server states rules, to the first path.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n queue: UseUploadQueue;\n /** Screens files against the rules and queues the rest; returns the refusals. */\n add: (files: Iterable<File>) => Screened[\"refused\"];\n /** The last add()'s refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order and clears them from the queue. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Uploads the worker fails after this\n * mounted are reported once (\"folder.process\"). The queue is the first\n * item's: key the component by ref to switch items.\n */\nexport function useMediaFolder(ref: RefBody, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const read = useMediaRead(ref, { editor: true, window: ALL, poll: o.poll, client });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n\n const queue = useUploadQueue({ client, ref, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The UploadQueue itself is stable; the hook's wrapper is new every render.\n const q = queue.queue;\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stalled, setStalled] = useState<ContentKitError | undefined>();\n const opts = useRef(o);\n opts.current = o;\n const state = useRef({ groups, ref });\n state.current = { groups, ref };\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const { groups } = state.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = q.getSnapshot().items.filter((i) => i.status !== \"failed\");\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n t = [...g.files.map((f) => nameIn(g, f.path)), ...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path))];\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n if (q.getSnapshot().blocked) q.start();\n setStalled(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n return s.refused;\n },\n [q, report],\n );\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(state.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\" });\n for (const i of q.getSnapshot().items) if (i.status === \"committed\") q.remove(i.id);\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // A draft commits the uploaded head of the queue as it finishes; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const head = queue.items[0];\n const headReady = head?.status === \"uploaded\";\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStalled(toContentKitError(e)));\n }, [auto, headReady, committing, stalled, commit]);\n const manualCommit = useCallback(async () => {\n setStalled(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of queue.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (queue.blocked && !seen.current.has(queue.blocked)) {\n seen.current.add(queue.blocked);\n report(queue.blocked, \"upload\");\n }\n }, [queue.items, queue.blocked, report]);\n\n // Processing failures that appear after the first read.\n const failures = useRef<Set<string> | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (!failures.current) {\n failures.current = new Set(failed.map((f) => `${f.path}:${f.failed!.of}`));\n return;\n }\n for (const f of failed) {\n const key = `${f.path}:${f.failed!.of}`;\n if (failures.current.has(key)) continue;\n failures.current.add(key);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => state.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n read,\n groups,\n uploads,\n queue,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: state.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? `${ref.kind}/${ref.id}` : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The UploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, ready, loadError });\n live.current = { groups, ref: target, listed, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n t = [...g.files.map((f) => nameIn(g, f.path)), ...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path))];\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\" });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\";\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/polls.js
CHANGED
|
@@ -15,7 +15,7 @@ function c(e, t) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
function l(n, i = {}) {
|
|
18
|
-
let { client: l, store: u,
|
|
18
|
+
let { client: l, store: u, scope: d } = r(i.client), f = n ? t("poll", d, n) : t("poll", d, "latest", i.language ?? ""), p = a(u, f, {
|
|
19
19
|
type: "poll",
|
|
20
20
|
id: n ?? void 0
|
|
21
21
|
}, async (e) => n ? l.polls.get(n, e) : (await l.polls.list({
|
|
@@ -59,7 +59,7 @@ function l(n, i = {}) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
function u(e = {}, a = {}) {
|
|
62
|
-
let { client: o, store: s,
|
|
62
|
+
let { client: o, store: s, scope: c } = r(a.client), l = e.pageSize ?? 20, u = {
|
|
63
63
|
language: e.language,
|
|
64
64
|
month: e.month,
|
|
65
65
|
date: e.date
|
|
@@ -73,7 +73,7 @@ function u(e = {}, a = {}) {
|
|
|
73
73
|
}, n)));
|
|
74
74
|
}
|
|
75
75
|
function d(n, i = {}) {
|
|
76
|
-
let { client: c, store: l,
|
|
76
|
+
let { client: c, store: l, scope: u } = r(i.client), [d, f] = s(null), p = n ?? d, m = p ? t("poll", u, p) : null, h = a(l, m, {
|
|
77
77
|
type: "poll",
|
|
78
78
|
id: p ?? void 0
|
|
79
79
|
}, (e) => c.polls.get(p, e)), g = h.data ?? null, [_, v] = s(0), y = o(async (e) => {
|
package/dist/react/polls.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"polls.js","names":[],"sources":["../../src/react/polls.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Poll, PollInput, PollOption, PollUpdate } from \"../client/generated/wire.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** The poll after the caller votes for option; a vote is final. */\nexport function withVote(p: Poll, option: string): Poll {\n if (p.voted) return p;\n return { ...p, voted: true, my_option: option, total_votes: p.total_votes + 1, options: p.options.map((o) => (o.id === option ? { ...o, vote_count: o.vote_count + 1 } : o)) };\n}\n\nexport interface UsePoll {\n /** null: no such poll (or no live one). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** A vote or answer is in flight. */\n pending: boolean;\n /** Votes at once (a vote is final); rolled back if the server refuses it. */\n vote: (option: string) => Promise<void>;\n /** Stores or replaces the signed-in caller's free-text answer. */\n answer: (text: string) => Promise<void>;\n}\n\n/**\n * A poll with the caller's vote or answer. Without an id it is the newest\n * live poll (in language): the one a home page shows.\n */\nexport function usePoll(id?: string | null, o: { initial?: Poll; language?: string; client?: ContentKitClient } = {}): UsePoll {\n const { client, store, viewer } = useContentScope(o.client);\n const key = id ? keyOf(\"poll\", viewer, id) : keyOf(\"poll\", viewer, \"latest\", o.language ?? \"\");\n const r = useResource<Poll | null>(\n store,\n key,\n { type: \"poll\", id: id ?? undefined },\n async (s) => (id ? client.polls.get(id, s) : ((await client.polls.list({ language: o.language, limit: 1 }, s))[0] ?? null)),\n { initial: o.initial },\n );\n const [pending, setPending] = useState(false);\n const poll = r.data ?? null;\n const vote = useCallback(\n async (option: string) => {\n const prev = store.snapshot<Poll | null>(key).data ?? poll;\n if (!prev) return;\n const token = store.mark(key);\n store.set(key, withVote(prev, option));\n setPending(true);\n try {\n await client.polls.vote(prev.id, option);\n } catch (e) {\n store.rollback(key, token, prev);\n throw toContentKitError(e);\n } finally {\n setPending(false);\n }\n },\n [client, store, key, poll],\n );\n const answer = useCallback(\n async (text: string) => {\n if (!poll) return;\n setPending(true);\n try {\n await client.polls.answer(poll.id, text);\n } finally {\n setPending(false);\n }\n },\n [client, poll],\n );\n return { poll, loading: r.loading || (!r.loaded && o.initial === undefined), error: r.error, reload: r.reload, pending, vote, answer };\n}\n\nexport interface PollFilter {\n language?: string;\n /** YYYY-MM */\n month?: string;\n /** YYYY-MM-DD */\n date?: string;\n /** Staff: every poll, scheduled and inactive ones included (PollWrite). */\n admin?: boolean;\n pageSize?: number;\n}\n\n/** Polls, newest first, a page at a time. */\nexport function usePolls(f: PollFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Poll> {\n const { client, store, viewer } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n const q = { language: f.language, month: f.month, date: f.date };\n return useList(\n store,\n keyOf(\"polls\", viewer, !!f.admin, q.language, q.month, q.date, size),\n { type: \"polls\" },\n offsetPages(size, (page, signal) => (f.admin ? client.polls.adminList({ ...q, ...page }, signal) : client.polls.list({ ...q, ...page }, signal))),\n );\n}\n\n/** Images for a new poll, uploaded once it exists: the question's, and each option's by index. */\nexport interface PollImages {\n question?: Blob | null;\n options?: readonly (Blob | null | undefined)[];\n}\n\nexport interface UsePollEditor {\n /** null before create (or while loading). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the poll, then uploads its images; the editor then edits it. Image failures are returned, not thrown. */\n create: (input: PollInput, images?: PollImages) => Promise<{ poll: Poll; imageErrors: ContentKitError[] }>;\n update: (patch: PollUpdate) => Promise<Poll>;\n remove: () => Promise<void>;\n addOption: (label: string, image?: Blob | null) => Promise<PollOption>;\n renameOption: (option: string, label: string) => Promise<void>;\n /** Removes an option and its votes; a poll keeps at least two. */\n removeOption: (option: string) => Promise<void>;\n /** Moves an option to index to, at once; refetched if a write fails. */\n moveOption: (option: string, to: number) => Promise<void>;\n setImage: (file: Blob | null) => Promise<void>;\n setOptionImage: (option: string, file: Blob | null) => Promise<void>;\n}\n\n/** Staff: one poll's editor (PollWrite); null creates a new poll. */\nexport function usePollEditor(id: string | null | undefined, o: { client?: ContentKitClient } = {}): UsePollEditor {\n const { client, store, viewer } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"poll\", viewer, pid) : null;\n const r = useResource<Poll | null>(store, key, { type: \"poll\", id: pid ?? undefined }, (s) => client.polls.get(pid!, s));\n const poll = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await fn();\n } finally {\n setInflight((n) => n - 1);\n }\n }, []);\n const need = useCallback(() => {\n if (!pid) throw new Error(\"contentkit-ui: create the poll first\");\n return pid;\n }, [pid]);\n\n const create = useCallback<UsePollEditor[\"create\"]>(\n (input, images = {}) =>\n run(async () => {\n const made = await client.polls.create(input);\n const imageErrors: ContentKitError[] = [];\n const tries: Promise<unknown>[] = [];\n if (images.question) tries.push(client.polls.uploadImage(made.id, images.question));\n const byPosition = [...made.options].sort((a, b) => a.position - b.position);\n (input.options ?? []).forEach((opt, i) => {\n const file = images.options?.[i];\n const target = byPosition.find((x) => x.position === opt.position) ?? byPosition[i];\n if (file && target) tries.push(client.polls.uploadOptionImage(made.id, target.id, file));\n });\n for (const res of await Promise.allSettled(tries)) if (res.status === \"rejected\") imageErrors.push(toContentKitError(res.reason));\n setCreated(made.id);\n return { poll: tries.length ? await client.polls.get(made.id) : made, imageErrors };\n }),\n [client, run],\n );\n const update = useCallback((patch: PollUpdate) => run(() => client.polls.update(need(), patch)), [client, run, need]);\n const remove = useCallback(() => run(() => client.polls.delete(need())), [client, run, need]);\n const addOption = useCallback(\n (label: string, image?: Blob | null) =>\n run(async () => {\n const opt = await client.polls.addOption(need(), { label });\n if (image) opt.image_url = (await client.polls.uploadOptionImage(need(), opt.id, image)) ?? undefined;\n return opt;\n }),\n [client, run, need],\n );\n const renameOption = useCallback((option: string, label: string) => run(async () => void (await client.polls.updateOption(need(), option, { label }))), [client, run, need]);\n const removeOption = useCallback((option: string) => run(() => client.polls.deleteOption(need(), option)), [client, run, need]);\n const moveOption = useCallback(\n (option: string, to: number) =>\n run(async () => {\n const current = store.snapshot<Poll | null>(key!).data;\n if (!current) return;\n const order = [...current.options].sort((a, b) => a.position - b.position);\n const from = order.findIndex((x) => x.id === option);\n if (from < 0 || to < 0 || to >= order.length || from === to) return;\n order.splice(to, 0, ...order.splice(from, 1));\n store.set<Poll | null>(key!, { ...current, options: order.map((x, position) => ({ ...x, position })) });\n try {\n await client.polls.reorderOptions(current.id, order);\n } catch (e) {\n store.load(key!, true);\n throw e;\n }\n }),\n [client, store, key, run],\n );\n const setImage = useCallback((file: Blob | null) => run(async () => void (await client.polls.uploadImage(need(), file))), [client, run, need]);\n const setOptionImage = useCallback(\n (option: string, file: Blob | null) => run(async () => void (await client.polls.uploadOptionImage(need(), option, file))),\n [client, run, need],\n );\n return {\n poll,\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n addOption,\n renameOption,\n removeOption,\n moveOption,\n setImage,\n setOptionImage,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"polls.js","names":[],"sources":["../../src/react/polls.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Poll, PollInput, PollOption, PollUpdate } from \"../client/generated/wire.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** The poll after the caller votes for option; a vote is final. */\nexport function withVote(p: Poll, option: string): Poll {\n if (p.voted) return p;\n return { ...p, voted: true, my_option: option, total_votes: p.total_votes + 1, options: p.options.map((o) => (o.id === option ? { ...o, vote_count: o.vote_count + 1 } : o)) };\n}\n\nexport interface UsePoll {\n /** null: no such poll (or no live one). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** A vote or answer is in flight. */\n pending: boolean;\n /** Votes at once (a vote is final); rolled back if the server refuses it. */\n vote: (option: string) => Promise<void>;\n /** Stores or replaces the signed-in caller's free-text answer. */\n answer: (text: string) => Promise<void>;\n}\n\n/**\n * A poll with the caller's vote or answer. Without an id it is the newest\n * live poll (in language): the one a home page shows.\n */\nexport function usePoll(id?: string | null, o: { initial?: Poll; language?: string; client?: ContentKitClient } = {}): UsePoll {\n const { client, store, scope } = useContentScope(o.client);\n const key = id ? keyOf(\"poll\", scope, id) : keyOf(\"poll\", scope, \"latest\", o.language ?? \"\");\n const r = useResource<Poll | null>(\n store,\n key,\n { type: \"poll\", id: id ?? undefined },\n async (s) => (id ? client.polls.get(id, s) : ((await client.polls.list({ language: o.language, limit: 1 }, s))[0] ?? null)),\n { initial: o.initial },\n );\n const [pending, setPending] = useState(false);\n const poll = r.data ?? null;\n const vote = useCallback(\n async (option: string) => {\n const prev = store.snapshot<Poll | null>(key).data ?? poll;\n if (!prev) return;\n const token = store.mark(key);\n store.set(key, withVote(prev, option));\n setPending(true);\n try {\n await client.polls.vote(prev.id, option);\n } catch (e) {\n store.rollback(key, token, prev);\n throw toContentKitError(e);\n } finally {\n setPending(false);\n }\n },\n [client, store, key, poll],\n );\n const answer = useCallback(\n async (text: string) => {\n if (!poll) return;\n setPending(true);\n try {\n await client.polls.answer(poll.id, text);\n } finally {\n setPending(false);\n }\n },\n [client, poll],\n );\n return { poll, loading: r.loading || (!r.loaded && o.initial === undefined), error: r.error, reload: r.reload, pending, vote, answer };\n}\n\nexport interface PollFilter {\n language?: string;\n /** YYYY-MM */\n month?: string;\n /** YYYY-MM-DD */\n date?: string;\n /** Staff: every poll, scheduled and inactive ones included (PollWrite). */\n admin?: boolean;\n pageSize?: number;\n}\n\n/** Polls, newest first, a page at a time. */\nexport function usePolls(f: PollFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Poll> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n const q = { language: f.language, month: f.month, date: f.date };\n return useList(\n store,\n keyOf(\"polls\", scope, !!f.admin, q.language, q.month, q.date, size),\n { type: \"polls\" },\n offsetPages(size, (page, signal) => (f.admin ? client.polls.adminList({ ...q, ...page }, signal) : client.polls.list({ ...q, ...page }, signal))),\n );\n}\n\n/** Images for a new poll, uploaded once it exists: the question's, and each option's by index. */\nexport interface PollImages {\n question?: Blob | null;\n options?: readonly (Blob | null | undefined)[];\n}\n\nexport interface UsePollEditor {\n /** null before create (or while loading). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the poll, then uploads its images; the editor then edits it. Image failures are returned, not thrown. */\n create: (input: PollInput, images?: PollImages) => Promise<{ poll: Poll; imageErrors: ContentKitError[] }>;\n update: (patch: PollUpdate) => Promise<Poll>;\n remove: () => Promise<void>;\n addOption: (label: string, image?: Blob | null) => Promise<PollOption>;\n renameOption: (option: string, label: string) => Promise<void>;\n /** Removes an option and its votes; a poll keeps at least two. */\n removeOption: (option: string) => Promise<void>;\n /** Moves an option to index to, at once; refetched if a write fails. */\n moveOption: (option: string, to: number) => Promise<void>;\n setImage: (file: Blob | null) => Promise<void>;\n setOptionImage: (option: string, file: Blob | null) => Promise<void>;\n}\n\n/** Staff: one poll's editor (PollWrite); null creates a new poll. */\nexport function usePollEditor(id: string | null | undefined, o: { client?: ContentKitClient } = {}): UsePollEditor {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"poll\", scope, pid) : null;\n const r = useResource<Poll | null>(store, key, { type: \"poll\", id: pid ?? undefined }, (s) => client.polls.get(pid!, s));\n const poll = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await fn();\n } finally {\n setInflight((n) => n - 1);\n }\n }, []);\n const need = useCallback(() => {\n if (!pid) throw new Error(\"contentkit-ui: create the poll first\");\n return pid;\n }, [pid]);\n\n const create = useCallback<UsePollEditor[\"create\"]>(\n (input, images = {}) =>\n run(async () => {\n const made = await client.polls.create(input);\n const imageErrors: ContentKitError[] = [];\n const tries: Promise<unknown>[] = [];\n if (images.question) tries.push(client.polls.uploadImage(made.id, images.question));\n const byPosition = [...made.options].sort((a, b) => a.position - b.position);\n (input.options ?? []).forEach((opt, i) => {\n const file = images.options?.[i];\n const target = byPosition.find((x) => x.position === opt.position) ?? byPosition[i];\n if (file && target) tries.push(client.polls.uploadOptionImage(made.id, target.id, file));\n });\n for (const res of await Promise.allSettled(tries)) if (res.status === \"rejected\") imageErrors.push(toContentKitError(res.reason));\n setCreated(made.id);\n return { poll: tries.length ? await client.polls.get(made.id) : made, imageErrors };\n }),\n [client, run],\n );\n const update = useCallback((patch: PollUpdate) => run(() => client.polls.update(need(), patch)), [client, run, need]);\n const remove = useCallback(() => run(() => client.polls.delete(need())), [client, run, need]);\n const addOption = useCallback(\n (label: string, image?: Blob | null) =>\n run(async () => {\n const opt = await client.polls.addOption(need(), { label });\n if (image) opt.image_url = (await client.polls.uploadOptionImage(need(), opt.id, image)) ?? undefined;\n return opt;\n }),\n [client, run, need],\n );\n const renameOption = useCallback((option: string, label: string) => run(async () => void (await client.polls.updateOption(need(), option, { label }))), [client, run, need]);\n const removeOption = useCallback((option: string) => run(() => client.polls.deleteOption(need(), option)), [client, run, need]);\n const moveOption = useCallback(\n (option: string, to: number) =>\n run(async () => {\n const current = store.snapshot<Poll | null>(key!).data;\n if (!current) return;\n const order = [...current.options].sort((a, b) => a.position - b.position);\n const from = order.findIndex((x) => x.id === option);\n if (from < 0 || to < 0 || to >= order.length || from === to) return;\n order.splice(to, 0, ...order.splice(from, 1));\n store.set<Poll | null>(key!, { ...current, options: order.map((x, position) => ({ ...x, position })) });\n try {\n await client.polls.reorderOptions(current.id, order);\n } catch (e) {\n store.load(key!, true);\n throw e;\n }\n }),\n [client, store, key, run],\n );\n const setImage = useCallback((file: Blob | null) => run(async () => void (await client.polls.uploadImage(need(), file))), [client, run, need]);\n const setOptionImage = useCallback(\n (option: string, file: Blob | null) => run(async () => void (await client.polls.uploadOptionImage(need(), option, file))),\n [client, run, need],\n );\n return {\n poll,\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n addOption,\n renameOption,\n removeOption,\n moveOption,\n setImage,\n setOptionImage,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/posts.js
CHANGED
|
@@ -3,7 +3,7 @@ import { keyOf as t, offsetPages as n, useContentScope as r, useList as i, useRe
|
|
|
3
3
|
import { useCallback as o, useEffect as s, useMemo as c, useRef as l, useState as u } from "react";
|
|
4
4
|
//#region src/react/posts.ts
|
|
5
5
|
function d(e = {}, a = {}) {
|
|
6
|
-
let { client: o, store: s,
|
|
6
|
+
let { client: o, store: s, scope: c } = r(a.client), l = e.pageSize ?? 20;
|
|
7
7
|
return i(s, t("posts", c, !!e.admin, e.language, e.sort, e.draft, e.deleted, e.q, l), {
|
|
8
8
|
type: "posts",
|
|
9
9
|
scope: e.admin && e.deleted ? "deleted" : void 0
|
|
@@ -24,7 +24,7 @@ function h(e, t = Date.now()) {
|
|
|
24
24
|
return !e.is_draft && !e.moderation && (!e.live_at || Date.parse(e.live_at) <= t);
|
|
25
25
|
}
|
|
26
26
|
function g(n, i = {}) {
|
|
27
|
-
let { client: d, store: f,
|
|
27
|
+
let { client: d, store: f, scope: g } = r(i.client), [_, v] = u(null), y = n ?? _, b = y ? t("post", g, y) : null, x = a(f, b, {
|
|
28
28
|
type: "post",
|
|
29
29
|
id: y ?? void 0
|
|
30
30
|
}, (e) => d.posts.get(y, e), { initial: i.initial }), S = x.data ?? null, [C, w] = u(0), T = o(async (e) => {
|