@rocapine/community-core 0.8.1 → 0.10.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/README.md +2 -1
- package/lib/hooks.d.ts +17 -10
- package/lib/hooks.js +47 -14
- package/lib/index.d.ts +3 -3
- package/lib/index.js +5 -1
- package/lib/models.d.ts +1 -1
- package/lib/models.js +7 -3
- package/lib/service.d.ts +9 -3
- package/lib/service.js +48 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -175,7 +175,8 @@ runs in production and is silent in degraded mode.
|
|
|
175
175
|
(pure helper behind `feed.extraPostColumns`, see below).
|
|
176
176
|
- **React Query hooks** (`hooks.ts`): `useCommunityFeed`, `useSearchPosts`,
|
|
177
177
|
`useNewPostsCount`, `useCommunityUnseenCount`, `useThread`, `useProfile`,
|
|
178
|
-
`useUserPosts`, `useMyUid`, `
|
|
178
|
+
`useUserPosts`, `useMyUid`, `useNotificationPreferences` (push module: the
|
|
179
|
+
caller's `notify_*` flags), `useUpdateProfile`, `useCreatePost`,
|
|
179
180
|
`useCreateComment`, `useToggleLike`, `useVotePoll`, `useReactToPost`,
|
|
180
181
|
`useReport`, `useBlockUser`, `useDeleteContent`.
|
|
181
182
|
- **Inbox** (opt-in via `modules.inbox`): `fetchInbox`, `markInboxSeen`,
|
package/lib/hooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type InfiniteData, type QueryClient } from "@tanstack/react-query";
|
|
2
2
|
import { type CommunityProfile, type FeedPost, type ThreadComment } from "./models";
|
|
3
|
-
import { type ModerationVerdict, type ReportInput, type UpdateProfileInput, type UpdateProfileResult } from "./service";
|
|
3
|
+
import { type NotificationPreferenceKey, type NotificationPreferences, type ModerationVerdict, type ReportInput, type UpdateProfileInput, type UpdateProfileResult } from "./service";
|
|
4
4
|
/** True while a post/comment is still the optimistic placeholder inserted by
|
|
5
5
|
* `useCreatePost`/`useCreateComment` (its temp id is swapped for the real one
|
|
6
6
|
* once moderation answers). Server actions keyed on the id (reactions, likes,
|
|
@@ -54,14 +54,23 @@ export declare function useNewPostsCount(sinceIso: string | null, topic?: string
|
|
|
54
54
|
* package has no store access at all, so the host now owns that value and
|
|
55
55
|
* passes it in).
|
|
56
56
|
*/
|
|
57
|
-
export declare function useCommunityUnseenCount(lastSeenAtIso: string | null
|
|
57
|
+
export declare function useCommunityUnseenCount(lastSeenAtIso: string | null, options?: {
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
}): number;
|
|
58
60
|
export declare function useThread(postId: string | null): import("@tanstack/react-query").UseQueryResult<ThreadComment[], Error>;
|
|
59
61
|
export declare function useProfile(userId: string | null): import("@tanstack/react-query").UseQueryResult<CommunityProfile, Error>;
|
|
60
62
|
/**
|
|
61
63
|
* One user's posts, newest first, infinite-scrolling like the feed.
|
|
62
64
|
*/
|
|
63
65
|
export declare function useUserPosts(userId: string | null): import("@tanstack/react-query").UseInfiniteQueryResult<InfiniteData<FeedPost[], unknown>, Error>;
|
|
64
|
-
/**
|
|
66
|
+
/** The caller's push preferences (push module). `preferences` is `null` until the
|
|
67
|
+
* device has a push row; `setPreference` updates one flag optimistically. */
|
|
68
|
+
export declare function useNotificationPreferences(): {
|
|
69
|
+
preferences: NotificationPreferences | null;
|
|
70
|
+
isLoading: boolean;
|
|
71
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NotificationPreferences | null, Error>>;
|
|
72
|
+
setPreference: (key: NotificationPreferenceKey, enabled: boolean) => Promise<void>;
|
|
73
|
+
};
|
|
65
74
|
export declare function useMyUid(): string | null;
|
|
66
75
|
/** Profile edits refresh everything derived from a profile row: the profile
|
|
67
76
|
* itself, plus the handle/avatar shown on every cached feed and thread. */
|
|
@@ -111,16 +120,14 @@ export declare function useVotePoll(): import("@tanstack/react-query").UseMutati
|
|
|
111
120
|
optionId: string;
|
|
112
121
|
}, PostCacheSnapshot>;
|
|
113
122
|
/**
|
|
114
|
-
* Optimistic reaction across every post cache (mirror of
|
|
115
|
-
* from the mold's `usePrayForPost`). `
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* that post already had `hasReacted` so a retap of an already-reacted post
|
|
119
|
-
* (the reaction is non-retractable, `setReaction` is idempotent) does not
|
|
120
|
-
* re-emit the analytics event.
|
|
123
|
+
* Optimistic reaction toggle across every post cache (mirror of useToggleLike;
|
|
124
|
+
* renamed from the mold's `usePrayForPost`). `on` defaults to true (react);
|
|
125
|
+
* `on: false` withdraws it. `alreadyReacted` keeps a retap of an
|
|
126
|
+
* already-reacted post from re-emitting the analytics event.
|
|
121
127
|
*/
|
|
122
128
|
export declare function useReactToPost(): import("@tanstack/react-query").UseMutationResult<void, Error, {
|
|
123
129
|
postId: string;
|
|
130
|
+
on?: boolean;
|
|
124
131
|
}, PostCacheSnapshot & {
|
|
125
132
|
alreadyReacted: boolean;
|
|
126
133
|
}>;
|
package/lib/hooks.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.useCommunityUnseenCount = useCommunityUnseenCount;
|
|
|
19
19
|
exports.useThread = useThread;
|
|
20
20
|
exports.useProfile = useProfile;
|
|
21
21
|
exports.useUserPosts = useUserPosts;
|
|
22
|
+
exports.useNotificationPreferences = useNotificationPreferences;
|
|
22
23
|
exports.useMyUid = useMyUid;
|
|
23
24
|
exports.useUpdateProfile = useUpdateProfile;
|
|
24
25
|
exports.useCreatePost = useCreatePost;
|
|
@@ -168,12 +169,13 @@ function useNewPostsCount(sinceIso, topic, polling = true) {
|
|
|
168
169
|
* package has no store access at all, so the host now owns that value and
|
|
169
170
|
* passes it in).
|
|
170
171
|
*/
|
|
171
|
-
function useCommunityUnseenCount(lastSeenAtIso) {
|
|
172
|
+
function useCommunityUnseenCount(lastSeenAtIso, options = {}) {
|
|
172
173
|
const cfg = (0, provider_1.useCommunityConfig)();
|
|
173
174
|
const q = (0, react_query_1.useQuery)({
|
|
174
175
|
queryKey: ["community", "unseen", lastSeenAtIso ?? "never"],
|
|
175
176
|
queryFn: () => (0, service_1.countNewPosts)(cfg, lastSeenAtIso ?? "1970-01-01T00:00:00Z"),
|
|
176
|
-
enabled:
|
|
177
|
+
// `enabled: false` keeps a host's kill-switch arm from creating the identity or polling.
|
|
178
|
+
enabled: cfg.supabase !== null && options.enabled !== false,
|
|
177
179
|
staleTime: 1000 * 60,
|
|
178
180
|
refetchInterval: 1000 * 120,
|
|
179
181
|
refetchIntervalInBackground: false,
|
|
@@ -219,6 +221,40 @@ function useUserPosts(userId) {
|
|
|
219
221
|
});
|
|
220
222
|
}
|
|
221
223
|
/** Our own stable community uid, resolved once and cached for the app session. */
|
|
224
|
+
const NOTIFICATION_PREFS_KEY = ["community", "notificationPreferences"];
|
|
225
|
+
/** The caller's push preferences (push module). `preferences` is `null` until the
|
|
226
|
+
* device has a push row; `setPreference` updates one flag optimistically. */
|
|
227
|
+
function useNotificationPreferences() {
|
|
228
|
+
const cfg = (0, provider_1.useCommunityConfig)();
|
|
229
|
+
const queryClient = (0, react_query_1.useQueryClient)();
|
|
230
|
+
const q = (0, react_query_1.useQuery)({
|
|
231
|
+
queryKey: NOTIFICATION_PREFS_KEY,
|
|
232
|
+
queryFn: () => (0, service_1.fetchNotificationPreferences)(cfg),
|
|
233
|
+
enabled: cfg.supabase !== null && cfg.modules.push,
|
|
234
|
+
staleTime: 1000 * 60,
|
|
235
|
+
});
|
|
236
|
+
const mutation = (0, react_query_1.useMutation)({
|
|
237
|
+
mutationFn: ({ key, enabled }) => (0, service_1.setNotificationPreference)(cfg, key, enabled),
|
|
238
|
+
onMutate: async ({ key, enabled }) => {
|
|
239
|
+
await queryClient.cancelQueries({ queryKey: NOTIFICATION_PREFS_KEY });
|
|
240
|
+
const previous = queryClient.getQueryData(NOTIFICATION_PREFS_KEY);
|
|
241
|
+
if (previous)
|
|
242
|
+
queryClient.setQueryData(NOTIFICATION_PREFS_KEY, { ...previous, [key]: enabled });
|
|
243
|
+
return { previous };
|
|
244
|
+
},
|
|
245
|
+
onError: (_error, _vars, context) => {
|
|
246
|
+
if (context)
|
|
247
|
+
queryClient.setQueryData(NOTIFICATION_PREFS_KEY, context.previous);
|
|
248
|
+
},
|
|
249
|
+
onSettled: () => queryClient.invalidateQueries({ queryKey: NOTIFICATION_PREFS_KEY }),
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
preferences: q.data ?? null,
|
|
253
|
+
isLoading: q.isLoading,
|
|
254
|
+
refetch: q.refetch,
|
|
255
|
+
setPreference: (key, enabled) => mutation.mutateAsync({ key, enabled }),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
222
258
|
function useMyUid() {
|
|
223
259
|
const cfg = (0, provider_1.useCommunityConfig)();
|
|
224
260
|
const q = (0, react_query_1.useQuery)({
|
|
@@ -440,27 +476,24 @@ function useVotePoll() {
|
|
|
440
476
|
});
|
|
441
477
|
}
|
|
442
478
|
/**
|
|
443
|
-
* Optimistic reaction across every post cache (mirror of
|
|
444
|
-
* from the mold's `usePrayForPost`). `
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* that post already had `hasReacted` so a retap of an already-reacted post
|
|
448
|
-
* (the reaction is non-retractable, `setReaction` is idempotent) does not
|
|
449
|
-
* re-emit the analytics event.
|
|
479
|
+
* Optimistic reaction toggle across every post cache (mirror of useToggleLike;
|
|
480
|
+
* renamed from the mold's `usePrayForPost`). `on` defaults to true (react);
|
|
481
|
+
* `on: false` withdraws it. `alreadyReacted` keeps a retap of an
|
|
482
|
+
* already-reacted post from re-emitting the analytics event.
|
|
450
483
|
*/
|
|
451
484
|
function useReactToPost() {
|
|
452
485
|
const cfg = (0, provider_1.useCommunityConfig)();
|
|
453
486
|
const queryClient = (0, react_query_1.useQueryClient)();
|
|
454
487
|
return (0, react_query_1.useMutation)({
|
|
455
|
-
mutationFn: ({ postId }) => (0, service_1.setReaction)(cfg, postId),
|
|
456
|
-
onMutate: async ({ postId }) => {
|
|
488
|
+
mutationFn: ({ postId, on = true }) => (0, service_1.setReaction)(cfg, postId, on),
|
|
489
|
+
onMutate: async ({ postId, on = true }) => {
|
|
457
490
|
let alreadyReacted = false;
|
|
458
491
|
const snapshot = await applyOptimisticToAllPostCaches(queryClient, (post) => {
|
|
459
492
|
if (post.id !== postId)
|
|
460
493
|
return post;
|
|
461
494
|
if (post.hasReacted)
|
|
462
495
|
alreadyReacted = true;
|
|
463
|
-
return (0, models_1.applyReaction)(post);
|
|
496
|
+
return (0, models_1.applyReaction)(post, on);
|
|
464
497
|
});
|
|
465
498
|
return { ...snapshot, alreadyReacted };
|
|
466
499
|
},
|
|
@@ -468,8 +501,8 @@ function useReactToPost() {
|
|
|
468
501
|
if (context)
|
|
469
502
|
rollbackPostCaches(queryClient, context);
|
|
470
503
|
},
|
|
471
|
-
onSuccess: (_data, { postId }, context) => {
|
|
472
|
-
if (!context?.alreadyReacted)
|
|
504
|
+
onSuccess: (_data, { postId, on = true }, context) => {
|
|
505
|
+
if (on && !context?.alreadyReacted)
|
|
473
506
|
(0, events_1.emitEvent)(cfg, events_1.COMMUNITY_EVENTS.reactionAdded, { postId });
|
|
474
507
|
},
|
|
475
508
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export { timeAgo, timeAgoParts } from "./time";
|
|
|
9
9
|
export { COMMUNITY_EVENTS, emitEvent } from "./events";
|
|
10
10
|
export type { CommunityEventName } from "./events";
|
|
11
11
|
export { ensureIdentity, resetIdentity, syncProfileFromHost } from "./identity";
|
|
12
|
-
export { buildFeedSelect, fetchFeedPage, countNewPosts, fetchProfile, fetchUserPosts, searchPosts, fetchThread, createPost, votePoll, createComment, moderateOne, setLike, setReaction, fetchReactionSummaries, reportContent, blockUser, deleteOwnPost, deleteOwnComment, updateProfile, uploadAvatar, } from "./service";
|
|
13
|
-
export type { ModerationVerdict, CreatePostInput, ReportReason, ReportInput, UpdateProfileInput, UpdateProfileResult, } from "./service";
|
|
14
|
-
export { useCommunityFeed, useSearchPosts, useNewPostsCount, useCommunityUnseenCount, useThread, useProfile, useUserPosts, useMyUid, useUpdateProfile, useCreatePost, useCreateComment, useToggleLike, useVotePoll, useReactToPost, useReport, useBlockUser, useDeleteContent, isOptimistic, } from "./hooks";
|
|
12
|
+
export { buildFeedSelect, fetchFeedPage, countNewPosts, fetchProfile, fetchUserPosts, searchPosts, fetchThread, createPost, votePoll, createComment, moderateOne, setLike, setReaction, fetchReactionSummaries, reportContent, blockUser, deleteOwnPost, deleteOwnComment, updateProfile, fetchNotificationPreferences, setNotificationPreference, NOTIFICATION_PREFERENCE_KEYS, uploadAvatar, } from "./service";
|
|
13
|
+
export type { ModerationVerdict, CreatePostInput, ReportReason, ReportInput, UpdateProfileInput, UpdateProfileResult, NotificationPreferenceKey, NotificationPreferences, } from "./service";
|
|
14
|
+
export { useCommunityFeed, useSearchPosts, useNewPostsCount, useCommunityUnseenCount, useThread, useProfile, useUserPosts, useMyUid, useNotificationPreferences, useUpdateProfile, useCreatePost, useCreateComment, useToggleLike, useVotePoll, useReactToPost, useReport, useBlockUser, useDeleteContent, isOptimistic, } from "./hooks";
|
|
15
15
|
export { fetchInbox, localizeExcerpts, markInboxSeen, unreadCount } from "./inbox-service";
|
|
16
16
|
export type { InboxItem, InboxState } from "./inbox-service";
|
|
17
17
|
export { useNotificationInbox, useUnreadNotificationCount, useMarkInboxSeen } from "./inbox-hooks";
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.deleteOwnComment = exports.deleteOwnPost = exports.blockUser = exports.reportContent = exports.fetchReactionSummaries = exports.setReaction = exports.setLike = exports.moderateOne = exports.createComment = exports.votePoll = exports.createPost = exports.fetchThread = exports.searchPosts = exports.fetchUserPosts = exports.fetchProfile = exports.countNewPosts = exports.fetchFeedPage = exports.buildFeedSelect = exports.syncProfileFromHost = exports.resetIdentity = exports.ensureIdentity = exports.emitEvent = exports.COMMUNITY_EVENTS = exports.timeAgoParts = exports.timeAgo = exports.newestCreatedAt = exports.newPostsLabel = exports.mapCommentRow = exports.applyReaction = exports.applyPollVote = exports.mapPostRow = exports.pollPercent = exports.buildPoll = exports.mapProfileRow = exports.normalizeTopic = exports.displayName = exports.EMPTY_REACTION_DATA = exports.EMPTY_POLL_DATA = exports.POLL_OPTION_MAX_LENGTH = exports.POLL_MAX_OPTIONS = exports.POLL_MIN_OPTIONS = exports.COMMENT_MAX_LENGTH = exports.POST_MAX_LENGTH = exports.FEED_PAGE_SIZE = exports.useCommunityConfig = exports.CommunityProvider = exports.languageOf = exports.readerLocale = exports.resolveConfig = exports.CommunityDisabledError = void 0;
|
|
4
|
-
exports.REQUIRED_SCHEMA_VERSION = exports.useMarkInboxSeen = exports.useUnreadNotificationCount = exports.useNotificationInbox = exports.unreadCount = exports.markInboxSeen = exports.localizeExcerpts = exports.fetchInbox = exports.isOptimistic = exports.useDeleteContent = exports.useBlockUser = exports.useReport = exports.useReactToPost = exports.useVotePoll = exports.useToggleLike = exports.useCreateComment = exports.useCreatePost = exports.useUpdateProfile = exports.useMyUid = exports.useUserPosts = exports.useProfile = exports.useThread = exports.useCommunityUnseenCount = exports.useNewPostsCount = exports.useSearchPosts = exports.useCommunityFeed = exports.uploadAvatar = exports.updateProfile = void 0;
|
|
4
|
+
exports.REQUIRED_SCHEMA_VERSION = exports.useMarkInboxSeen = exports.useUnreadNotificationCount = exports.useNotificationInbox = exports.unreadCount = exports.markInboxSeen = exports.localizeExcerpts = exports.fetchInbox = exports.isOptimistic = exports.useDeleteContent = exports.useBlockUser = exports.useReport = exports.useReactToPost = exports.useVotePoll = exports.useToggleLike = exports.useCreateComment = exports.useCreatePost = exports.useUpdateProfile = exports.useNotificationPreferences = exports.useMyUid = exports.useUserPosts = exports.useProfile = exports.useThread = exports.useCommunityUnseenCount = exports.useNewPostsCount = exports.useSearchPosts = exports.useCommunityFeed = exports.uploadAvatar = exports.NOTIFICATION_PREFERENCE_KEYS = exports.setNotificationPreference = exports.fetchNotificationPreferences = exports.updateProfile = void 0;
|
|
5
5
|
var config_1 = require("./config");
|
|
6
6
|
Object.defineProperty(exports, "CommunityDisabledError", { enumerable: true, get: function () { return config_1.CommunityDisabledError; } });
|
|
7
7
|
Object.defineProperty(exports, "resolveConfig", { enumerable: true, get: function () { return config_1.resolveConfig; } });
|
|
@@ -61,6 +61,9 @@ Object.defineProperty(exports, "blockUser", { enumerable: true, get: function ()
|
|
|
61
61
|
Object.defineProperty(exports, "deleteOwnPost", { enumerable: true, get: function () { return service_1.deleteOwnPost; } });
|
|
62
62
|
Object.defineProperty(exports, "deleteOwnComment", { enumerable: true, get: function () { return service_1.deleteOwnComment; } });
|
|
63
63
|
Object.defineProperty(exports, "updateProfile", { enumerable: true, get: function () { return service_1.updateProfile; } });
|
|
64
|
+
Object.defineProperty(exports, "fetchNotificationPreferences", { enumerable: true, get: function () { return service_1.fetchNotificationPreferences; } });
|
|
65
|
+
Object.defineProperty(exports, "setNotificationPreference", { enumerable: true, get: function () { return service_1.setNotificationPreference; } });
|
|
66
|
+
Object.defineProperty(exports, "NOTIFICATION_PREFERENCE_KEYS", { enumerable: true, get: function () { return service_1.NOTIFICATION_PREFERENCE_KEYS; } });
|
|
64
67
|
Object.defineProperty(exports, "uploadAvatar", { enumerable: true, get: function () { return service_1.uploadAvatar; } });
|
|
65
68
|
var hooks_1 = require("./hooks");
|
|
66
69
|
Object.defineProperty(exports, "useCommunityFeed", { enumerable: true, get: function () { return hooks_1.useCommunityFeed; } });
|
|
@@ -71,6 +74,7 @@ Object.defineProperty(exports, "useThread", { enumerable: true, get: function ()
|
|
|
71
74
|
Object.defineProperty(exports, "useProfile", { enumerable: true, get: function () { return hooks_1.useProfile; } });
|
|
72
75
|
Object.defineProperty(exports, "useUserPosts", { enumerable: true, get: function () { return hooks_1.useUserPosts; } });
|
|
73
76
|
Object.defineProperty(exports, "useMyUid", { enumerable: true, get: function () { return hooks_1.useMyUid; } });
|
|
77
|
+
Object.defineProperty(exports, "useNotificationPreferences", { enumerable: true, get: function () { return hooks_1.useNotificationPreferences; } });
|
|
74
78
|
Object.defineProperty(exports, "useUpdateProfile", { enumerable: true, get: function () { return hooks_1.useUpdateProfile; } });
|
|
75
79
|
Object.defineProperty(exports, "useCreatePost", { enumerable: true, get: function () { return hooks_1.useCreatePost; } });
|
|
76
80
|
Object.defineProperty(exports, "useCreateComment", { enumerable: true, get: function () { return hooks_1.useCreateComment; } });
|
package/lib/models.d.ts
CHANGED
|
@@ -203,7 +203,7 @@ export declare function applyPollVote(post: FeedPost, postId: string, optionId:
|
|
|
203
203
|
* caller is expected to have already located the target post (e.g. via
|
|
204
204
|
* `posts.map((p) => (p.id === postId ? applyReaction(p) : p))`).
|
|
205
205
|
*/
|
|
206
|
-
export declare function applyReaction(post: FeedPost): FeedPost;
|
|
206
|
+
export declare function applyReaction(post: FeedPost, on?: boolean): FeedPost;
|
|
207
207
|
export declare function mapCommentRow(row: CommentRow, myUid: string, fallback: string): ThreadComment;
|
|
208
208
|
/** Floating pill label. "1 new post" / "N new posts". */
|
|
209
209
|
export declare function newPostsLabel(count: number): string;
|
package/lib/models.js
CHANGED
|
@@ -150,10 +150,14 @@ function applyPollVote(post, postId, optionId) {
|
|
|
150
150
|
* caller is expected to have already located the target post (e.g. via
|
|
151
151
|
* `posts.map((p) => (p.id === postId ? applyReaction(p) : p))`).
|
|
152
152
|
*/
|
|
153
|
-
function applyReaction(post) {
|
|
154
|
-
if (post.hasReacted)
|
|
153
|
+
function applyReaction(post, on = true) {
|
|
154
|
+
if (post.hasReacted === on)
|
|
155
155
|
return post;
|
|
156
|
-
return {
|
|
156
|
+
return {
|
|
157
|
+
...post,
|
|
158
|
+
hasReacted: on,
|
|
159
|
+
reactionCount: Math.max(0, post.reactionCount + (on ? 1 : -1)),
|
|
160
|
+
};
|
|
157
161
|
}
|
|
158
162
|
function mapCommentRow(row, myUid, fallback) {
|
|
159
163
|
return {
|
package/lib/service.d.ts
CHANGED
|
@@ -102,10 +102,10 @@ export declare function moderateOne(cfg: ResolvedCommunityConfig, input: {
|
|
|
102
102
|
}): Promise<ModerationVerdict>;
|
|
103
103
|
export declare function setLike(cfg: ResolvedCommunityConfig, postId: string, on: boolean): Promise<void>;
|
|
104
104
|
/**
|
|
105
|
-
* `setPrayed` generalized to `setReaction`: insert my private
|
|
106
|
-
* reaction row
|
|
105
|
+
* `setPrayed` generalized to `setReaction`: insert (`on`) or delete my private
|
|
106
|
+
* reaction row, like `setLike`. Gated on `cfg.modules.reaction`.
|
|
107
107
|
*/
|
|
108
|
-
export declare function setReaction(cfg: ResolvedCommunityConfig, postId: string): Promise<void>;
|
|
108
|
+
export declare function setReaction(cfg: ResolvedCommunityConfig, postId: string, on?: boolean): Promise<void>;
|
|
109
109
|
export type ReportReason = "spam" | "harassment" | "hate" | "inappropriate" | "other";
|
|
110
110
|
export interface ReportInput {
|
|
111
111
|
reportedUserId: string;
|
|
@@ -116,6 +116,12 @@ export interface ReportInput {
|
|
|
116
116
|
}
|
|
117
117
|
export declare function reportContent(cfg: ResolvedCommunityConfig, input: ReportInput): Promise<void>;
|
|
118
118
|
export declare function blockUser(cfg: ResolvedCommunityConfig, blockedId: string): Promise<void>;
|
|
119
|
+
export declare const NOTIFICATION_PREFERENCE_KEYS: readonly ["notify_likes", "notify_comments", "notify_reactions"];
|
|
120
|
+
export type NotificationPreferenceKey = (typeof NOTIFICATION_PREFERENCE_KEYS)[number];
|
|
121
|
+
export type NotificationPreferences = Record<NotificationPreferenceKey, boolean>;
|
|
122
|
+
/** The caller's `push_tokens` preferences (push module); `null` while the device has no push row. */
|
|
123
|
+
export declare function fetchNotificationPreferences(cfg: ResolvedCommunityConfig): Promise<NotificationPreferences | null>;
|
|
124
|
+
export declare function setNotificationPreference(cfg: ResolvedCommunityConfig, key: NotificationPreferenceKey, enabled: boolean): Promise<void>;
|
|
119
125
|
export declare function deleteOwnPost(cfg: ResolvedCommunityConfig, postId: string): Promise<void>;
|
|
120
126
|
export declare function deleteOwnComment(cfg: ResolvedCommunityConfig, commentId: string): Promise<void>;
|
|
121
127
|
/**
|
package/lib/service.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// feed/thread composition (search, poll batching via `poll_vote_counts`, reaction batching
|
|
9
9
|
// generalized from that host's prayer-style feature's summary RPC into `post_reaction_summary`).
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.NOTIFICATION_PREFERENCE_KEYS = void 0;
|
|
11
12
|
exports.buildFeedSelect = buildFeedSelect;
|
|
12
13
|
exports.withTranslationFilters = withTranslationFilters;
|
|
13
14
|
exports.fetchReactionSummaries = fetchReactionSummaries;
|
|
@@ -26,6 +27,8 @@ exports.setLike = setLike;
|
|
|
26
27
|
exports.setReaction = setReaction;
|
|
27
28
|
exports.reportContent = reportContent;
|
|
28
29
|
exports.blockUser = blockUser;
|
|
30
|
+
exports.fetchNotificationPreferences = fetchNotificationPreferences;
|
|
31
|
+
exports.setNotificationPreference = setNotificationPreference;
|
|
29
32
|
exports.deleteOwnPost = deleteOwnPost;
|
|
30
33
|
exports.deleteOwnComment = deleteOwnComment;
|
|
31
34
|
exports.updateProfile = updateProfile;
|
|
@@ -416,18 +419,28 @@ async function setLike(cfg, postId, on) {
|
|
|
416
419
|
}
|
|
417
420
|
}
|
|
418
421
|
/**
|
|
419
|
-
* `setPrayed` generalized to `setReaction`: insert my private
|
|
420
|
-
* reaction row
|
|
422
|
+
* `setPrayed` generalized to `setReaction`: insert (`on`) or delete my private
|
|
423
|
+
* reaction row, like `setLike`. Gated on `cfg.modules.reaction`.
|
|
421
424
|
*/
|
|
422
|
-
async function setReaction(cfg, postId) {
|
|
425
|
+
async function setReaction(cfg, postId, on = true) {
|
|
423
426
|
if (!cfg.modules.reaction)
|
|
424
427
|
throw new Error("reaction module is not enabled");
|
|
425
428
|
const client = cfg.requireClient();
|
|
426
429
|
const uid = await requireUid(cfg);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
if (on) {
|
|
431
|
+
const { error } = await client.from("post_reactions").insert({ post_id: postId, user_id: uid });
|
|
432
|
+
// 23505 = already reacted to this post: the desired state holds.
|
|
433
|
+
if (error && error.code !== "23505")
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const { error } = await client
|
|
438
|
+
.from("post_reactions")
|
|
439
|
+
.delete()
|
|
440
|
+
.match({ post_id: postId, user_id: uid });
|
|
441
|
+
if (error)
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
431
444
|
}
|
|
432
445
|
async function reportContent(cfg, input) {
|
|
433
446
|
const client = cfg.requireClient();
|
|
@@ -450,6 +463,34 @@ async function blockUser(cfg, blockedId) {
|
|
|
450
463
|
if (error && error.code !== "23505")
|
|
451
464
|
throw error; // already blocked is fine
|
|
452
465
|
}
|
|
466
|
+
exports.NOTIFICATION_PREFERENCE_KEYS = [
|
|
467
|
+
"notify_likes",
|
|
468
|
+
"notify_comments",
|
|
469
|
+
"notify_reactions",
|
|
470
|
+
];
|
|
471
|
+
/** The caller's `push_tokens` preferences (push module); `null` while the device has no push row. */
|
|
472
|
+
async function fetchNotificationPreferences(cfg) {
|
|
473
|
+
const client = cfg.requireClient();
|
|
474
|
+
const uid = await requireUid(cfg);
|
|
475
|
+
const { data, error } = await client
|
|
476
|
+
.from("push_tokens")
|
|
477
|
+
.select(exports.NOTIFICATION_PREFERENCE_KEYS.join(","))
|
|
478
|
+
.eq("user_id", uid)
|
|
479
|
+
.maybeSingle();
|
|
480
|
+
if (error)
|
|
481
|
+
throw error;
|
|
482
|
+
return data ?? null;
|
|
483
|
+
}
|
|
484
|
+
async function setNotificationPreference(cfg, key, enabled) {
|
|
485
|
+
const client = cfg.requireClient();
|
|
486
|
+
const uid = await requireUid(cfg);
|
|
487
|
+
const { error } = await client
|
|
488
|
+
.from("push_tokens")
|
|
489
|
+
.update({ [key]: enabled })
|
|
490
|
+
.eq("user_id", uid);
|
|
491
|
+
if (error)
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
453
494
|
async function deleteOwnPost(cfg, postId) {
|
|
454
495
|
const client = cfg.requireClient();
|
|
455
496
|
await requireUid(cfg);
|
package/package.json
CHANGED