mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: commenting issues fixed
This commit is contained in:
parent
4fd7a78d70
commit
376da835ba
@ -56,15 +56,18 @@ export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="inline-flex flex-shrink-0 items-center gap-2 text-xs">
|
<div className="inline-flex flex-shrink-0 items-center gap-2 text-xs">
|
||||||
|
{projectStore.deploySettings?.votes && (
|
||||||
|
<>
|
||||||
{/* upvotes */}
|
{/* upvotes */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<IssueBlockUpVotes number={totalUpVotes.length} />
|
<IssueBlockUpVotes number={totalUpVotes.length} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* downotes */}
|
{/* downotes */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<IssueBlockDownVotes number={totalDownVotes.length} />
|
<IssueBlockDownVotes number={totalDownVotes.length} />
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* priority */}
|
{/* priority */}
|
||||||
{issue?.priority && (
|
{issue?.priority && (
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { NavbarSearch } from "./search";
|
import { NavbarSearch } from "./search";
|
||||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||||
import { NavbarIssueFilter } from "./issue-filter";
|
import { NavbarIssueFilter } from "./issue-filter";
|
||||||
import { NavbarTheme } from "./theme";
|
import { NavbarTheme } from "./theme";
|
||||||
// mobx react lite
|
// lib
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
// mobx
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// store
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
const renderEmoji = (emoji: string | { name: string; color: string }) => {
|
const renderEmoji = (emoji: string | { name: string; color: string }) => {
|
||||||
if (!emoji) return;
|
if (!emoji) return;
|
||||||
@ -39,7 +39,6 @@ const IssueNavbar = observer(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace_slug && projectStore) {
|
if (workspace_slug && projectStore) {
|
||||||
if (board) {
|
if (board) {
|
||||||
console.log("setting");
|
|
||||||
projectStore.setActiveBoard(board.toString());
|
projectStore.setActiveBoard(board.toString());
|
||||||
} else {
|
} else {
|
||||||
router.push(`/${workspace_slug}/${project_slug}?board=list`);
|
router.push(`/${workspace_slug}/${project_slug}?board=list`);
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
// react-hook-form
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
|
|
||||||
// mobx
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
|
|
||||||
// headless ui
|
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
|
// lib
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// icons
|
// icons
|
||||||
import { ChatBubbleLeftEllipsisIcon, CheckIcon, XMarkIcon, EllipsisVerticalIcon } from "@heroicons/react/24/outline";
|
import { ChatBubbleLeftEllipsisIcon, CheckIcon, XMarkIcon, EllipsisVerticalIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
import { timeAgo } from "helpers/date-time.helper";
|
import { timeAgo } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { Comment } from "types";
|
import { Comment } from "types";
|
||||||
|
// components
|
||||||
import { TipTapEditor } from "components/tiptap";
|
import { TipTapEditor } from "components/tiptap";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -26,51 +20,37 @@ type Props = {
|
|||||||
|
|
||||||
export const CommentCard: React.FC<Props> = observer((props) => {
|
export const CommentCard: React.FC<Props> = observer((props) => {
|
||||||
const { comment, workspaceSlug } = props;
|
const { comment, workspaceSlug } = props;
|
||||||
|
// store
|
||||||
const { user: userStore, issue: issueStore } = useMobxStore();
|
const { user: userStore, issue: issueStore, issueDetails: issueDetailStore } = useMobxStore();
|
||||||
|
// states
|
||||||
const editorRef = useRef<any>(null);
|
|
||||||
const showEditorRef = useRef<any>(null);
|
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setFocus,
|
setFocus,
|
||||||
watch,
|
control,
|
||||||
setValue,
|
} = useForm<any>({
|
||||||
} = useForm<Comment>({
|
defaultValues: { comment_html: comment.comment_html },
|
||||||
defaultValues: comment,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!workspaceSlug || !issueStore.activePeekOverviewIssueId) return;
|
if (!workspaceSlug || !issueDetailStore.peekId) return;
|
||||||
|
|
||||||
await issueStore.deleteIssueCommentAsync(
|
await issueDetailStore.deleteIssueComment(workspaceSlug, comment.project, issueDetailStore.peekId, comment.id);
|
||||||
workspaceSlug,
|
|
||||||
comment.project,
|
|
||||||
issueStore.activePeekOverviewIssueId,
|
|
||||||
comment.id
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCommentUpdate = async (formData: Comment) => {
|
const handleCommentUpdate = async (formData: Comment) => {
|
||||||
if (!workspaceSlug || !issueStore.activePeekOverviewIssueId) return;
|
if (!workspaceSlug || !issueDetailStore.peekId) return;
|
||||||
|
|
||||||
const response = await issueStore.updateIssueCommentAsync(
|
console.log("formData", formData);
|
||||||
|
const response = await issueDetailStore.updateIssueComment(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
comment.project,
|
comment.project,
|
||||||
issueStore.activePeekOverviewIssueId,
|
issueDetailStore.peekId,
|
||||||
comment.id,
|
comment.id,
|
||||||
formData
|
formData
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response) {
|
|
||||||
editorRef.current?.setEditorValue(response.comment_html);
|
|
||||||
showEditorRef.current?.setEditorValue(response.comment_html);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,6 +62,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
|||||||
<div className="relative flex items-start space-x-3">
|
<div className="relative flex items-start space-x-3">
|
||||||
<div className="relative px-1">
|
<div className="relative px-1">
|
||||||
{comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? (
|
{comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? (
|
||||||
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img
|
<img
|
||||||
src={comment.actor_detail.avatar}
|
src={comment.actor_detail.avatar}
|
||||||
alt={
|
alt={
|
||||||
@ -118,17 +99,21 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
|||||||
className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`}
|
className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="comment_html"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
<TipTapEditor
|
<TipTapEditor
|
||||||
workspaceSlug={workspaceSlug as string}
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={editorRef}
|
value={value}
|
||||||
value={watch("comment_html")}
|
|
||||||
debouncedUpdatesEnabled={false}
|
debouncedUpdatesEnabled={false}
|
||||||
customClassName="min-h-[50px] p-3 shadow-sm"
|
customClassName="min-h-[50px] p-3 shadow-sm"
|
||||||
onChange={(comment_json: Object, comment_html: string) => {
|
onChange={(comment_json: Object, comment_html: string) => {
|
||||||
setValue("comment_json", comment_json);
|
onChange(comment_html);
|
||||||
setValue("comment_html", comment_html);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1 self-end">
|
<div className="flex gap-1 self-end">
|
||||||
<button
|
<button
|
||||||
@ -149,8 +134,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
|||||||
</form>
|
</form>
|
||||||
<div className={`${isEditing ? "hidden" : ""}`}>
|
<div className={`${isEditing ? "hidden" : ""}`}>
|
||||||
<TipTapEditor
|
<TipTapEditor
|
||||||
workspaceSlug={workspaceSlug as string}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
ref={showEditorRef}
|
|
||||||
value={comment.comment_html}
|
value={comment.comment_html}
|
||||||
editable={false}
|
editable={false}
|
||||||
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
||||||
|
@ -20,6 +20,8 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const comments = issueDetailStore.details[issueDetailStore.peekId || ""]?.comments || [];
|
const comments = issueDetailStore.details[issueDetailStore.peekId || ""]?.comments || [];
|
||||||
|
|
||||||
|
console.log("comments", comments);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium">Activity</h4>
|
<h4 className="font-medium">Activity</h4>
|
||||||
|
@ -19,7 +19,7 @@ export const IssuePeekOverview: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspace_slug, project_slug, peekId } = router.query;
|
const { workspace_slug, project_slug, peekId, board } = router.query;
|
||||||
// store
|
// store
|
||||||
const { issueDetails: issueDetailStore, issue: issueStore } = useMobxStore();
|
const { issueDetails: issueDetailStore, issue: issueStore } = useMobxStore();
|
||||||
|
|
||||||
@ -34,14 +34,13 @@ export const IssuePeekOverview: React.FC<Props> = observer((props) => {
|
|||||||
}, [workspace_slug, project_slug, issueDetailStore, issueDetails, peekId, issueStore.issues]);
|
}, [workspace_slug, project_slug, issueDetailStore, issueDetails, peekId, issueStore.issues]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
const { query } = router;
|
|
||||||
delete query.peekId;
|
|
||||||
|
|
||||||
issueDetailStore.setPeekId(null);
|
issueDetailStore.setPeekId(null);
|
||||||
router.replace(
|
router.replace(
|
||||||
{
|
{
|
||||||
pathname: `/${workspace_slug?.toString()}/${project_slug}`,
|
pathname: `/${workspace_slug?.toString()}/${project_slug}`,
|
||||||
query,
|
query: {
|
||||||
|
board,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
{ shallow: true }
|
{ shallow: true }
|
||||||
|
@ -30,7 +30,7 @@ export const SidePeekView: React.FC<Props> = observer((props) => {
|
|||||||
<PeekOverviewIssueDetails issueDetails={issueDetails} />
|
<PeekOverviewIssueDetails issueDetails={issueDetails} />
|
||||||
</div>
|
</div>
|
||||||
{/* issue properties */}
|
{/* issue properties */}
|
||||||
<div className="w-full mt-10">
|
<div className="w-full mt-6">
|
||||||
<PeekOverviewIssueProperties issueDetails={issueDetails} />
|
<PeekOverviewIssueProperties issueDetails={issueDetails} />
|
||||||
</div>
|
</div>
|
||||||
{/* divider */}
|
{/* divider */}
|
||||||
|
@ -38,7 +38,6 @@ const HomePage = () => {
|
|||||||
router.push(`/onboarding?next_path=${next_path}`);
|
router.push(`/onboarding?next_path=${next_path}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("hello");
|
|
||||||
router.push(next_path.toString());
|
router.push(next_path.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
|
"tailwindcss/nesting": {},
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
|
@ -23,7 +23,14 @@ export interface IIssueDetailStore {
|
|||||||
fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void;
|
fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void;
|
||||||
// issue comments
|
// issue comments
|
||||||
addIssueComment: (workspaceId: string, projectId: string, issueId: string, data: any) => Promise<void>;
|
addIssueComment: (workspaceId: string, projectId: string, issueId: string, data: any) => Promise<void>;
|
||||||
deleteIssueComment: (workspaceId: string, projectId: string, issueId: string) => void;
|
updateIssueComment: (
|
||||||
|
workspaceId: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
comment_id: string,
|
||||||
|
data: any
|
||||||
|
) => Promise<any>;
|
||||||
|
deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void;
|
||||||
// issue reactions
|
// issue reactions
|
||||||
addIssueReaction: (workspaceId: string, projectId: string, issueId: string, data: any) => void;
|
addIssueReaction: (workspaceId: string, projectId: string, issueId: string, data: any) => void;
|
||||||
removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, data: any) => void;
|
removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, data: any) => void;
|
||||||
@ -40,7 +47,7 @@ class IssueDetailStore implements IssueDetailStore {
|
|||||||
details: {
|
details: {
|
||||||
[key: string]: IIssue;
|
[key: string]: IIssue;
|
||||||
} = {};
|
} = {};
|
||||||
issueService: any;
|
issueService;
|
||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
|
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(_rootStore: RootStore) {
|
||||||
@ -97,7 +104,6 @@ class IssueDetailStore implements IssueDetailStore {
|
|||||||
try {
|
try {
|
||||||
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId);
|
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId);
|
||||||
const issueCommentResponse = await this.issueService.createIssueComment(workspaceSlug, projectId, issueId, data);
|
const issueCommentResponse = await this.issueService.createIssueComment(workspaceSlug, projectId, issueId, data);
|
||||||
console.log("issueCommentResponse", issueCommentResponse);
|
|
||||||
if (issueDetails) {
|
if (issueDetails) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
@ -116,36 +122,58 @@ class IssueDetailStore implements IssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
|
updateIssueComment = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
commentId: string,
|
||||||
|
data: any
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const issueVoteResponse = await this.issueService.updateIssueComment(workspaceSlug, projectId, issueId, data);
|
const issueCommentUpdateResponse = await this.issueService.updateIssueComment(
|
||||||
if (issueVoteResponse) {
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
commentId,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
// const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
|
if (issueCommentUpdateResponse) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
const remainingComments = this.details[issueId].comments.filter((com) => com.id === commentId);
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueId]: {
|
||||||
...issueDetails,
|
...this.details[issueId],
|
||||||
comments: commentsResponse,
|
comments: [...remainingComments, issueCommentUpdateResponse],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return issueCommentUpdateResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to add issue comment");
|
console.log("Failed to add issue comment");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteIssueComment = async () => {
|
deleteIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, comment_id: string) => {
|
||||||
try {
|
try {
|
||||||
const issueVoteResponse = await this.issueService.deleteIssueComment(workspaceSlug, projectId, issueId, data);
|
const issueVoteResponse = await this.issueService.deleteIssueComment(
|
||||||
// const issueDetails = await this.issueService.fetchIssueDetails(workspaceSlug, projectId, issueId);
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
comment_id
|
||||||
|
);
|
||||||
|
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
if (issueVoteResponse) {
|
if (issueVoteResponse) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueId]: {
|
||||||
...issueDetails,
|
...this.details[issueId],
|
||||||
|
comments: issueComments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user