@voltro/plugin-comments 0.69.1 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1369 -111
- package/dist/index.js +221 -116
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
|
|
2
2
|
import { BASE_NAME as r, commentsFeed as i, commentsRpcClientImports as a, createDescriptor as o, deleteDescriptor as s, editDescriptor as c, listDescriptor as l, markReadDescriptor as u, mentionSearchDescriptor as d, reactDescriptor as f, resolveDescriptor as p } from "./rpc.js";
|
|
3
3
|
import { Effect as m, Option as h } from "effect";
|
|
4
|
-
import { and as g,
|
|
5
|
-
import { definePlugin as
|
|
6
|
-
import { NotificationService as
|
|
4
|
+
import { and as g, contains as _, eq as v, generateId as y, id as b, inSet as x, json as S, neq as C, or as w, table as T, text as E, timestamp as D } from "@voltro/database";
|
|
5
|
+
import { definePlugin as O, pluginInstanceName as k, publishReactivity as A } from "@voltro/protocol";
|
|
6
|
+
import { NotificationService as j } from "@voltro/plugin-notifications";
|
|
7
7
|
//#region src/index.ts
|
|
8
|
-
var
|
|
9
|
-
id:
|
|
10
|
-
anchor:
|
|
11
|
-
status:
|
|
12
|
-
createdBy:
|
|
13
|
-
createdAt:
|
|
14
|
-
tenantId:
|
|
15
|
-
}).index("byThreadAnchor", ["anchor", "tenantId"]),
|
|
16
|
-
id:
|
|
17
|
-
threadId:
|
|
18
|
-
body:
|
|
19
|
-
authorSubjectId:
|
|
20
|
-
mentions:
|
|
21
|
-
editedAt:
|
|
22
|
-
createdAt:
|
|
23
|
-
tenantId:
|
|
24
|
-
}).index("byCommentThread", ["threadId"]),
|
|
25
|
-
id:
|
|
26
|
-
commentId:
|
|
27
|
-
subjectId:
|
|
28
|
-
emoji:
|
|
8
|
+
var M = T("_voltro_comment_threads", {
|
|
9
|
+
id: b({ prefix: "cmtthr" }),
|
|
10
|
+
anchor: E(),
|
|
11
|
+
status: E().default("open"),
|
|
12
|
+
createdBy: E(),
|
|
13
|
+
createdAt: D().default("now"),
|
|
14
|
+
tenantId: E().nullable()
|
|
15
|
+
}).index("byThreadAnchor", ["anchor", "tenantId"]), N = T("_voltro_comments", {
|
|
16
|
+
id: b({ prefix: "cmt" }),
|
|
17
|
+
threadId: E(),
|
|
18
|
+
body: E(),
|
|
19
|
+
authorSubjectId: E(),
|
|
20
|
+
mentions: S(),
|
|
21
|
+
editedAt: D().nullable(),
|
|
22
|
+
createdAt: D().default("now"),
|
|
23
|
+
tenantId: E().nullable()
|
|
24
|
+
}).index("byCommentThread", ["threadId"]), P = T("_voltro_comment_reactions", {
|
|
25
|
+
id: b({ prefix: "cmtrx" }),
|
|
26
|
+
commentId: E(),
|
|
27
|
+
subjectId: E(),
|
|
28
|
+
emoji: E()
|
|
29
29
|
}).unique("byReactionIdentity", [
|
|
30
30
|
"commentId",
|
|
31
31
|
"subjectId",
|
|
32
32
|
"emoji"
|
|
33
|
-
]).index("byReactionComment", ["commentId"]),
|
|
34
|
-
id:
|
|
35
|
-
threadId:
|
|
36
|
-
subjectId:
|
|
37
|
-
lastReadAt:
|
|
38
|
-
}).unique("byReadThreadSubject", ["threadId", "subjectId"]),
|
|
33
|
+
]).index("byReactionComment", ["commentId"]), F = T("_voltro_comment_reads", {
|
|
34
|
+
id: b({ prefix: "cmtread" }),
|
|
35
|
+
threadId: E(),
|
|
36
|
+
subjectId: E(),
|
|
37
|
+
lastReadAt: D()
|
|
38
|
+
}).unique("byReadThreadSubject", ["threadId", "subjectId"]), I = r, L = (e) => {
|
|
39
39
|
let t = e.request.subject ?? {};
|
|
40
40
|
return {
|
|
41
41
|
id: t.id ?? null,
|
|
42
42
|
tenantId: t.tenantId ?? null,
|
|
43
43
|
scopes: t.scopes ?? []
|
|
44
44
|
};
|
|
45
|
-
},
|
|
46
|
-
let
|
|
47
|
-
base:
|
|
45
|
+
}, R = (r = {}) => {
|
|
46
|
+
let b = k({
|
|
47
|
+
base: I,
|
|
48
48
|
alias: r.alias,
|
|
49
49
|
instance: r.name
|
|
50
|
-
}),
|
|
50
|
+
}), S, T, E = async (e, t, n) => S === void 0 ? [] : S.query({
|
|
51
51
|
table: e,
|
|
52
52
|
predicate: t,
|
|
53
53
|
order: [],
|
|
54
54
|
take: n,
|
|
55
55
|
skip: void 0,
|
|
56
56
|
projection: void 0
|
|
57
|
-
}),
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
let i =
|
|
57
|
+
}), D = () => {
|
|
58
|
+
A(T, i);
|
|
59
|
+
}, R = (t, n) => m.gen(function* () {
|
|
60
|
+
let i = L(n);
|
|
61
61
|
return r.access?.viaEntity === void 0 ? r.access?.scope === void 0 ? yield* m.fail(new e({
|
|
62
62
|
anchor: t,
|
|
63
63
|
reason: "no access rule declared — set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }"
|
|
@@ -69,7 +69,7 @@ var O = x("_voltro_comment_threads", {
|
|
|
69
69
|
return await r.access.viaEntity({
|
|
70
70
|
anchor: t,
|
|
71
71
|
subject: i,
|
|
72
|
-
store:
|
|
72
|
+
store: S
|
|
73
73
|
});
|
|
74
74
|
} catch {
|
|
75
75
|
return !1;
|
|
@@ -78,9 +78,9 @@ var O = x("_voltro_comment_threads", {
|
|
|
78
78
|
anchor: t,
|
|
79
79
|
reason: "the anchored entity is not readable by this caller (or no longer available)"
|
|
80
80
|
}));
|
|
81
|
-
}),
|
|
81
|
+
}), z = async (e) => (await E("_voltro_comment_threads", v("id", e), 1))[0], B = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", V = (e) => m.gen(function* () {
|
|
82
82
|
if (e.mentions.length === 0) return;
|
|
83
|
-
let t = yield* m.serviceOption(
|
|
83
|
+
let t = yield* m.serviceOption(j);
|
|
84
84
|
if (h.isNone(t)) {
|
|
85
85
|
yield* m.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
|
|
86
86
|
return;
|
|
@@ -96,7 +96,7 @@ var O = x("_voltro_comment_threads", {
|
|
|
96
96
|
},
|
|
97
97
|
tenantId: e.tenantId
|
|
98
98
|
}).then(() => void 0).catch(() => void 0)));
|
|
99
|
-
}),
|
|
99
|
+
}), H = async (e, t) => {
|
|
100
100
|
if (e.length === 0 || r.resolveMentions === void 0) return [];
|
|
101
101
|
let n = [];
|
|
102
102
|
for (let i of [...new Set(e)].slice(0, 20)) {
|
|
@@ -107,14 +107,14 @@ var O = x("_voltro_comment_threads", {
|
|
|
107
107
|
e !== void 0 && (r.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === t.tenantId) && n.push(i);
|
|
108
108
|
}
|
|
109
109
|
return n;
|
|
110
|
-
},
|
|
111
|
-
let r = await
|
|
110
|
+
}, U = async (e, t, n) => {
|
|
111
|
+
let r = await E("_voltro_comment_threads", g(v("anchor", e), v("tenantId", t))), i = [];
|
|
112
112
|
for (let e of r) {
|
|
113
|
-
let t = [...await
|
|
114
|
-
t.sort((e, t) =>
|
|
115
|
-
let r = n === null ? [] : await
|
|
113
|
+
let t = [...await E("_voltro_comments", v("threadId", String(e.id)))];
|
|
114
|
+
t.sort((e, t) => B(e.createdAt) < B(t.createdAt) ? -1 : 1);
|
|
115
|
+
let r = n === null ? [] : await E("_voltro_comment_reads", g(v("threadId", String(e.id)), v("subjectId", n)), 1), a = r[0] ? B(r[0].lastReadAt) : "", o = [];
|
|
116
116
|
for (let e of t) {
|
|
117
|
-
let t = await
|
|
117
|
+
let t = await E("_voltro_comment_reactions", v("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
|
|
118
118
|
for (let e of t) {
|
|
119
119
|
let t = r.get(String(e.emoji)) ?? {
|
|
120
120
|
count: 0,
|
|
@@ -128,8 +128,8 @@ var O = x("_voltro_comment_threads", {
|
|
|
128
128
|
body: String(e.body),
|
|
129
129
|
authorSubjectId: String(e.authorSubjectId),
|
|
130
130
|
mentions: e.mentions ?? [],
|
|
131
|
-
editedAt: e.editedAt == null ? null :
|
|
132
|
-
createdAt:
|
|
131
|
+
editedAt: e.editedAt == null ? null : B(e.editedAt),
|
|
132
|
+
createdAt: B(e.createdAt),
|
|
133
133
|
reactions: [...r.entries()].map(([e, t]) => ({
|
|
134
134
|
emoji: e,
|
|
135
135
|
count: t.count,
|
|
@@ -142,21 +142,21 @@ var O = x("_voltro_comment_threads", {
|
|
|
142
142
|
anchor: String(e.anchor),
|
|
143
143
|
status: e.status === "resolved" ? "resolved" : "open",
|
|
144
144
|
createdBy: String(e.createdBy),
|
|
145
|
-
createdAt:
|
|
145
|
+
createdAt: B(e.createdAt),
|
|
146
146
|
comments: o,
|
|
147
147
|
unreadCount: o.filter((e) => String(e.authorSubjectId) !== n && String(e.createdAt) > a).length
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
150
|
return i.sort((e, t) => String(e.createdAt) < String(t.createdAt) ? -1 : 1), i;
|
|
151
|
-
},
|
|
151
|
+
}, W = "comments:moderate", G = [
|
|
152
152
|
{
|
|
153
153
|
...l,
|
|
154
154
|
description: "Threads + comments + reactions + unread for one anchor, live.",
|
|
155
155
|
execute: (e, t) => m.gen(function* () {
|
|
156
156
|
let { anchor: n } = e;
|
|
157
|
-
yield*
|
|
158
|
-
let r =
|
|
159
|
-
return yield* m.promise(() =>
|
|
157
|
+
yield* R(n, t);
|
|
158
|
+
let r = L(t);
|
|
159
|
+
return yield* m.promise(() => U(n, r.tenantId, r.id));
|
|
160
160
|
})
|
|
161
161
|
},
|
|
162
162
|
{
|
|
@@ -164,14 +164,14 @@ var O = x("_voltro_comment_threads", {
|
|
|
164
164
|
description: "Comment on an anchor — a reply into a thread, or a new thread.",
|
|
165
165
|
execute: (e, n) => m.gen(function* () {
|
|
166
166
|
let r = e;
|
|
167
|
-
yield*
|
|
168
|
-
let i =
|
|
167
|
+
yield* R(r.anchor, n);
|
|
168
|
+
let i = L(n), a = i.id ?? "anonymous", o = S;
|
|
169
169
|
if (o === void 0) return yield* m.fail(new t({ id: r.anchor }));
|
|
170
170
|
let s = r.threadId;
|
|
171
171
|
if (s !== void 0) {
|
|
172
|
-
let e = yield* m.promise(() =>
|
|
172
|
+
let e = yield* m.promise(() => z(s));
|
|
173
173
|
if (e === void 0 || String(e.anchor) !== r.anchor) return yield* m.fail(new t({ id: s }));
|
|
174
|
-
} else s =
|
|
174
|
+
} else s = y({
|
|
175
175
|
kind: "typeid",
|
|
176
176
|
prefix: "cmtthr"
|
|
177
177
|
}, "_voltro_comment_threads"), yield* m.promise(() => o.insert("_voltro_comment_threads", {
|
|
@@ -182,7 +182,7 @@ var O = x("_voltro_comment_threads", {
|
|
|
182
182
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
183
183
|
tenantId: i.tenantId
|
|
184
184
|
}));
|
|
185
|
-
let c = yield* m.promise(() =>
|
|
185
|
+
let c = yield* m.promise(() => H(r.mentions ?? [], i)), l = y({
|
|
186
186
|
kind: "typeid",
|
|
187
187
|
prefix: "cmt"
|
|
188
188
|
}, "_voltro_comments");
|
|
@@ -195,7 +195,7 @@ var O = x("_voltro_comment_threads", {
|
|
|
195
195
|
editedAt: null,
|
|
196
196
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
197
197
|
tenantId: i.tenantId
|
|
198
|
-
})),
|
|
198
|
+
})), D(), yield* V({
|
|
199
199
|
mentions: c,
|
|
200
200
|
author: a,
|
|
201
201
|
anchor: r.anchor,
|
|
@@ -212,60 +212,60 @@ var O = x("_voltro_comment_threads", {
|
|
|
212
212
|
...c,
|
|
213
213
|
description: "Edit your own comment.",
|
|
214
214
|
execute: (e, r) => m.gen(function* () {
|
|
215
|
-
let i = e, a = (yield* m.promise(() =>
|
|
215
|
+
let i = e, a = (yield* m.promise(() => E("_voltro_comments", v("id", i.commentId), 1)))[0];
|
|
216
216
|
if (a === void 0) return yield* m.fail(new t({ id: i.commentId }));
|
|
217
|
-
let o = yield* m.promise(() =>
|
|
218
|
-
return o === void 0 ? yield* m.fail(new t({ id: i.commentId })) : (yield*
|
|
217
|
+
let o = yield* m.promise(() => z(String(a.threadId)));
|
|
218
|
+
return o === void 0 ? yield* m.fail(new t({ id: i.commentId })) : (yield* R(String(o.anchor), r), String(a.authorSubjectId) === (L(r).id ?? "anonymous") ? (yield* m.promise(() => S.update("_voltro_comments", i.commentId, {
|
|
219
219
|
body: i.body,
|
|
220
220
|
editedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
221
|
-
})),
|
|
221
|
+
})), D(), { ok: !0 }) : yield* m.fail(new n({ id: i.commentId })));
|
|
222
222
|
})
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
...p,
|
|
226
226
|
description: "Resolve or reopen a thread.",
|
|
227
227
|
execute: (e, n) => m.gen(function* () {
|
|
228
|
-
let r = e, i = yield* m.promise(() =>
|
|
229
|
-
return i === void 0 ? yield* m.fail(new t({ id: r.threadId })) : (yield*
|
|
228
|
+
let r = e, i = yield* m.promise(() => z(r.threadId));
|
|
229
|
+
return i === void 0 ? yield* m.fail(new t({ id: r.threadId })) : (yield* R(String(i.anchor), n), yield* m.promise(() => S.update("_voltro_comment_threads", r.threadId, { status: r.resolved ? "resolved" : "open" })), D(), { ok: !0 });
|
|
230
230
|
})
|
|
231
231
|
},
|
|
232
232
|
{
|
|
233
233
|
...s,
|
|
234
234
|
description: "Delete your own comment (comments:moderate deletes any).",
|
|
235
235
|
execute: (e, r) => m.gen(function* () {
|
|
236
|
-
let i = e, a = (yield* m.promise(() =>
|
|
236
|
+
let i = e, a = (yield* m.promise(() => E("_voltro_comments", v("id", i.commentId), 1)))[0];
|
|
237
237
|
if (a === void 0) return yield* m.fail(new t({ id: i.commentId }));
|
|
238
|
-
let o = yield* m.promise(() =>
|
|
238
|
+
let o = yield* m.promise(() => z(String(a.threadId)));
|
|
239
239
|
if (o === void 0) return yield* m.fail(new t({ id: i.commentId }));
|
|
240
|
-
yield*
|
|
241
|
-
let s =
|
|
242
|
-
return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes(
|
|
243
|
-
for (let e of await
|
|
244
|
-
await
|
|
245
|
-
}),
|
|
240
|
+
yield* R(String(o.anchor), r);
|
|
241
|
+
let s = L(r);
|
|
242
|
+
return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes(W) ? yield* m.fail(new n({ id: i.commentId })) : (yield* m.promise(async () => {
|
|
243
|
+
for (let e of await E("_voltro_comment_reactions", v("commentId", i.commentId))) await S.delete("_voltro_comment_reactions", String(e.id));
|
|
244
|
+
await S.delete("_voltro_comments", i.commentId);
|
|
245
|
+
}), D(), { ok: !0 });
|
|
246
246
|
})
|
|
247
247
|
},
|
|
248
248
|
{
|
|
249
249
|
...f,
|
|
250
250
|
description: "Toggle an emoji reaction on a comment.",
|
|
251
251
|
execute: (e, n) => m.gen(function* () {
|
|
252
|
-
let r = e, i = (yield* m.promise(() =>
|
|
252
|
+
let r = e, i = (yield* m.promise(() => E("_voltro_comments", v("id", r.commentId), 1)))[0];
|
|
253
253
|
if (i === void 0) return yield* m.fail(new t({ id: r.commentId }));
|
|
254
|
-
let a = yield* m.promise(() =>
|
|
254
|
+
let a = yield* m.promise(() => z(String(i.threadId)));
|
|
255
255
|
if (a === void 0) return yield* m.fail(new t({ id: r.commentId }));
|
|
256
|
-
yield*
|
|
257
|
-
let o =
|
|
258
|
-
if (s !== void 0) return yield* m.promise(() =>
|
|
259
|
-
let c =
|
|
256
|
+
yield* R(String(a.anchor), n);
|
|
257
|
+
let o = L(n).id ?? "anonymous", s = (yield* m.promise(() => E("_voltro_comment_reactions", g(v("commentId", r.commentId), g(v("subjectId", o), v("emoji", r.emoji))), 1)))[0];
|
|
258
|
+
if (s !== void 0) return yield* m.promise(() => S.delete("_voltro_comment_reactions", String(s.id))), D(), { reacted: !1 };
|
|
259
|
+
let c = y({
|
|
260
260
|
kind: "typeid",
|
|
261
261
|
prefix: "cmtrx"
|
|
262
262
|
}, "_voltro_comment_reactions");
|
|
263
|
-
return yield* m.promise(() =>
|
|
263
|
+
return yield* m.promise(() => S.insert("_voltro_comment_reactions", {
|
|
264
264
|
id: c,
|
|
265
265
|
commentId: r.commentId,
|
|
266
266
|
subjectId: o,
|
|
267
267
|
emoji: r.emoji
|
|
268
|
-
})),
|
|
268
|
+
})), D(), { reacted: !0 };
|
|
269
269
|
})
|
|
270
270
|
},
|
|
271
271
|
{
|
|
@@ -273,12 +273,12 @@ var O = x("_voltro_comment_threads", {
|
|
|
273
273
|
description: "Stamp the caller's read marker for a thread.",
|
|
274
274
|
execute: (e, n) => m.gen(function* () {
|
|
275
275
|
let r = e;
|
|
276
|
-
if ((yield* m.promise(() =>
|
|
277
|
-
let i =
|
|
276
|
+
if ((yield* m.promise(() => z(r.threadId))) === void 0) return yield* m.fail(new t({ id: r.threadId }));
|
|
277
|
+
let i = L(n).id ?? "anonymous", a = y({
|
|
278
278
|
kind: "typeid",
|
|
279
279
|
prefix: "cmtread"
|
|
280
280
|
}, "_voltro_comment_reads");
|
|
281
|
-
return yield* m.promise(() =>
|
|
281
|
+
return yield* m.promise(() => S.upsert("_voltro_comment_reads", {
|
|
282
282
|
id: a,
|
|
283
283
|
threadId: r.threadId,
|
|
284
284
|
subjectId: i,
|
|
@@ -295,7 +295,7 @@ var O = x("_voltro_comment_threads", {
|
|
|
295
295
|
execute: (e, t) => m.promise(async () => {
|
|
296
296
|
let { query: n } = e;
|
|
297
297
|
if (r.resolveMentions === void 0) return [];
|
|
298
|
-
let i =
|
|
298
|
+
let i = L(t);
|
|
299
299
|
return (await r.resolveMentions({
|
|
300
300
|
query: n,
|
|
301
301
|
subject: {
|
|
@@ -308,57 +308,162 @@ var O = x("_voltro_comment_threads", {
|
|
|
308
308
|
}));
|
|
309
309
|
})
|
|
310
310
|
}
|
|
311
|
-
],
|
|
311
|
+
], K = (e) => ({
|
|
312
312
|
kind: "json",
|
|
313
313
|
data: e
|
|
314
314
|
});
|
|
315
|
-
return
|
|
316
|
-
name:
|
|
317
|
-
baseName:
|
|
315
|
+
return O({
|
|
316
|
+
name: b,
|
|
317
|
+
baseName: I,
|
|
318
318
|
description: "Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread — live over the reactive engine.",
|
|
319
319
|
permissions: ["inspect:read", "store:write"],
|
|
320
|
-
declaredScopes: [
|
|
320
|
+
declaredScopes: [W],
|
|
321
321
|
extendSchema: { tables: [
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
322
|
+
M,
|
|
323
|
+
N,
|
|
324
|
+
P,
|
|
325
|
+
F
|
|
326
326
|
] },
|
|
327
|
-
routes:
|
|
327
|
+
routes: G,
|
|
328
328
|
rpcClientDescriptors: a,
|
|
329
329
|
inspectEndpoints: [{
|
|
330
330
|
method: "GET",
|
|
331
331
|
path: "/threads",
|
|
332
|
-
description: "Thread volume
|
|
333
|
-
handler: () => m.promise(async () => {
|
|
334
|
-
let
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
332
|
+
description: "Thread volume and searchable, paginated threads (operator surface).",
|
|
333
|
+
handler: (e) => m.promise(async () => {
|
|
334
|
+
let t = new URL(e.url, "http://inspect.local").searchParams, n = t.get("threadId"), r = Number(t.get("offset") ?? 0), i = Number.isSafeInteger(r) && r >= 0 ? r : 0, a = (t.get("search") ?? "").trim().slice(0, 200);
|
|
335
|
+
if (n) {
|
|
336
|
+
let e = (await E("_voltro_comment_threads", v("id", n), 1))[0], t = v("threadId", n), [r, a] = e && S ? await Promise.all([S.query({
|
|
337
|
+
table: "_voltro_comments",
|
|
338
|
+
predicate: t,
|
|
339
|
+
order: [{
|
|
340
|
+
column: "createdAt",
|
|
341
|
+
direction: "asc"
|
|
342
|
+
}, {
|
|
343
|
+
column: "id",
|
|
344
|
+
direction: "asc"
|
|
345
|
+
}],
|
|
346
|
+
take: 50,
|
|
347
|
+
skip: i,
|
|
348
|
+
projection: void 0
|
|
349
|
+
}), S.query({
|
|
350
|
+
table: "_voltro_comments",
|
|
351
|
+
predicate: t,
|
|
352
|
+
order: [],
|
|
353
|
+
take: void 0,
|
|
354
|
+
skip: void 0,
|
|
355
|
+
projection: void 0,
|
|
356
|
+
aggregations: [{
|
|
357
|
+
op: "count",
|
|
358
|
+
alias: "count"
|
|
359
|
+
}]
|
|
360
|
+
})]) : [[], []], o = Number(a[0]?.count ?? 0);
|
|
361
|
+
return K({
|
|
362
|
+
stats: {
|
|
363
|
+
threads: +!!e,
|
|
364
|
+
open: e && e.status !== "resolved" ? 1 : 0,
|
|
365
|
+
comments: o
|
|
366
|
+
},
|
|
367
|
+
recent: [],
|
|
368
|
+
detail: {
|
|
369
|
+
threadId: n,
|
|
370
|
+
found: !!e,
|
|
371
|
+
offset: i,
|
|
372
|
+
total: o,
|
|
373
|
+
messages: r.map((e) => ({
|
|
374
|
+
id: String(e.id),
|
|
375
|
+
body: String(e.body),
|
|
376
|
+
author: String(e.authorSubjectId),
|
|
377
|
+
createdAt: B(e.createdAt),
|
|
378
|
+
editedAt: e.editedAt == null ? null : B(e.editedAt)
|
|
379
|
+
}))
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (!S) throw Error("Comments store is not available");
|
|
384
|
+
let o = async (e, t) => {
|
|
385
|
+
let n = await S.query({
|
|
386
|
+
table: e,
|
|
387
|
+
predicate: t,
|
|
388
|
+
order: [],
|
|
389
|
+
take: void 0,
|
|
390
|
+
skip: void 0,
|
|
391
|
+
projection: void 0,
|
|
392
|
+
aggregations: [{
|
|
393
|
+
op: "count",
|
|
394
|
+
alias: "count"
|
|
395
|
+
}]
|
|
396
|
+
});
|
|
397
|
+
return Number(n[0]?.count ?? 0);
|
|
398
|
+
}, s = a ? w(_("anchor", a), _("id", a), _("createdBy", a)) : void 0, [c, l, u, d, f] = await Promise.all([
|
|
399
|
+
S.query({
|
|
400
|
+
table: "_voltro_comment_threads",
|
|
401
|
+
predicate: s,
|
|
402
|
+
order: [{
|
|
403
|
+
column: "createdAt",
|
|
404
|
+
direction: "desc"
|
|
405
|
+
}, {
|
|
406
|
+
column: "id",
|
|
407
|
+
direction: "desc"
|
|
408
|
+
}],
|
|
409
|
+
take: 50,
|
|
410
|
+
skip: i,
|
|
411
|
+
projection: void 0
|
|
412
|
+
}),
|
|
413
|
+
o("_voltro_comment_threads"),
|
|
414
|
+
o("_voltro_comment_threads", C("status", "resolved")),
|
|
415
|
+
o("_voltro_comments"),
|
|
416
|
+
o("_voltro_comment_threads", s)
|
|
417
|
+
]), p = c.length === 0 ? [] : await S.query({
|
|
418
|
+
table: "_voltro_comments",
|
|
419
|
+
predicate: x("threadId", c.map((e) => String(e.id))),
|
|
420
|
+
order: [],
|
|
421
|
+
take: void 0,
|
|
422
|
+
skip: void 0,
|
|
423
|
+
projection: void 0,
|
|
424
|
+
aggregations: [{
|
|
425
|
+
op: "column",
|
|
426
|
+
column: "threadId",
|
|
427
|
+
alias: "threadId"
|
|
428
|
+
}, {
|
|
429
|
+
op: "count",
|
|
430
|
+
alias: "count"
|
|
431
|
+
}],
|
|
432
|
+
groupBy: ["threadId"]
|
|
433
|
+
}), m = new Map(p.map((e) => [String(e.threadId), Number(e.count)]));
|
|
434
|
+
return K({
|
|
343
435
|
stats: {
|
|
344
|
-
threads:
|
|
345
|
-
open:
|
|
346
|
-
comments:
|
|
436
|
+
threads: l,
|
|
437
|
+
open: u,
|
|
438
|
+
comments: d
|
|
347
439
|
},
|
|
348
|
-
|
|
440
|
+
page: {
|
|
441
|
+
offset: i,
|
|
442
|
+
search: a,
|
|
443
|
+
total: f,
|
|
444
|
+
pageSize: 50
|
|
445
|
+
},
|
|
446
|
+
recent: c.map((e) => ({
|
|
447
|
+
id: String(e.id),
|
|
448
|
+
anchor: String(e.anchor),
|
|
449
|
+
status: String(e.status),
|
|
450
|
+
createdBy: String(e.createdBy),
|
|
451
|
+
createdAt: B(e.createdAt),
|
|
452
|
+
comments: m.get(String(e.id)) ?? 0
|
|
453
|
+
}))
|
|
349
454
|
});
|
|
350
455
|
})
|
|
351
456
|
}],
|
|
352
457
|
bindDataStore: (e) => {
|
|
353
|
-
|
|
458
|
+
S = e, T = e;
|
|
354
459
|
},
|
|
355
460
|
onActivate: (e) => m.sync(() => {
|
|
356
461
|
r.access?.viaEntity === void 0 && r.access?.scope === void 0 && e.logger.warn("comments: no access rule declared — EVERY read and write will be refused (fail-closed). Set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }."), e.logger.info("comments active", { mentions: r.resolveMentions !== void 0 });
|
|
357
462
|
}),
|
|
358
463
|
onDeactivate: () => m.sync(() => {
|
|
359
|
-
|
|
464
|
+
S = void 0, T = void 0;
|
|
360
465
|
})
|
|
361
466
|
});
|
|
362
|
-
},
|
|
467
|
+
}, z = () => "plugin-comments";
|
|
363
468
|
//#endregion
|
|
364
|
-
export { e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours,
|
|
469
|
+
export { e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours, P as commentReactionsTable, F as commentReadsTable, M as commentThreadsTable, i as commentsFeed, R as commentsPlugin, N as commentsTable, z as pluginComments };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-comments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Comment threads on any anchor your app can name — replies, resolve/reopen, reactions, per-subject unread — live over the existing reactivity channel, with no second push mechanism. Access delegates to your app's own guard (access.viaEntity) or a scope and fails closed when neither is declared; @-mentions are re-filtered to the caller's tenant on search and re-validated at create, then delivered through plugin-notifications when it is installed. useComments / useThread / useMentionSearch in /web, an ejectable <CommentsThread> in @voltro/ui, plus inspect endpoints and a dashboard panel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"node": ">=24.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@voltro/database": "0.
|
|
52
|
-
"@voltro/plugin-notifications": "0.
|
|
53
|
-
"@voltro/protocol": "0.
|
|
51
|
+
"@voltro/database": "0.71.0",
|
|
52
|
+
"@voltro/plugin-notifications": "0.71.0",
|
|
53
|
+
"@voltro/protocol": "0.71.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"effect": "^3.22.1",
|