chore: peekoverview issue comments and comment reactions (#2507)
This commit is contained in:
parent
4b03802d22
commit
914657334d
11 changed files with 791 additions and 34 deletions
135
web/components/issues/issue-peek-overview/activity/card.tsx
Normal file
135
web/components/issues/issue-peek-overview/activity/card.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { History } from "lucide-react";
|
||||||
|
// packages
|
||||||
|
import { Loader, Tooltip } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { ActivityIcon, ActivityMessage } from "components/core";
|
||||||
|
import { IssueCommentCard } from "./comment-card";
|
||||||
|
// helpers
|
||||||
|
import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper";
|
||||||
|
|
||||||
|
interface IssueActivityCard {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
user: any;
|
||||||
|
issueComments: any;
|
||||||
|
issueCommentUpdate: (comment: any) => void;
|
||||||
|
issueCommentRemove: (commentId: string) => void;
|
||||||
|
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
|
||||||
|
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
|
||||||
|
const {
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
user,
|
||||||
|
issueComments,
|
||||||
|
issueCommentUpdate,
|
||||||
|
issueCommentRemove,
|
||||||
|
issueCommentReactionCreate,
|
||||||
|
issueCommentReactionRemove,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
console.log("issueComments", issueComments);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flow-root">
|
||||||
|
<ul role="list" className="-mb-4">
|
||||||
|
{issueComments.map((activityItem: any, index: any) => {
|
||||||
|
// determines what type of action is performed
|
||||||
|
const message = activityItem.field ? <ActivityMessage activity={activityItem} /> : "created the issue.";
|
||||||
|
|
||||||
|
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||||
|
return (
|
||||||
|
<li key={activityItem.id}>
|
||||||
|
<div className="relative pb-1">
|
||||||
|
{issueComments.length > 1 && index !== issueComments.length - 1 ? (
|
||||||
|
<span
|
||||||
|
className="absolute top-5 left-5 -ml-[1.5px] h-full w-0.5 bg-custom-background-100"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="relative flex items-start space-x-2">
|
||||||
|
<div>
|
||||||
|
<div className="relative px-1.5">
|
||||||
|
<div className="mt-1.5">
|
||||||
|
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-100 text-custom-text-200 ring-white">
|
||||||
|
{activityItem.field ? (
|
||||||
|
activityItem.new_value === "restore" ? (
|
||||||
|
<History className="h-3.5 w-3.5 text-custom-text-200" />
|
||||||
|
) : (
|
||||||
|
<ActivityIcon activity={activityItem} />
|
||||||
|
)
|
||||||
|
) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? (
|
||||||
|
<img
|
||||||
|
src={activityItem.actor_detail.avatar}
|
||||||
|
alt={activityItem.actor_detail.display_name}
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
className="rounded-full h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white`}
|
||||||
|
>
|
||||||
|
{activityItem.actor_detail.is_bot
|
||||||
|
? activityItem.actor_detail.first_name.charAt(0)
|
||||||
|
: activityItem.actor_detail.display_name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1 py-3">
|
||||||
|
<div className="text-xs text-custom-text-200 break-words">
|
||||||
|
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
|
||||||
|
<span className="text-gray font-medium">Plane</span>
|
||||||
|
) : activityItem.actor_detail.is_bot ? (
|
||||||
|
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
|
||||||
|
) : (
|
||||||
|
<Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
|
||||||
|
<a className="text-gray font-medium">
|
||||||
|
{activityItem.actor_detail.is_bot
|
||||||
|
? activityItem.actor_detail.first_name
|
||||||
|
: activityItem.actor_detail.display_name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
)}{" "}
|
||||||
|
{message}{" "}
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={`${renderLongDateFormat(activityItem.created_at)}, ${render24HourFormatTime(
|
||||||
|
activityItem.created_at
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
<span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
} else if ("comment_json" in activityItem)
|
||||||
|
return (
|
||||||
|
<div key={activityItem.id} className="mt-4">
|
||||||
|
<IssueCommentCard
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
user={user}
|
||||||
|
comment={activityItem}
|
||||||
|
onSubmit={issueCommentUpdate}
|
||||||
|
handleCommentDeletion={issueCommentRemove}
|
||||||
|
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||||
|
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||||
|
// showAccessSpecifier={showAccessSpecifier}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
|
||||||
|
// services
|
||||||
|
import { FileService } from "services/file.service";
|
||||||
|
// ui
|
||||||
|
import { CustomMenu } from "@plane/ui";
|
||||||
|
import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor";
|
||||||
|
// components
|
||||||
|
import { IssueCommentReaction } from "./comment-reaction";
|
||||||
|
// helpers
|
||||||
|
import { timeAgo } from "helpers/date-time.helper";
|
||||||
|
// types
|
||||||
|
import type { IIssueComment } from "types";
|
||||||
|
|
||||||
|
// services
|
||||||
|
const fileService = new FileService();
|
||||||
|
|
||||||
|
type IIssueCommentCard = {
|
||||||
|
comment: IIssueComment;
|
||||||
|
handleCommentDeletion: (comment: string) => void;
|
||||||
|
onSubmit: (data: Partial<IIssueComment>) => void;
|
||||||
|
showAccessSpecifier?: boolean;
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
user: any;
|
||||||
|
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
|
||||||
|
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
|
||||||
|
const {
|
||||||
|
comment,
|
||||||
|
handleCommentDeletion,
|
||||||
|
onSubmit,
|
||||||
|
showAccessSpecifier = false,
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
user,
|
||||||
|
issueCommentReactionCreate,
|
||||||
|
issueCommentReactionRemove,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const editorRef = React.useRef<any>(null);
|
||||||
|
const showEditorRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
formState: { isSubmitting },
|
||||||
|
handleSubmit,
|
||||||
|
setFocus,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
} = useForm<IIssueComment>({
|
||||||
|
defaultValues: comment,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formSubmit = (formData: Partial<IIssueComment>) => {
|
||||||
|
if (isSubmitting) return;
|
||||||
|
|
||||||
|
setIsEditing(false);
|
||||||
|
|
||||||
|
onSubmit({ id: comment.id, ...formData });
|
||||||
|
|
||||||
|
editorRef.current?.setEditorValue(formData.comment_html);
|
||||||
|
showEditorRef.current?.setEditorValue(formData.comment_html);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isEditing && setFocus("comment");
|
||||||
|
}, [isEditing, setFocus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex items-start space-x-3">
|
||||||
|
<div className="relative px-1">
|
||||||
|
{comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? (
|
||||||
|
<img
|
||||||
|
src={comment.actor_detail.avatar}
|
||||||
|
alt={
|
||||||
|
comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name
|
||||||
|
}
|
||||||
|
height={30}
|
||||||
|
width={30}
|
||||||
|
className="grid h-7 w-7 place-items-center rounded-full border-2 border-custom-border-200"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}>
|
||||||
|
{comment.actor_detail.is_bot
|
||||||
|
? comment.actor_detail.first_name.charAt(0)
|
||||||
|
: comment.actor_detail.display_name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="absolute -bottom-0.5 -right-1 rounded-tl bg-custom-background-80 px-0.5 py-px">
|
||||||
|
<MessageSquare className="h-3.5 w-3.5 text-custom-text-200" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs">
|
||||||
|
{comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name}
|
||||||
|
</div>
|
||||||
|
<p className="mt-0.5 text-xs text-custom-text-200">commented {timeAgo(comment.created_at)}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="issue-comments-section p-0">
|
||||||
|
<div className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`}>
|
||||||
|
<div>
|
||||||
|
<LiteTextEditorWithRef
|
||||||
|
onEnterKeyPress={handleSubmit(formSubmit)}
|
||||||
|
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
|
||||||
|
deleteFile={fileService.deleteImage}
|
||||||
|
ref={editorRef}
|
||||||
|
value={watch("comment_html")}
|
||||||
|
debouncedUpdatesEnabled={false}
|
||||||
|
customClassName="min-h-[50px] p-3 shadow-sm"
|
||||||
|
onChange={(comment_json: Object, comment_html: string) => {
|
||||||
|
setValue("comment_json", comment_json);
|
||||||
|
setValue("comment_html", comment_html);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1 self-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="group rounded border border-green-500 bg-green-500/20 p-2 shadow-md duration-300 hover:bg-green-500"
|
||||||
|
onClick={handleSubmit(formSubmit)}
|
||||||
|
>
|
||||||
|
<Check className="h-3 w-3 text-green-500 duration-300 group-hover:text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="group rounded border border-red-500 bg-red-500/20 p-2 shadow-md duration-300 hover:bg-red-500"
|
||||||
|
onClick={() => setIsEditing(false)}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3 text-red-500 duration-300 group-hover:text-white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`relative ${isEditing ? "hidden" : ""}`}>
|
||||||
|
{showAccessSpecifier && (
|
||||||
|
<div className="absolute top-1 right-1.5 z-[1] text-custom-text-300">
|
||||||
|
{comment.access === "INTERNAL" ? <Lock className="h-3 w-3" /> : <Globe2 className="h-3 w-3" />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<LiteReadOnlyEditorWithRef
|
||||||
|
ref={showEditorRef}
|
||||||
|
value={comment.comment_html}
|
||||||
|
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-1">
|
||||||
|
<IssueCommentReaction
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
user={user}
|
||||||
|
comment={comment}
|
||||||
|
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||||
|
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{user?.id === comment.actor && (
|
||||||
|
<CustomMenu ellipsis>
|
||||||
|
<CustomMenu.MenuItem onClick={() => setIsEditing(true)} className="flex items-center gap-1">
|
||||||
|
<Pencil className="h-3 w-3" />
|
||||||
|
Edit comment
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
{showAccessSpecifier && (
|
||||||
|
<>
|
||||||
|
{comment.access === "INTERNAL" ? (
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => onSubmit({ id: comment.id, access: "EXTERNAL" })}
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Globe2 className="h-3 w-3" />
|
||||||
|
Switch to public comment
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
) : (
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => onSubmit({ id: comment.id, access: "INTERNAL" })}
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Lock className="h-3 w-3" />
|
||||||
|
Switch to private comment
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleCommentDeletion(comment.id);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
Delete comment
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
</CustomMenu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import { FileService } from "services/file.service";
|
||||||
|
// components
|
||||||
|
import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
|
||||||
|
// ui
|
||||||
|
import { Button, Tooltip } from "@plane/ui";
|
||||||
|
import { Globe2, Lock } from "lucide-react";
|
||||||
|
|
||||||
|
// types
|
||||||
|
import type { IIssueComment } from "types";
|
||||||
|
|
||||||
|
const defaultValues: Partial<IIssueComment> = {
|
||||||
|
access: "INTERNAL",
|
||||||
|
comment_html: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
type IIssueCommentEditor = {
|
||||||
|
disabled?: boolean;
|
||||||
|
onSubmit: (data: IIssueComment) => Promise<void>;
|
||||||
|
showAccessSpecifier?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type commentAccessType = {
|
||||||
|
icon: any;
|
||||||
|
key: string;
|
||||||
|
label: "Private" | "Public";
|
||||||
|
};
|
||||||
|
const commentAccess: commentAccessType[] = [
|
||||||
|
{
|
||||||
|
icon: Lock,
|
||||||
|
key: "INTERNAL",
|
||||||
|
label: "Private",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Globe2,
|
||||||
|
key: "EXTERNAL",
|
||||||
|
label: "Public",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// services
|
||||||
|
const fileService = new FileService();
|
||||||
|
|
||||||
|
export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
|
||||||
|
const { disabled = false, onSubmit, showAccessSpecifier = false } = props;
|
||||||
|
|
||||||
|
const editorRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
} = useForm<IIssueComment>({ defaultValues });
|
||||||
|
|
||||||
|
const handleAddComment = async (formData: IIssueComment) => {
|
||||||
|
if (!formData.comment_html || isSubmitting) return;
|
||||||
|
|
||||||
|
await onSubmit(formData).then(() => {
|
||||||
|
reset(defaultValues);
|
||||||
|
editorRef.current?.clearEditor();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(handleAddComment)}>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="relative">
|
||||||
|
{showAccessSpecifier && (
|
||||||
|
<div className="absolute bottom-2 left-3 z-[1]">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="access"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<div className="flex border border-custom-border-300 divide-x divide-custom-border-300 rounded overflow-hidden">
|
||||||
|
{commentAccess.map((access) => (
|
||||||
|
<Tooltip key={access.key} tooltipContent={access.label}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onChange(access.key)}
|
||||||
|
className={`grid place-items-center p-1 hover:bg-custom-background-80 ${
|
||||||
|
value === access.key ? "bg-custom-background-80" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<access.icon
|
||||||
|
className={`w-4 h-4 -mt-1 ${
|
||||||
|
value === access.key ? "!text-custom-text-100" : "!text-custom-text-400"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
name="access"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange: onAccessChange, value: accessValue } }) => (
|
||||||
|
<Controller
|
||||||
|
name="comment_html"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange: onCommentChange, value: commentValue } }) => (
|
||||||
|
<LiteTextEditorWithRef
|
||||||
|
onEnterKeyPress={handleSubmit(handleAddComment)}
|
||||||
|
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
|
||||||
|
deleteFile={fileService.deleteImage}
|
||||||
|
ref={editorRef}
|
||||||
|
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
|
||||||
|
customClassName="p-3 min-h-[100px] shadow-sm"
|
||||||
|
debouncedUpdatesEnabled={false}
|
||||||
|
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
|
||||||
|
commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}>
|
||||||
|
{isSubmitting ? "Adding..." : "Comment"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// components
|
||||||
|
import { IssueReaction } from "../reactions";
|
||||||
|
// hooks
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "store/root";
|
||||||
|
|
||||||
|
interface IIssueCommentReaction {
|
||||||
|
workspaceSlug: any;
|
||||||
|
projectId: any;
|
||||||
|
user: any;
|
||||||
|
|
||||||
|
comment: any;
|
||||||
|
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
|
||||||
|
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IssueCommentReaction: FC<IIssueCommentReaction> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, user, comment, issueCommentReactionCreate, issueCommentReactionRemove } = props;
|
||||||
|
|
||||||
|
const { issueDetail: issueDetailStore }: RootStore = useMobxStore();
|
||||||
|
|
||||||
|
const handleCommentReactionCreate = (reaction: string) => {
|
||||||
|
if (issueCommentReactionCreate && comment?.id) issueCommentReactionCreate(comment?.id, reaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCommentReactionRemove = (reaction: string) => {
|
||||||
|
if (issueCommentReactionRemove && comment?.id) issueCommentReactionRemove(comment?.id, reaction);
|
||||||
|
};
|
||||||
|
|
||||||
|
useSWR(
|
||||||
|
workspaceSlug && projectId && comment && comment?.id ? `ISSUE+PEEK_OVERVIEW_COMMENT_${comment?.id}` : null,
|
||||||
|
() => {
|
||||||
|
if (workspaceSlug && projectId && comment && comment.id) {
|
||||||
|
issueDetailStore.fetchIssueCommentReactions(workspaceSlug, projectId, comment?.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const issueReactions = issueDetailStore?.getIssueCommentReactionsByCommentId(comment.id) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<IssueReaction
|
||||||
|
issueReactions={issueReactions}
|
||||||
|
user={user}
|
||||||
|
issueReactionCreate={handleCommentReactionCreate}
|
||||||
|
issueReactionRemove={handleCommentReactionRemove}
|
||||||
|
position="top"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./view";
|
||||||
|
|
||||||
|
export * from "./card";
|
||||||
|
export * from "./comment-editor";
|
||||||
|
export * from "./comment-reaction";
|
||||||
59
web/components/issues/issue-peek-overview/activity/view.tsx
Normal file
59
web/components/issues/issue-peek-overview/activity/view.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
// components
|
||||||
|
import { IssueActivityCard } from "./card";
|
||||||
|
import { IssueCommentEditor } from "./comment-editor";
|
||||||
|
|
||||||
|
interface IIssueComment {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
user: any;
|
||||||
|
issueComments: any;
|
||||||
|
issueCommentCreate: (comment: any) => void;
|
||||||
|
issueCommentUpdate: (comment: any) => void;
|
||||||
|
issueCommentRemove: (commentId: string) => void;
|
||||||
|
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
|
||||||
|
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IssueComment: FC<IIssueComment> = (props) => {
|
||||||
|
const {
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
user,
|
||||||
|
issueComments,
|
||||||
|
issueCommentCreate,
|
||||||
|
issueCommentUpdate,
|
||||||
|
issueCommentRemove,
|
||||||
|
issueCommentReactionCreate,
|
||||||
|
issueCommentReactionRemove,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const handleAddComment = async (formData: any) => {
|
||||||
|
if (!formData.comment_html) return;
|
||||||
|
await issueCommentCreate(formData);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="font-medium text-xl">Activity</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<IssueCommentEditor
|
||||||
|
onSubmit={handleAddComment}
|
||||||
|
// showAccessSpecifier={projectDetails && projectDetails.is_deployed}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IssueActivityCard
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
user={user}
|
||||||
|
issueComments={issueComments}
|
||||||
|
issueCommentUpdate={issueCommentUpdate}
|
||||||
|
issueCommentRemove={issueCommentRemove}
|
||||||
|
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||||
|
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -30,7 +30,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
<div className="font-medium text-sm text-custom-text-200">
|
<div className="font-medium text-sm text-custom-text-200">
|
||||||
{issue?.project_detail?.identifier}-{issue?.sequence_id}
|
{issue?.project_detail?.identifier}-{issue?.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -46,6 +46,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
||||||
onChange={(description: Object, description_html: string) => {
|
onChange={(description: Object, description_html: string) => {
|
||||||
debouncedIssueDescription(description_html);
|
debouncedIssueDescription(description_html);
|
||||||
}}
|
}}
|
||||||
|
customClassName="p-3 min-h-[80px] shadow-sm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IssueReaction
|
<IssueReaction
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ interface IIssueReaction {
|
||||||
user: any;
|
user: any;
|
||||||
issueReactionCreate: (reaction: string) => void;
|
issueReactionCreate: (reaction: string) => void;
|
||||||
issueReactionRemove: (reaction: string) => void;
|
issueReactionRemove: (reaction: string) => void;
|
||||||
|
position?: "top" | "bottom";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueReaction: FC<IIssueReaction> = (props) => {
|
export const IssueReaction: FC<IIssueReaction> = (props) => {
|
||||||
const { issueReactions, user, issueReactionCreate, issueReactionRemove } = props;
|
const { issueReactions, user, issueReactionCreate, issueReactionRemove, position = "bottom" } = props;
|
||||||
|
|
||||||
const handleReaction = (reaction: string) => {
|
const handleReaction = (reaction: string) => {
|
||||||
const isReactionAvailable =
|
const isReactionAvailable =
|
||||||
|
|
@ -22,7 +23,7 @@ export const IssueReaction: FC<IIssueReaction> = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center flex-wrap gap-2">
|
<div className="relative flex items-center flex-wrap gap-2">
|
||||||
<IssueReactionSelector onSelect={handleReaction} position="bottom" />
|
<IssueReactionSelector onSelect={handleReaction} position={position} />
|
||||||
<IssueReactionPreview issueReactions={issueReactions} user={user} handleReaction={handleReaction} />
|
<IssueReactionPreview issueReactions={issueReactions} user={user} handleReaction={handleReaction} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,26 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const issueReactionCreate = (data: string) => {
|
const issueReactionCreate = (reaction: string) =>
|
||||||
issueDetailStore.createIssueReaction(workspaceSlug, projectId, issueId, data);
|
issueDetailStore.createIssueReaction(workspaceSlug, projectId, issueId, reaction);
|
||||||
};
|
|
||||||
|
|
||||||
const issueReactionRemove = (data: string) => {
|
const issueReactionRemove = (reaction: string) =>
|
||||||
issueDetailStore.removeIssueReaction(workspaceSlug, projectId, issueId, data);
|
issueDetailStore.removeIssueReaction(workspaceSlug, projectId, issueId, reaction);
|
||||||
};
|
|
||||||
|
const issueCommentCreate = (comment: any) =>
|
||||||
|
issueDetailStore.createIssueComment(workspaceSlug, projectId, issueId, comment);
|
||||||
|
|
||||||
|
const issueCommentUpdate = (comment: any) =>
|
||||||
|
issueDetailStore.updateIssueComment(workspaceSlug, projectId, issueId, comment?.id, comment);
|
||||||
|
|
||||||
|
const issueCommentRemove = (commentId: string) =>
|
||||||
|
issueDetailStore.removeIssueComment(workspaceSlug, projectId, issueId, commentId);
|
||||||
|
|
||||||
|
const issueCommentReactionCreate = (commentId: string, reaction: string) =>
|
||||||
|
issueDetailStore.creationIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
|
||||||
|
|
||||||
|
const issueCommentReactionRemove = (commentId: string, reaction: string) =>
|
||||||
|
issueDetailStore.removeIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueView
|
<IssueView
|
||||||
|
|
@ -53,6 +66,11 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||||
issueUpdate={issueUpdate}
|
issueUpdate={issueUpdate}
|
||||||
issueReactionCreate={issueReactionCreate}
|
issueReactionCreate={issueReactionCreate}
|
||||||
issueReactionRemove={issueReactionRemove}
|
issueReactionRemove={issueReactionRemove}
|
||||||
|
issueCommentCreate={issueCommentCreate}
|
||||||
|
issueCommentUpdate={issueCommentUpdate}
|
||||||
|
issueCommentRemove={issueCommentRemove}
|
||||||
|
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||||
|
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</IssueView>
|
</IssueView>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import useSWR from "swr";
|
||||||
// components
|
// components
|
||||||
import { PeekOverviewIssueDetails } from "./issue-detail";
|
import { PeekOverviewIssueDetails } from "./issue-detail";
|
||||||
import { PeekOverviewProperties } from "./properties";
|
import { PeekOverviewProperties } from "./properties";
|
||||||
|
import { IssueComment } from "./activity";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
|
|
@ -19,6 +20,11 @@ interface IIssueView {
|
||||||
issueUpdate: (issue: Partial<IIssue>) => void;
|
issueUpdate: (issue: Partial<IIssue>) => void;
|
||||||
issueReactionCreate: (reaction: string) => void;
|
issueReactionCreate: (reaction: string) => void;
|
||||||
issueReactionRemove: (reaction: string) => void;
|
issueReactionRemove: (reaction: string) => void;
|
||||||
|
issueCommentCreate: (comment: any) => void;
|
||||||
|
issueCommentUpdate: (comment: any) => void;
|
||||||
|
issueCommentRemove: (commentId: string) => void;
|
||||||
|
issueCommentReactionCreate: (commentId: string, reaction: string) => void;
|
||||||
|
issueCommentReactionRemove: (commentId: string, reaction: string) => void;
|
||||||
states: any;
|
states: any;
|
||||||
members: any;
|
members: any;
|
||||||
priorities: any;
|
priorities: any;
|
||||||
|
|
@ -53,6 +59,11 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
issueUpdate,
|
issueUpdate,
|
||||||
issueReactionCreate,
|
issueReactionCreate,
|
||||||
issueReactionRemove,
|
issueReactionRemove,
|
||||||
|
issueCommentCreate,
|
||||||
|
issueCommentUpdate,
|
||||||
|
issueCommentRemove,
|
||||||
|
issueCommentReactionCreate,
|
||||||
|
issueCommentReactionRemove,
|
||||||
states,
|
states,
|
||||||
members,
|
members,
|
||||||
priorities,
|
priorities,
|
||||||
|
|
@ -108,6 +119,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
|
|
||||||
const issue = issueDetailStore.getIssue;
|
const issue = issueDetailStore.getIssue;
|
||||||
const issueReactions = issueDetailStore.getIssueReactions;
|
const issueReactions = issueDetailStore.getIssueReactions;
|
||||||
|
const issueComments = issueDetailStore.getIssueComments;
|
||||||
|
|
||||||
const user = userStore?.currentUser;
|
const user = userStore?.currentUser;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -178,7 +191,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
issue && (
|
issue && (
|
||||||
<>
|
<>
|
||||||
{["side-peek", "modal"].includes(peekMode) ? (
|
{["side-peek", "modal"].includes(peekMode) ? (
|
||||||
<div className="space-y-8 p-4 py-5">
|
<div className="space-y-6 p-4 py-5">
|
||||||
<PeekOverviewIssueDetails
|
<PeekOverviewIssueDetails
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
issue={issue}
|
issue={issue}
|
||||||
|
|
@ -197,11 +210,23 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
priorities={priorities}
|
priorities={priorities}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <div className="border border-red-500">Activity</div> */}
|
<div className="border-t border-custom-border-400" />
|
||||||
|
|
||||||
|
<IssueComment
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
user={user}
|
||||||
|
issueComments={issueComments}
|
||||||
|
issueCommentCreate={issueCommentCreate}
|
||||||
|
issueCommentUpdate={issueCommentUpdate}
|
||||||
|
issueCommentRemove={issueCommentRemove}
|
||||||
|
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||||
|
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex">
|
<div className="w-full h-full flex">
|
||||||
<div className="w-full h-full space-y-8 p-4 py-5">
|
<div className="w-full h-full space-y-6 p-4 py-5">
|
||||||
<PeekOverviewIssueDetails
|
<PeekOverviewIssueDetails
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
issue={issue}
|
issue={issue}
|
||||||
|
|
@ -212,7 +237,19 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||||
issueReactionRemove={issueReactionRemove}
|
issueReactionRemove={issueReactionRemove}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <div className="border border-red-500">Activity</div> */}
|
<div className="border-t border-custom-border-400" />
|
||||||
|
|
||||||
|
<IssueComment
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
user={user}
|
||||||
|
issueComments={issueComments}
|
||||||
|
issueCommentCreate={issueCommentCreate}
|
||||||
|
issueCommentUpdate={issueCommentUpdate}
|
||||||
|
issueCommentRemove={issueCommentRemove}
|
||||||
|
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||||
|
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 !w-[400px] h-full border-l border-custom-border-200 p-4 py-5">
|
<div className="flex-shrink-0 !w-[400px] h-full border-l border-custom-border-200 p-4 py-5">
|
||||||
<PeekOverviewProperties
|
<PeekOverviewProperties
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { observable, action, makeObservable, runInAction, computed } from "mobx";
|
import { observable, action, makeObservable, runInAction, computed } from "mobx";
|
||||||
// services
|
// services
|
||||||
import { IssueService, IssueReactionService } from "services/issue";
|
import { IssueService, IssueReactionService, IssueCommentService } from "services/issue";
|
||||||
// types
|
// types
|
||||||
import { RootStore } from "../root";
|
import { RootStore } from "../root";
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
|
@ -31,7 +31,7 @@ export interface IIssueDetailStore {
|
||||||
getIssue: IIssue | null;
|
getIssue: IIssue | null;
|
||||||
getIssueReactions: any | null;
|
getIssueReactions: any | null;
|
||||||
getIssueComments: any | null;
|
getIssueComments: any | null;
|
||||||
getIssueCommentReactions: any | null;
|
getIssueCommentReactionsByCommentId: any | null;
|
||||||
|
|
||||||
// fetch issue details
|
// fetch issue details
|
||||||
fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
|
fetchIssueDetails: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
|
||||||
|
|
@ -59,23 +59,16 @@ export interface IIssueDetailStore {
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
removeIssueComment: (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => Promise<void>;
|
removeIssueComment: (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => Promise<void>;
|
||||||
|
|
||||||
fetchIssueCommentReactions: (
|
fetchIssueCommentReactions: (workspaceSlug: string, projectId: string, commentId: string) => Promise<void>;
|
||||||
|
creationIssueCommentReaction: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
|
||||||
commentId: string
|
|
||||||
) => Promise<void>;
|
|
||||||
addIssueCommentReaction: (
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
commentId: string,
|
||||||
reaction: string
|
reaction: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
removeIssueCommentReaction: (
|
removeIssueCommentReaction: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
commentId: string,
|
||||||
reaction: string
|
reaction: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
@ -104,6 +97,7 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
// service
|
// service
|
||||||
issueService;
|
issueService;
|
||||||
issueReactionService;
|
issueReactionService;
|
||||||
|
issueCommentService;
|
||||||
|
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(_rootStore: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
|
|
@ -120,10 +114,11 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
getIssue: computed,
|
getIssue: computed,
|
||||||
getIssueReactions: computed,
|
getIssueReactions: computed,
|
||||||
getIssueComments: computed,
|
getIssueComments: computed,
|
||||||
getIssueCommentReactions: computed,
|
|
||||||
|
|
||||||
setPeekId: action,
|
setPeekId: action,
|
||||||
|
|
||||||
|
getIssueCommentReactionsByCommentId: action,
|
||||||
|
|
||||||
fetchIssueDetails: action,
|
fetchIssueDetails: action,
|
||||||
createIssue: action,
|
createIssue: action,
|
||||||
updateIssue: action,
|
updateIssue: action,
|
||||||
|
|
@ -141,13 +136,14 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
removeIssueComment: action,
|
removeIssueComment: action,
|
||||||
|
|
||||||
fetchIssueCommentReactions: action,
|
fetchIssueCommentReactions: action,
|
||||||
addIssueCommentReaction: action,
|
creationIssueCommentReaction: action,
|
||||||
removeIssueCommentReaction: action,
|
removeIssueCommentReaction: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = _rootStore;
|
this.rootStore = _rootStore;
|
||||||
this.issueService = new IssueService();
|
this.issueService = new IssueService();
|
||||||
this.issueReactionService = new IssueReactionService();
|
this.issueReactionService = new IssueReactionService();
|
||||||
|
this.issueCommentService = new IssueCommentService();
|
||||||
}
|
}
|
||||||
|
|
||||||
get getIssue() {
|
get getIssue() {
|
||||||
|
|
@ -168,11 +164,11 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
return _comments || null;
|
return _comments || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get getIssueCommentReactions() {
|
getIssueCommentReactionsByCommentId = (commentId: string) => {
|
||||||
if (!this.peekId) return null;
|
if (!commentId) return null;
|
||||||
const _reactions = this.issue_comment_reactions[this.peekId];
|
const _reactions = this.issue_comment_reactions[commentId];
|
||||||
return _reactions || null;
|
return _reactions || null;
|
||||||
}
|
};
|
||||||
|
|
||||||
setPeekId = (issueId: string | null) => (this.peekId = issueId);
|
setPeekId = (issueId: string | null) => (this.peekId = issueId);
|
||||||
|
|
||||||
|
|
@ -426,6 +422,16 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
// comments
|
// comments
|
||||||
fetchIssueComments = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
fetchIssueComments = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
|
const _issueCommentResponse = await this.issueService.getIssueActivities(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
|
const _issueComments = {
|
||||||
|
...this.issue_comments,
|
||||||
|
[issueId]: [..._issueCommentResponse],
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comments = _issueComments;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error creating the issue comment", error);
|
console.warn("error creating the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -433,6 +439,22 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
};
|
};
|
||||||
createIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
|
createIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
|
||||||
try {
|
try {
|
||||||
|
const _issueCommentResponse = await this.issueCommentService.createIssueComment(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
data,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const _issueComments = {
|
||||||
|
...this.issue_comments,
|
||||||
|
[issueId]: [...this.issue_comments[issueId], _issueCommentResponse],
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comments = _issueComments;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error creating the issue comment", error);
|
console.warn("error creating the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -446,6 +468,25 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
data: any
|
data: any
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
|
const _issueCommentResponse = await this.issueCommentService.patchIssueComment(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
commentId,
|
||||||
|
data,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const _issueComments = {
|
||||||
|
...this.issue_comments,
|
||||||
|
[issueId]: this.issue_comments[issueId].map((comment: any) =>
|
||||||
|
comment.id === commentId ? _issueCommentResponse : comment
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comments = _issueComments;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error updating the issue comment", error);
|
console.warn("error updating the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -453,6 +494,16 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
};
|
};
|
||||||
removeIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
|
removeIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
|
||||||
try {
|
try {
|
||||||
|
const _issueComments = {
|
||||||
|
...this.issue_comments,
|
||||||
|
[issueId]: this.issue_comments[issueId].filter((comment: any) => comment.id != commentId),
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId, undefined);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comments = _issueComments;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error removing the issue comment", error);
|
console.warn("error removing the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -460,21 +511,52 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
};
|
};
|
||||||
|
|
||||||
// comment reaction
|
// comment reaction
|
||||||
fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
|
fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, commentId: string) => {
|
||||||
try {
|
try {
|
||||||
|
const _reactions = await this.issueReactionService.listIssueCommentReactions(workspaceSlug, projectId, commentId);
|
||||||
|
|
||||||
|
const _issue_comment_reactions = {
|
||||||
|
...this.issue_comment_reactions,
|
||||||
|
[commentId]: groupReactionEmojis(_reactions),
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comment_reactions = _issue_comment_reactions;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error removing the issue comment", error);
|
console.warn("error removing the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addIssueCommentReaction = async (
|
creationIssueCommentReaction = async (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
commentId: string,
|
||||||
reaction: string
|
reaction: string
|
||||||
) => {
|
) => {
|
||||||
|
let _currentReactions = this.getIssueCommentReactionsByCommentId(commentId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const _reaction = await this.issueReactionService.createIssueCommentReaction(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
commentId,
|
||||||
|
{
|
||||||
|
reaction,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_currentReactions = {
|
||||||
|
..._currentReactions,
|
||||||
|
[reaction]: [..._currentReactions[reaction], { ..._reaction }],
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comment_reactions = {
|
||||||
|
...this.issue_comment_reactions,
|
||||||
|
[commentId]: _currentReactions,
|
||||||
|
};
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error removing the issue comment", error);
|
console.warn("error removing the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -483,11 +565,29 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||||
removeIssueCommentReaction = async (
|
removeIssueCommentReaction = async (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
commentId: string,
|
||||||
reaction: string
|
reaction: string
|
||||||
) => {
|
) => {
|
||||||
|
let _currentReactions = this.getIssueCommentReactionsByCommentId(commentId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const user = this.rootStore.user.currentUser;
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
_currentReactions = {
|
||||||
|
..._currentReactions,
|
||||||
|
[reaction]: [..._currentReactions[reaction].filter((r: any) => r.actor !== user.id)],
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issue_comment_reactions = {
|
||||||
|
...this.issue_comment_reactions,
|
||||||
|
[commentId]: _currentReactions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.issueReactionService.deleteIssueCommentReaction(workspaceSlug, projectId, commentId, reaction);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("error removing the issue comment", error);
|
console.warn("error removing the issue comment", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue