* feat(newsfeed): improved the newsfeed ui * feat(newsfeed): updated the ui * feat(newsfeed): some improvements * feat(newsfeed): cleanup * chore(newsfeed): added translations * fix(newsfeed): fixing comments and reviews from greptile and coderabbit * fix(newsfeed): fixing comments and reviews from greptile and coderabbit, improved replies to a reply * fix(newsfeed): fixing reviews * fix(ui): expose cancelLabel prop in comment-tree-input and pass localized text --------- Co-authored-by: Rotimi Best <[email protected]>
680 lines
21 KiB
TypeScript
680 lines
21 KiB
TypeScript
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
|
|
import type {
|
|
CreateNewsfeedCommentRequest,
|
|
CreateNewsfeedRequest,
|
|
DeleteNewsfeedCommentRequest,
|
|
DeleteNewsfeedRequest,
|
|
GetNewsfeedCommentsRequest,
|
|
GetNewsfeedRequest,
|
|
ListNewsfeed,
|
|
ListNewsfeedRequest,
|
|
NewsfeedCommentsResponse,
|
|
ReactToNewsfeedRequest,
|
|
UpdateNewsfeedCommentRequest,
|
|
UpdateNewsfeedRequest
|
|
} from '../utils/types';
|
|
import type { TNewsfeedCreate, TNewsfeedReactionUpdate, TNewsfeedUpdate } from '@cio/utils/validation/newsfeed';
|
|
import {
|
|
ZNewsfeedCommentCreate,
|
|
ZNewsfeedCommentUpdate,
|
|
ZNewsfeedCreate,
|
|
ZNewsfeedReactionUpdate,
|
|
ZNewsfeedUpdate
|
|
} from '@cio/utils/validation/newsfeed';
|
|
|
|
import { get } from 'svelte/store';
|
|
import { isHtmlValueEmpty } from '$lib/utils/functions/toHtml';
|
|
import { mapZodErrorsToTranslations } from '$lib/utils/validation';
|
|
import { profile } from '$lib/utils/store/user';
|
|
import { snackbar } from '$features/ui/snackbar/store';
|
|
|
|
export type NewsfeedCommentsByFeedId = {
|
|
items: NewsfeedCommentsResponse['items'];
|
|
totalCount: number;
|
|
hasMore: boolean;
|
|
isLoading: boolean;
|
|
cursor: string | null;
|
|
};
|
|
|
|
/**
|
|
* API class for newsfeed operations
|
|
*/
|
|
export class NewsfeedApi extends BaseApiWithErrors {
|
|
feeds = $state<ListNewsfeed>([]);
|
|
|
|
commentsByFeedId = $state<Record<string, NewsfeedCommentsByFeedId>>({});
|
|
|
|
repliesByParentId = $state<Record<number, NewsfeedCommentsByFeedId>>({});
|
|
|
|
isNewFeedModalOpen = $state(false);
|
|
|
|
/**
|
|
* Opens the new feed modal
|
|
*/
|
|
openNewFeedModal() {
|
|
this.isNewFeedModalOpen = true;
|
|
}
|
|
|
|
/**
|
|
* Closes the new feed modal
|
|
*/
|
|
closeNewFeedModal() {
|
|
this.isNewFeedModalOpen = false;
|
|
}
|
|
|
|
/**
|
|
* Lists newsfeed items for a course
|
|
*/
|
|
async list(courseId: string) {
|
|
await this.execute<ListNewsfeedRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed.$get({
|
|
param: { courseId },
|
|
query: {}
|
|
}),
|
|
logContext: 'listing newsfeed',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.feeds = Array.isArray(response.data) ? response.data : response.data.items || [];
|
|
for (const feed of this.feeds) {
|
|
this.initComments(feed.id, feed.commentCount || 0);
|
|
}
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to list newsfeed');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gets a newsfeed item by ID
|
|
*/
|
|
async get(courseId: string, feedId: string) {
|
|
await this.execute<GetNewsfeedRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].$get({
|
|
param: { courseId, feedId }
|
|
}),
|
|
logContext: 'fetching newsfeed item',
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to fetch newsfeed item');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a new newsfeed item
|
|
*/
|
|
async create(courseId: string, fields: TNewsfeedCreate) {
|
|
if (isHtmlValueEmpty(fields.content)) {
|
|
this.errors = mapZodErrorsToTranslations(
|
|
ZNewsfeedCreate.safeParse({ ...fields, content: '', courseId }).error!,
|
|
'newsfeed'
|
|
);
|
|
return;
|
|
}
|
|
|
|
const result = ZNewsfeedCreate.safeParse({ ...fields, courseId });
|
|
if (!result.success) {
|
|
this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed');
|
|
return;
|
|
}
|
|
|
|
await this.execute<CreateNewsfeedRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed.$post({
|
|
param: { courseId },
|
|
json: result.data
|
|
}),
|
|
logContext: 'creating newsfeed',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
snackbar.success('Post created successfully');
|
|
const feedsArray = Array.isArray(this.feeds) ? this.feeds : [];
|
|
const author = get(profile);
|
|
|
|
const newFeed = {
|
|
...response.data,
|
|
authorProfileId: author.id,
|
|
authorFullname: author.fullname,
|
|
authorUsername: author.username,
|
|
authorAvatarUrl: author.avatarUrl,
|
|
commentCount: 0
|
|
};
|
|
|
|
this.feeds = [...feedsArray, newFeed];
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to create post');
|
|
return;
|
|
}
|
|
if ('error' in result && 'field' in result && result.field) {
|
|
this.errors[result.field] = result.error;
|
|
snackbar.error(result.error);
|
|
} else if ('error' in result) {
|
|
this.errors.general = result.error;
|
|
snackbar.error(result.error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates a newsfeed item (content and isPinned only)
|
|
*/
|
|
async update(courseId: string, feedId: string, fields: TNewsfeedUpdate) {
|
|
if (fields.content !== undefined && isHtmlValueEmpty(fields.content)) {
|
|
this.errors = mapZodErrorsToTranslations(
|
|
ZNewsfeedUpdate.safeParse({ ...fields, content: '' }).error!,
|
|
'newsfeed'
|
|
);
|
|
snackbar.error(this.errors.content ?? 'Failed to update post');
|
|
return;
|
|
}
|
|
|
|
const result = ZNewsfeedUpdate.safeParse(fields);
|
|
|
|
if (!result.success) {
|
|
this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed');
|
|
return;
|
|
}
|
|
|
|
await this.execute<UpdateNewsfeedRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].$put({
|
|
param: { courseId, feedId },
|
|
json: result.data
|
|
}),
|
|
logContext: 'updating newsfeed',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
snackbar.success('Post updated successfully');
|
|
this.feeds = this.feeds.map((feed) => {
|
|
if (feed.id !== feedId) {
|
|
return feed;
|
|
}
|
|
|
|
return {
|
|
...feed,
|
|
...response.data,
|
|
authorProfileId: feed.authorProfileId,
|
|
authorFullname: feed.authorFullname,
|
|
authorUsername: feed.authorUsername,
|
|
authorAvatarUrl: feed.authorAvatarUrl
|
|
};
|
|
});
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to update post');
|
|
return;
|
|
}
|
|
if ('error' in result && 'field' in result && result.field) {
|
|
this.errors[result.field] = result.error;
|
|
snackbar.error(result.error);
|
|
} else if ('error' in result) {
|
|
this.errors.general = result.error;
|
|
snackbar.error(result.error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates a newsfeed item's reaction
|
|
*/
|
|
async react(courseId: string, feedId: string, reaction: TNewsfeedReactionUpdate['reaction']) {
|
|
const result = ZNewsfeedReactionUpdate.safeParse({ reaction });
|
|
if (!result.success) {
|
|
this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed');
|
|
return;
|
|
}
|
|
|
|
await this.execute<ReactToNewsfeedRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].react.$put({
|
|
param: { courseId, feedId },
|
|
json: result.data
|
|
}),
|
|
logContext: 'updating newsfeed reaction',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.feeds = this.feeds.map((feed) => {
|
|
if (feed.id !== feedId) {
|
|
return feed;
|
|
}
|
|
|
|
return {
|
|
...feed,
|
|
...response.data,
|
|
authorProfileId: feed.authorProfileId,
|
|
authorFullname: feed.authorFullname,
|
|
authorUsername: feed.authorUsername,
|
|
authorAvatarUrl: feed.authorAvatarUrl
|
|
};
|
|
});
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to update reaction');
|
|
return;
|
|
}
|
|
if ('error' in result && 'field' in result && result.field) {
|
|
this.errors[result.field] = result.error;
|
|
snackbar.error(result.error);
|
|
} else if ('error' in result) {
|
|
this.errors.general = result.error;
|
|
snackbar.error(result.error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deletes a newsfeed item
|
|
*/
|
|
async delete(courseId: string, feedId: string) {
|
|
await this.execute<DeleteNewsfeedRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].$delete({
|
|
param: { courseId, feedId }
|
|
}),
|
|
logContext: 'deleting newsfeed',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
snackbar.success('Post deleted successfully');
|
|
this.feeds = this.feeds.filter((feed) => feed.id !== feedId);
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to delete post');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize comment state for a feed
|
|
*/
|
|
initComments(feedId: string, totalCount: number) {
|
|
if (!this.commentsByFeedId[feedId]) {
|
|
this.commentsByFeedId[feedId] = {
|
|
items: [],
|
|
totalCount,
|
|
hasMore: totalCount > 0,
|
|
isLoading: false,
|
|
cursor: null
|
|
};
|
|
} else {
|
|
this.commentsByFeedId[feedId].totalCount = totalCount;
|
|
this.commentsByFeedId[feedId].hasMore = totalCount > this.commentsByFeedId[feedId].items.length;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch paginated comments for a feed
|
|
*/
|
|
async getComments(courseId: string, feedId: string, limit = 5) {
|
|
if (!this.commentsByFeedId[feedId]) {
|
|
this.initComments(feedId, 0);
|
|
}
|
|
|
|
this.commentsByFeedId[feedId].isLoading = true;
|
|
|
|
await this.execute<GetNewsfeedCommentsRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].comments.$get({
|
|
param: { courseId, feedId },
|
|
query: { limit: String(limit) }
|
|
}),
|
|
logContext: 'fetching newsfeed comments',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.commentsByFeedId[feedId] = {
|
|
items: response.data.items,
|
|
totalCount: response.data.totalCount,
|
|
hasMore: response.data.hasMore,
|
|
isLoading: false,
|
|
cursor: response.data.nextCursor
|
|
};
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
this.commentsByFeedId[feedId].isLoading = false;
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to fetch comments');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch replies for a specific parent comment
|
|
*/
|
|
async getReplies(courseId: string, feedId: string, parentId: number, limit = 5) {
|
|
if (!this.repliesByParentId[parentId]) {
|
|
this.repliesByParentId[parentId] = {
|
|
items: [],
|
|
totalCount: 0,
|
|
hasMore: false,
|
|
isLoading: true,
|
|
cursor: null
|
|
};
|
|
} else {
|
|
this.repliesByParentId[parentId].isLoading = true;
|
|
}
|
|
|
|
await this.execute<GetNewsfeedCommentsRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].comments.$get({
|
|
param: { courseId, feedId },
|
|
query: { parentId: String(parentId), limit: String(limit) }
|
|
}),
|
|
logContext: 'fetching newsfeed comment replies',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.repliesByParentId[parentId] = {
|
|
items: response.data.items,
|
|
totalCount: response.data.totalCount,
|
|
hasMore: response.data.hasMore,
|
|
isLoading: false,
|
|
cursor: response.data.nextCursor
|
|
};
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (this.repliesByParentId[parentId]) {
|
|
this.repliesByParentId[parentId].isLoading = false;
|
|
}
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to fetch replies');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load more replies for a specific parent comment (uses cursor)
|
|
*/
|
|
async loadMoreReplies(courseId: string, feedId: string, parentId: number, limit = 5) {
|
|
const replyState = this.repliesByParentId[parentId];
|
|
if (!replyState || !replyState.hasMore || replyState.isLoading || !replyState.cursor) {
|
|
return;
|
|
}
|
|
|
|
replyState.isLoading = true;
|
|
|
|
await this.execute<GetNewsfeedCommentsRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].comments.$get({
|
|
param: { courseId, feedId },
|
|
query: { parentId: String(parentId), cursor: replyState.cursor!, limit: String(limit) }
|
|
}),
|
|
logContext: 'loading more newsfeed replies',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.repliesByParentId[parentId] = {
|
|
items: [...replyState.items, ...response.data.items],
|
|
totalCount: response.data.totalCount,
|
|
hasMore: response.data.hasMore,
|
|
isLoading: false,
|
|
cursor: response.data.nextCursor
|
|
};
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (this.repliesByParentId[parentId]) {
|
|
this.repliesByParentId[parentId].isLoading = false;
|
|
}
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to load more replies');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load more comments (uses cursor)
|
|
*/
|
|
async loadMoreComments(courseId: string, feedId: string, limit = 5) {
|
|
const commentState = this.commentsByFeedId[feedId];
|
|
if (!commentState || !commentState.hasMore || commentState.isLoading || !commentState.cursor) {
|
|
return;
|
|
}
|
|
|
|
commentState.isLoading = true;
|
|
|
|
await this.execute<GetNewsfeedCommentsRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].comments.$get({
|
|
param: { courseId, feedId },
|
|
query: { cursor: commentState.cursor!, limit: String(limit) }
|
|
}),
|
|
logContext: 'loading more newsfeed comments',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.commentsByFeedId[feedId] = {
|
|
items: [...commentState.items, ...response.data.items],
|
|
totalCount: response.data.totalCount,
|
|
hasMore: response.data.hasMore,
|
|
isLoading: false,
|
|
cursor: response.data.nextCursor
|
|
};
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
this.commentsByFeedId[feedId].isLoading = false;
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to load more comments');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a newsfeed comment or reply
|
|
*/
|
|
async createComment(
|
|
courseId: string,
|
|
feedId: string,
|
|
content: string,
|
|
author: { id: string; username: string; fullname: string; avatarUrl: string },
|
|
parentId?: number
|
|
) {
|
|
const result = ZNewsfeedCommentCreate.safeParse({ courseNewsfeedId: feedId, content, parentId });
|
|
if (!result.success) {
|
|
this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed');
|
|
return;
|
|
}
|
|
|
|
await this.execute<CreateNewsfeedCommentRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed[':feedId'].comment.$post({
|
|
param: { courseId, feedId },
|
|
json: result.data
|
|
}),
|
|
logContext: 'creating newsfeed comment',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
snackbar.success('Comment added successfully');
|
|
|
|
const newComment = {
|
|
...response.data,
|
|
authorProfileId: author.id,
|
|
authorFullname: author.fullname,
|
|
authorUsername: author.username,
|
|
authorAvatarUrl: author.avatarUrl,
|
|
replyCount: 0
|
|
};
|
|
|
|
if (parentId) {
|
|
const repliesState = this.repliesByParentId[parentId];
|
|
if (repliesState) {
|
|
this.repliesByParentId[parentId] = {
|
|
...repliesState,
|
|
items: [...repliesState.items, newComment],
|
|
totalCount: repliesState.totalCount + 1
|
|
};
|
|
} else {
|
|
this.repliesByParentId[parentId] = {
|
|
items: [newComment],
|
|
totalCount: 1,
|
|
hasMore: false,
|
|
isLoading: false,
|
|
cursor: null
|
|
};
|
|
}
|
|
|
|
// Increment parent reply count in local top-level items
|
|
const commentState = this.commentsByFeedId[feedId];
|
|
if (commentState) {
|
|
const updatedItems = commentState.items.map((item) => {
|
|
if (item.id === parentId) {
|
|
return { ...item, replyCount: (item.replyCount || 0) + 1 };
|
|
}
|
|
return item;
|
|
});
|
|
this.commentsByFeedId[feedId] = { ...commentState, items: updatedItems };
|
|
}
|
|
} else {
|
|
const commentState = this.commentsByFeedId[feedId];
|
|
if (commentState) {
|
|
this.commentsByFeedId[feedId] = {
|
|
...commentState,
|
|
items: [newComment, ...commentState.items],
|
|
totalCount: commentState.totalCount + 1
|
|
};
|
|
}
|
|
}
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to add comment');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates a newsfeed comment
|
|
*/
|
|
async updateComment(courseId: string, feedId: string, commentId: string, content: string, parentId?: number) {
|
|
const result = ZNewsfeedCommentUpdate.safeParse({ content });
|
|
if (!result.success) {
|
|
this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed');
|
|
return;
|
|
}
|
|
|
|
await this.execute<UpdateNewsfeedCommentRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed.comment[':commentId'].$put({
|
|
param: { courseId, commentId },
|
|
json: result.data
|
|
}),
|
|
logContext: 'updating newsfeed comment',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
snackbar.success('Comment updated successfully');
|
|
|
|
const numericId = Number(commentId);
|
|
if (parentId && this.repliesByParentId[parentId]) {
|
|
const replyState = this.repliesByParentId[parentId];
|
|
const updatedItems = replyState.items.map((item) => {
|
|
if (item.id === numericId) {
|
|
return { ...item, content: response.data?.content ?? content };
|
|
}
|
|
return item;
|
|
});
|
|
this.repliesByParentId[parentId] = { ...replyState, items: updatedItems };
|
|
} else {
|
|
const commentState = this.commentsByFeedId[feedId];
|
|
if (commentState) {
|
|
const updatedItems = commentState.items.map((item) => {
|
|
if (item.id === numericId) {
|
|
return { ...item, content: response.data?.content ?? content };
|
|
}
|
|
return item;
|
|
});
|
|
this.commentsByFeedId[feedId] = { ...commentState, items: updatedItems };
|
|
}
|
|
}
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to update comment');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deletes a newsfeed comment
|
|
*/
|
|
async deleteComment(courseId: string, feedId: string, commentId: string, parentId?: number) {
|
|
await this.execute<DeleteNewsfeedCommentRequest>({
|
|
requestFn: () =>
|
|
classroomio.course[':courseId'].newsfeed.comment[':commentId'].$delete({
|
|
param: { courseId, commentId }
|
|
}),
|
|
logContext: 'deleting newsfeed comment',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
snackbar.success('Comment deleted successfully');
|
|
|
|
const numericId = Number(commentId);
|
|
if (parentId && this.repliesByParentId[parentId]) {
|
|
const replyState = this.repliesByParentId[parentId];
|
|
this.repliesByParentId[parentId] = {
|
|
...replyState,
|
|
items: replyState.items.filter((item) => String(item.id) !== commentId),
|
|
totalCount: Math.max(0, replyState.totalCount - 1)
|
|
};
|
|
|
|
const commentState = this.commentsByFeedId[feedId];
|
|
if (commentState) {
|
|
const updatedItems = commentState.items.map((item) => {
|
|
if (item.id === parentId) {
|
|
return { ...item, replyCount: Math.max(0, (item.replyCount || 1) - 1) };
|
|
}
|
|
return item;
|
|
});
|
|
this.commentsByFeedId[feedId] = { ...commentState, items: updatedItems };
|
|
}
|
|
} else {
|
|
if (this.repliesByParentId[numericId]) {
|
|
delete this.repliesByParentId[numericId];
|
|
}
|
|
const commentState = this.commentsByFeedId[feedId];
|
|
if (commentState) {
|
|
this.commentsByFeedId[feedId] = {
|
|
...commentState,
|
|
items: commentState.items.filter((item) => String(item.id) !== commentId),
|
|
totalCount: Math.max(0, commentState.totalCount - 1)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
snackbar.error('Failed to delete comment');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export const newsfeedApi = /* @__PURE__ */ new NewsfeedApi();
|