Skip to content

Commit

Permalink
Add comment edit and deletion, comment ellipse for more actions (aeha…
Browse files Browse the repository at this point in the history
…rding#82)

* Add comment edit and deletion, comment ellipse for more actions

Resolves aeharding#81, aeharding#20
  • Loading branch information
aeharding authored Jun 30, 2023
1 parent ce8ad65 commit cf0bab0
Show file tree
Hide file tree
Showing 20 changed files with 484 additions and 74 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"es2022": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
Expand Down
17 changes: 5 additions & 12 deletions src/features/auth/authSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,18 +164,11 @@ export const handleSelector = createSelector([activeAccount], (account) => {
export const login =
(client: LemmyHttp, username: string, password: string, totp?: string) =>
async (dispatch: AppDispatch) => {
let res;

try {
res = await client.login({
username_or_email: username,
password,
totp_2fa_token: totp || undefined,
});
} catch (error) {
// todo
throw error;
}
const res = await client.login({
username_or_email: username,
password,
totp_2fa_token: totp || undefined,
});

if (!res.jwt) {
// todo
Expand Down
34 changes: 22 additions & 12 deletions src/features/comment/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import AnimateHeight from "react-animate-height";
import CommentContent from "./CommentContent";
import useKeyPressed from "../../helpers/useKeyPressed";
import SlidingNestedCommentVote from "../shared/sliding/SlidingNestedCommentVote";
import CommentEllipsis from "./CommentEllipsis";
import { useAppSelector } from "../../store";

const rainbowColors = [
"#FF0000", // Red
Expand Down Expand Up @@ -173,7 +175,7 @@ interface CommentProps {
}

export default function Comment({
comment,
comment: commentView,
highlightedCommentId,
depth,
onClick,
Expand All @@ -185,12 +187,16 @@ export default function Comment({
className,
rootIndex,
}: CommentProps) {
const commentById = useAppSelector((state) => state.comment.commentById);
const keyPressed = useKeyPressed();
// eslint-disable-next-line no-undef
const commentRef = useRef<HTMLIonItemElement>(null);

// Comment from slice might be more up to date, e.g. edits
const comment = commentById[commentView.comment.id] ?? commentView.comment;

useEffect(() => {
if (highlightedCommentId !== comment.comment.id) return;
if (highlightedCommentId !== comment.id) return;

setTimeout(() => {
commentRef.current?.scrollIntoView({
Expand All @@ -204,7 +210,7 @@ export default function Comment({
return (
<AnimateHeight duration={200} height={fullyCollapsed ? 0 : "auto"}>
<SlidingNestedCommentVote
item={comment}
item={commentView}
className={className}
rootIndex={rootIndex}
collapsed={!!collapsed}
Expand All @@ -219,25 +225,29 @@ export default function Comment({
>
<PositionedContainer
depth={depth || 0}
highlighted={highlightedCommentId === comment.comment.id}
highlighted={highlightedCommentId === comment.id}
>
<Container depth={depth || 0}>
<Header>
<StyledPersonLabel
person={comment.creator}
opId={comment.post.creator_id}
distinguished={comment.comment.distinguished}
person={commentView.creator}
opId={commentView.post.creator_id}
distinguished={comment.distinguished}
/>
<Vote
voteFromServer={comment.my_vote as 1 | 0 | -1 | undefined}
score={comment.counts.score}
id={comment.comment.id}
voteFromServer={commentView.my_vote as 1 | 0 | -1 | undefined}
score={commentView.counts.score}
id={commentView.comment.id}
type="comment"
/>
<div style={{ flex: 1 }} />
{!collapsed ? (
<>
<Ago date={comment.comment.published} />
<CommentEllipsis
comment={commentView}
rootIndex={rootIndex}
/>
<Ago date={comment.published} />
</>
) : (
<>
Expand All @@ -255,7 +265,7 @@ export default function Comment({
if (e.target.nodeName === "A") e.stopPropagation();
}}
>
<CommentContent item={comment.comment} />
<CommentContent item={comment} />
{context}
</Content>
</AnimateHeight>
Expand Down
226 changes: 226 additions & 0 deletions src/features/comment/CommentEllipsis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import {
IonActionSheet,
IonIcon,
useIonModal,
useIonRouter,
useIonToast,
} from "@ionic/react";
import {
arrowDownOutline,
arrowUndoOutline,
arrowUpOutline,
bookmarkOutline,
chevronCollapseOutline,
ellipsisHorizontal,
pencilOutline,
personOutline,
shareOutline,
trashOutline,
} from "ionicons/icons";
import { CommentView } from "lemmy-js-client";
import { useContext, useState } from "react";
import { useBuildGeneralBrowseLink } from "../../helpers/routes";
import { useAppDispatch, useAppSelector } from "../../store";
import { handleSelector, jwtSelector } from "../auth/authSlice";
import { PageContext } from "../auth/PageContext";
import Login from "../auth/Login";
import CommentReply from "./reply/CommentReply";
import {
getHandle,
getRemoteHandle,
canModify as isCommentMutable,
} from "../../helpers/lemmy";
import { deleteComment, voteOnComment } from "./commentSlice";
import styled from "@emotion/styled";
import { notEmpty } from "../../helpers/array";
import CommentEditing from "./edit/CommentEdit";
import useCollapseRootComment from "./useCollapseRootComment";
import { FeedContext } from "../feed/FeedContext";

const StyledIonIcon = styled(IonIcon)`
padding: 8px 12px;
margin: -8px -12px;
`;

interface MoreActionsProps {
comment: CommentView;
rootIndex: number | undefined;
}

export default function MoreActions({ comment, rootIndex }: MoreActionsProps) {
const buildGeneralBrowseLink = useBuildGeneralBrowseLink();
const dispatch = useAppDispatch();
const [open, setOpen] = useState(false);
const jwt = useAppSelector(jwtSelector);
const { refresh: refreshPost } = useContext(FeedContext);
const myHandle = useAppSelector(handleSelector);
const [present] = useIonToast();
const collapseRootComment = useCollapseRootComment(comment, rootIndex);

const router = useIonRouter();

const pageContext = useContext(PageContext);
const [login, onDismiss] = useIonModal(Login, {
onDismiss: (data: string, role: string) => onDismiss(data, role),
});

const [reply, onDismissReply] = useIonModal(CommentReply, {
onDismiss: (data: string, role: string) => {
if (role === "post") refreshPost();
onDismissReply(data, role);
},
item: comment,
});

const [edit, onDismissEdit] = useIonModal(CommentEditing, {
onDismiss: (data: string, role: string) => {
onDismissEdit(data, role);
},
item: comment,
});

const commentVotesById = useAppSelector(
(state) => state.comment.commentVotesById
);

const myVote = commentVotesById[comment.comment.id] ?? comment.my_vote;

const isMyComment = getRemoteHandle(comment.creator) === myHandle;

return (
<>
<StyledIonIcon
icon={ellipsisHorizontal}
onClick={(e) => {
setOpen(true);
e.stopPropagation();
}}
/>

<IonActionSheet
cssClass="left-align-buttons"
onClick={(e) => e.stopPropagation()}
isOpen={open}
buttons={[
{
text: myVote !== 1 ? "Upvote" : "Undo Upvote",
role: "upvote",
icon: arrowUpOutline,
},
{
text: myVote !== -1 ? "Downvote" : "Undo Downvote",
role: "downvote",
icon: arrowDownOutline,
},
{
text: "Save",
role: "save",
icon: bookmarkOutline,
},
isMyComment && isCommentMutable(comment)
? {
text: "Edit",
role: "edit",
icon: pencilOutline,
}
: undefined,
isMyComment && isCommentMutable(comment)
? {
text: "Delete",
role: "delete",
icon: trashOutline,
}
: undefined,
{
text: "Reply",
role: "reply",
icon: arrowUndoOutline,
},
{
text: getHandle(comment.creator),
role: "person",
icon: personOutline,
},
{
text: "Share",
role: "share",
icon: shareOutline,
},
rootIndex !== undefined
? {
text: "Collapse to Top",
role: "collapse",
icon: chevronCollapseOutline,
}
: undefined,
{
text: "Cancel",
role: "cancel",
},
].filter(notEmpty)}
onWillDismiss={async (e) => {
setOpen(false);

switch (e.detail.role) {
case "upvote":
if (!jwt) return login({ presentingElement: pageContext.page });

dispatch(voteOnComment(comment.comment.id, myVote === 1 ? 0 : 1));
break;
case "downvote":
if (!jwt) return login({ presentingElement: pageContext.page });

dispatch(
voteOnComment(comment.comment.id, myVote === -1 ? 0 : -1)
);
break;
case "save":
if (!jwt) return login({ presentingElement: pageContext.page });
// TODO
break;
case "edit":
edit({ presentingElement: pageContext.page });
break;
case "delete":
try {
await dispatch(deleteComment(comment.comment.id));
} catch (error) {
present({
message: "Problem deleting comment. Please try again.",
duration: 3500,
position: "bottom",
color: "danger",
});

throw error;
}

present({
message: "Comment deleted!",
duration: 3500,
position: "bottom",
color: "primary",
});
break;
case "reply":
if (!jwt) return login({ presentingElement: pageContext.page });

reply({ presentingElement: pageContext.page });
break;
case "person":
router.push(
buildGeneralBrowseLink(`/u/${getHandle(comment.creator)}`)
);
break;
case "share":
navigator.share({ url: comment.comment.ap_id });
break;
case "collapse":
collapseRootComment();
break;
}
}}
/>
</>
);
}
1 change: 1 addition & 0 deletions src/features/comment/CommentTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function CommentTree({
);
}

// eslint-disable-next-line no-sparse-arrays
return [
<React.Fragment key={comment.comment_view.comment.id}>
{!first && <CommentHr depth={comment.depth} />}
Expand Down
6 changes: 3 additions & 3 deletions src/features/comment/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { RefresherCustomEvent } from "@ionic/core";
import { getPost } from "../post/postSlice";
import useClient from "../../helpers/useClient";
import { useSetActivePage } from "../auth/AppContext";
import { PostContext } from "../post/detail/PostContext";
import { FeedContext } from "../feed/FeedContext";
import { jwtSelector } from "../auth/authSlice";

const centerCss = css`
Expand Down Expand Up @@ -201,7 +201,7 @@ export default function Comments({
}, [commentTree, comments.length, highlightedCommentId, loading, op]);

return (
<PostContext.Provider value={{ refreshPost: () => fetchComments(true) }}>
<FeedContext.Provider value={{ refresh: () => fetchComments(true) }}>
<IonRefresher
slot="fixed"
onIonRefresh={handleRefresh}
Expand All @@ -225,6 +225,6 @@ export default function Comments({
: {}
}
/>
</PostContext.Provider>
</FeedContext.Provider>
);
}
Loading

0 comments on commit cf0bab0

Please sign in to comment.