Skip to content

Commit

Permalink
fix: removed comment content empty for mods (#1753)
Browse files Browse the repository at this point in the history
  • Loading branch information
aeharding authored Nov 27, 2024
1 parent 0a598b3 commit d8ab341
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 44 deletions.
1 change: 1 addition & 0 deletions src/features/comment/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export default function Comment({
item={comment}
showTouchFriendlyLinks={!context}
mdClassName="collapse-md-margins"
canModerate={canModerate}
/>
)}
{context}
Expand Down
41 changes: 36 additions & 5 deletions src/features/comment/CommentContent.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,65 @@
import { IonText } from "@ionic/react";
import { Comment, Post } from "lemmy-js-client";
import { useEffect } from "react";

import { useAppSelector } from "#/store";
import { ModeratorRole } from "#/features/moderation/useCanModerate";
import { useAppDispatch, useAppSelector } from "#/store";

import CommentLinks from "./CommentLinks";
import CommentMarkdown from "./CommentMarkdown";
import { getCommentContent, LOADING_CONTENT } from "./commentSlice";

interface CommentContentProps {
item: Comment | Post;
showTouchFriendlyLinks?: boolean;
mdClassName?: string;
canModerate?: ModeratorRole | undefined;
}

export default function CommentContent({
item,
showTouchFriendlyLinks = true,
mdClassName,
canModerate,
}: CommentContentProps) {
const dispatch = useAppDispatch();
const touchFriendlyLinks = useAppSelector(
(state) => state.settings.general.comments.touchFriendlyLinks,
);
const removedCommentContent = useAppSelector(
(state) => state.comment.commentContentById[item.id],
);

const content = (() => {
// is post
if (!("content" in item)) return item.body ?? item.name;

if (item.content === "") {
return removedCommentContent ?? "";
}

return item.content;
})();

useEffect(() => {
if (!item.removed) return;
if (!("content" in item)) return; // only comments
if (content) return;
if (!canModerate) return;

dispatch(getCommentContent(item.id));
}, [item, content, dispatch, canModerate]);

if (content === LOADING_CONTENT)
return <IonText color="medium">Loading comment...</IonText>;

return (
<>
<CommentMarkdown className={mdClassName} id={item.ap_id}>
{"content" in item ? item.content : (item.body ?? item.name)}
{content}
</CommentMarkdown>
{showTouchFriendlyLinks && touchFriendlyLinks && (
<CommentLinks
markdown={"content" in item ? item.content : (item.body ?? item.name)}
/>
<CommentLinks markdown={content} />
)}
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/features/comment/CommentHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function CommentHeader({
);

function renderActions() {
if (inModqueue) return <ModqueueItemActions item={commentView} />;
if (inModqueue) return <ModqueueItemActions itemView={commentView} />;

if (canModerate)
return <ModActions comment={commentView} role={canModerate} />;
Expand Down
53 changes: 49 additions & 4 deletions src/features/comment/commentSlice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Comment, CommentView } from "lemmy-js-client";

import { clientSelector } from "#/features/auth/authSelectors";
Expand All @@ -10,11 +10,14 @@ import {
import { getRemoteHandle } from "#/helpers/lemmy";
import { AppDispatch, RootState } from "#/store";

export const LOADING_CONTENT = -1;

interface CommentState {
commentCollapsedById: Record<string, boolean>;
commentVotesById: Record<string, 1 | -1 | 0 | undefined>;
commentSavedById: Record<string, boolean | undefined>;
commentById: Record<string, Comment>;
commentContentById: Record<number, string | typeof LOADING_CONTENT>;
}

const initialState: CommentState = {
Expand All @@ -25,6 +28,9 @@ const initialState: CommentState = {
* surgical changes received after user edits or deletes comment
*/
commentById: {},

// https://github.com/LemmyNet/lemmy/issues/5230
commentContentById: {},
};

export const commentSlice = createSlice({
Expand Down Expand Up @@ -76,8 +82,23 @@ export const commentSlice = createSlice({
) => {
state.commentSavedById[action.payload.commentId] = action.payload.saved;
},
setCommentContent: (state, action: PayloadAction<Comment>) => {
state.commentContentById[action.payload.id] = action.payload.content;
},
resetComments: () => initialState,
},
extraReducers: (builder) => {
builder
.addCase(getCommentContent.fulfilled, (state, action) => {
state.commentContentById[action.meta.arg] = action.payload ?? "";
})
.addCase(getCommentContent.pending, (state, action) => {
state.commentContentById[action.meta.arg] = LOADING_CONTENT;
})
.addCase(getCommentContent.rejected, (state, action) => {
state.commentContentById[action.meta.arg] = "";
});
},
});

// Action creators are generated for each case reducer function
Expand All @@ -87,6 +108,7 @@ export const {
updateCommentVote,
updateCommentSaved,
resetComments,
setCommentContent,
} = commentSlice.actions;

export default commentSlice.reducer;
Expand Down Expand Up @@ -172,16 +194,17 @@ export const editComment =
};

export const modRemoveComment =
(commentId: number, removed: boolean, reason?: string) =>
(comment: Comment, removed: boolean, reason?: string) =>
async (dispatch: AppDispatch, getState: () => RootState) => {
const response = await clientSelector(getState())?.removeComment({
comment_id: commentId,
comment_id: comment.id,
removed,
reason,
});

dispatch(setCommentContent(comment));
dispatch(mutatedComment(response.comment_view));
await dispatch(resolveCommentReport(commentId));
await dispatch(resolveCommentReport(comment.id));
};

export const modNukeCommentChain =
Expand Down Expand Up @@ -232,3 +255,25 @@ export const receivedComments =
fetchTagsForHandles(comments.map((c) => getRemoteHandle(c.creator))),
);
};

export const getCommentContent = createAsyncThunk(
"comment/getCommentContent",
async (commentId: number, thunkAPI) => {
const rootState = thunkAPI.getState() as RootState;
const client = clientSelector(rootState);

const log = await client.getModlog({ comment_id: commentId });

return log.removed_comments[0]?.comment.content;
},
{
condition: (commentId, { getState }) => {
const state = getState() as RootState;

if (state.comment.commentContentById[commentId] === undefined)
return true;

return false;
},
},
);
31 changes: 15 additions & 16 deletions src/features/moderation/ModqueueItemActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,43 @@ import { resolveCommentReport, resolvePostReport } from "./modSlice";
import useCanModerate, { getModColor } from "./useCanModerate";

interface ModqueueItemActionsProps {
item: PostView | CommentView;
itemView: PostView | CommentView;
}

export default function ModqueueItemActions({
item,
itemView,
}: ModqueueItemActionsProps) {
const dispatch = useAppDispatch();
const presentToast = useAppToast();
const canModerate = useCanModerate(item.community);
const canModerate = useCanModerate(itemView.community);

async function modRemoveItem(remove: boolean) {
const id = isPost(item) ? item.post.id : item.comment.id;
const isAlreadyRemoved = isPost(item)
? item.post.removed
: item.comment.removed;
const item = isPost(itemView) ? itemView.post : itemView.comment;
const isAlreadyRemoved = item.removed;

// If removal status already in the state you want, just resolve reports
if (remove === isAlreadyRemoved) {
const action = isPost(item) ? resolvePostReport : resolveCommentReport;
await dispatch(action(id));
const action = isPost(itemView)
? resolvePostReport
: resolveCommentReport;
await dispatch(action(item.id));

if (remove)
presentToast(isPost(item) ? postRemovedMod : commentRemovedMod);
else presentToast(isPost(item) ? postApproved : commentApproved);
presentToast(isPost(itemView) ? postRemovedMod : commentRemovedMod);
else presentToast(isPost(itemView) ? postApproved : commentApproved);

return;
}

const action = isPost(item) ? modRemovePost : modRemoveComment;

await dispatch(action(id, remove));
if (isPost(itemView)) await dispatch(modRemovePost(itemView.post, remove));
else await dispatch(modRemoveComment(itemView.comment, remove));

const toastMessage = (() => {
if (remove) {
if (isPost(item)) return postRemovedMod;
if (isPost(itemView)) return postRemovedMod;
else return commentRemovedMod;
} else {
if (isPost(item)) return postRestored;
if (isPost(itemView)) return postRestored;
else return commentRestored;
}
})();
Expand Down
4 changes: 2 additions & 2 deletions src/features/moderation/banner/RemovedBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export default function RemovedBanner({ itemView }: RemovedBannerProps) {
handler: () => {
(async () => {
if (isPost(itemView)) {
dispatch(modRemovePost(itemView.post.id, false));
dispatch(modRemovePost(itemView.post, false));
presentToast(postApproved);
} else {
await dispatch(modRemoveComment(itemView.comment.id, false));
await dispatch(modRemoveComment(itemView.comment, false));
presentToast(commentApproved);
}
})();
Expand Down
8 changes: 3 additions & 5 deletions src/features/moderation/useCommentModActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default function useCommentModActions(commentView: CommentView) {
icon: trashOutline,
handler: () => {
(async () => {
await dispatch(modRemoveComment(comment.id, true));
await dispatch(modRemoveComment(comment, true));

presentToast(commentRemovedMod);
})();
Expand All @@ -112,7 +112,7 @@ export default function useCommentModActions(commentView: CommentView) {
icon: checkmarkCircleOutline,
handler: () => {
(async () => {
await dispatch(modRemoveComment(comment.id, false));
await dispatch(modRemoveComment(comment, false));

presentToast(commentApproved);
})();
Expand All @@ -130,9 +130,7 @@ export default function useCommentModActions(commentView: CommentView) {
cssClass: "mod",
handler: ({ reason }) => {
(async () => {
await dispatch(
modRemoveComment(comment.id, true, reason),
);
await dispatch(modRemoveComment(comment, true, reason));

presentToast(commentRemovedMod);
})();
Expand Down
2 changes: 1 addition & 1 deletion src/features/moderation/useModZoneActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default function useModZoneActions(props: UseModZoneActionsProps) {
// icon: shieldCheckmarkOutline,
// handler: () => {
// (async () => {
// // await dispatch(modRemoveComment(comment.id, false));
// // await dispatch(modRemoveComment(comment, false));
// // presentToast(commentApproved);
// })();
// },
Expand Down
6 changes: 3 additions & 3 deletions src/features/moderation/usePostModActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function usePostModActions(post: PostView) {
icon: trashOutline,
handler: () => {
(async () => {
await dispatch(modRemovePost(post.post.id, true));
await dispatch(modRemovePost(post.post, true));

presentToast(postRemovedMod);
})();
Expand All @@ -89,7 +89,7 @@ export default function usePostModActions(post: PostView) {
icon: checkmarkCircleOutline,
handler: () => {
(async () => {
await dispatch(modRemovePost(post.post.id, false));
await dispatch(modRemovePost(post.post, false));

presentToast(postRestored);
})();
Expand All @@ -107,7 +107,7 @@ export default function usePostModActions(post: PostView) {
cssClass: "mod",
handler: ({ reason }) => {
(async () => {
await dispatch(modRemovePost(post.post.id, true, reason));
await dispatch(modRemovePost(post.post, true, reason));

presentToast(postRemovedMod);
})();
Expand Down
2 changes: 1 addition & 1 deletion src/features/post/inFeed/compact/CompactPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default function CompactPost({ post }: PostProps) {
<ActionsContainer>
<PreviewStats post={post} />
{inModqueue ? (
<ModqueueItemActions item={post} />
<ModqueueItemActions itemView={post} />
) : (
<MoreModActions
className={styles.styledModActions}
Expand Down
2 changes: 1 addition & 1 deletion src/features/post/inFeed/large/LargePost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default function LargePost({ post }: PostProps) {
</div>
{(showVotingButtons || inModqueue) && (
<div className={styles.rightDetails}>
{inModqueue && <ModqueueItemActions item={post} />}
{inModqueue && <ModqueueItemActions itemView={post} />}
<MoreActions post={post} />
{!inModqueue && (
<>
Expand Down
8 changes: 4 additions & 4 deletions src/features/post/postSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { PostView } from "lemmy-js-client";
import { Post, PostView } from "lemmy-js-client";

import {
clientSelector,
Expand Down Expand Up @@ -410,16 +410,16 @@ export const postHiddenByIdSelector = (state: RootState) => {
};

export const modRemovePost =
(postId: number, removed: boolean, reason?: string) =>
(post: Post, removed: boolean, reason?: string) =>
async (dispatch: AppDispatch, getState: () => RootState) => {
const response = await clientSelector(getState())?.removePost({
post_id: postId,
post_id: post.id,
removed,
reason,
});

dispatch(receivedPosts([response.post_view]));
await dispatch(resolvePostReport(postId));
await dispatch(resolvePostReport(post.id));
};

export const modLockPost =
Expand Down
Loading

0 comments on commit d8ab341

Please sign in to comment.