Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions apps/dashboard/src/components/details/comment-more-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {
CopyIcon,
Delete01Icon,
EditIcon,
LinkIcon,
MoreHorizontalIcon,
ViewIcon,
} from "@diffkit/icons";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@diffkit/ui/components/dropdown-menu";
import { toast } from "@diffkit/ui/components/sonner";
import { Spinner } from "@diffkit/ui/components/spinner";
import { cn } from "@diffkit/ui/lib/utils";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import {
deleteComment,
deleteReviewComment,
minimizeComment,
} from "#/lib/github.functions";
import { githubQueryKeys } from "#/lib/github.query";

type CommentMoreMenuProps = {
commentId: number;
body: string;
owner: string;
repo: string;
number: number;
commentType: "issue" | "review";
isAuthor: boolean;
onStartEdit?: () => void;
};

export function CommentMoreMenu({
commentId,
body,
owner,
repo,
number,
commentType,
isAuthor,
onStartEdit,
}: CommentMoreMenuProps) {
const queryClient = useQueryClient();
const [isDeleting, setIsDeleting] = useState(false);
const [isHiding, setIsHiding] = useState(false);

const commentUrl = `https://github.com/${owner}/${repo}/${commentType === "review" ? "pull" : "issues"}/${number}#${commentType === "review" ? "discussion_r" : "issuecomment-"}${commentId}`;

const handleCopyLink = () => {
void navigator.clipboard.writeText(commentUrl);
toast.success("Link copied");
};

const handleCopyMarkdown = () => {
void navigator.clipboard.writeText(body);
toast.success("Markdown copied");
};

const handleHide = async () => {
setIsHiding(true);
try {
const result = await minimizeComment({
data: { owner, repo, commentId, commentType },
});
if (result.ok) {
toast.success("Comment hidden");
void queryClient.invalidateQueries({
queryKey: githubQueryKeys.all,
});
} else {
toast.error(result.error);
}
} catch {
toast.error("Failed to hide comment");
} finally {
setIsHiding(false);
}
};

const handleDelete = async () => {
setIsDeleting(true);
try {
const deleteFn =
commentType === "review" ? deleteReviewComment : deleteComment;
const result = await deleteFn({
data: { owner, repo, commentId },
});
if (result.ok) {
toast.success("Comment deleted");
void queryClient.invalidateQueries({
queryKey: githubQueryKeys.all,
});
} else {
toast.error(result.error);
}
} catch {
toast.error("Failed to delete comment");
} finally {
setIsDeleting(false);
}
};

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className={cn(
"rounded-md p-0.5 text-muted-foreground opacity-0 transition-all hover:bg-surface-2 hover:text-foreground group-hover/comment:opacity-100",
"data-[state=open]:opacity-100",
)}
>
<MoreHorizontalIcon size={14} strokeWidth={2} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[160px]">
<DropdownMenuItem onClick={handleCopyLink}>
<LinkIcon size={14} strokeWidth={2} />
Copy link
</DropdownMenuItem>
<DropdownMenuItem onClick={handleCopyMarkdown}>
<CopyIcon size={14} strokeWidth={2} />
Copy Markdown
</DropdownMenuItem>
{isAuthor && (
<>
<DropdownMenuSeparator />
{onStartEdit && (
<DropdownMenuItem onClick={onStartEdit}>
<EditIcon size={14} strokeWidth={2} />
Edit
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => void handleHide()}
disabled={isHiding}
>
{isHiding ? (
<Spinner className="size-3.5" />
) : (
<ViewIcon size={14} strokeWidth={2} />
)}
Hide
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => void handleDelete()}
disabled={isDeleting}
variant="destructive"
>
{isDeleting ? (
<Spinner className="size-3.5" />
) : (
<Delete01Icon size={14} strokeWidth={2} />
)}
Delete
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
}
7 changes: 6 additions & 1 deletion apps/dashboard/src/components/details/detail-activity.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CommentIcon } from "@diffkit/icons";
import {
MarkdownEditor,
type MentionCandidate,
Expand Down Expand Up @@ -140,7 +141,11 @@ export function DetailCommentBox({
onClick={handleSend}
className="flex items-center gap-1.5 rounded-lg bg-foreground px-3 py-1.5 text-xs font-medium text-background transition-opacity disabled:opacity-40"
>
{isSending && <Spinner size={12} />}
{isSending ? (
<Spinner size={12} />
) : (
<CommentIcon size={12} strokeWidth={2} />
)}
Send
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Markdown } from "@diffkit/ui/components/markdown";
import { cn } from "@diffkit/ui/lib/utils";
import { useQueryClient } from "@tanstack/react-query";
import { useCallback, useRef, useState } from "react";
import { CommentMoreMenu } from "#/components/details/comment-more-menu";
import {
DetailActivityHeader,
DetailCommentBox,
Expand Down Expand Up @@ -238,6 +239,7 @@ export function IssueDetailActivitySection({
issueNumber,
scope,
issueAuthor,
viewerLogin,
}: {
comments?: IssueComment[];
events?: TimelineEvent[];
Expand All @@ -250,6 +252,7 @@ export function IssueDetailActivitySection({
issueNumber: number;
scope: GitHubQueryScope;
issueAuthor: GitHubActor | null;
viewerLogin?: string;
}) {
const allItems: IssueTimelineItem[] = [
...(comments ?? []).map((comment) => ({
Expand Down Expand Up @@ -340,7 +343,7 @@ export function IssueDetailActivitySection({
<div
key={`comment-${comment.id}`}
className={cn(
"flex flex-col gap-1 py-4",
"group/comment relative flex flex-col gap-1 py-4",
index === 0 && "pt-5",
)}
>
Expand All @@ -360,6 +363,20 @@ export function IssueDetailActivitySection({
<span className="text-[13px] text-muted-foreground">
{formatRelativeTime(comment.createdAt)}
</span>
<div className="ml-auto">
<CommentMoreMenu
commentId={comment.id}
body={comment.body}
owner={owner}
repo={repo}
number={issueNumber}
commentType="issue"
isAuthor={
viewerLogin != null &&
comment.author?.login === viewerLogin
}
/>
</div>
</div>
<Markdown className="text-muted-foreground">
{comment.body}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import {
githubIssuePageQueryOptions,
githubQueryKeys,
githubViewerQueryOptions,
} from "#/lib/github.query";
import { githubRevalidationSignalKeys } from "#/lib/github-revalidation";
import { useGitHubSignalStream } from "#/lib/use-github-signal-stream";
Expand Down Expand Up @@ -49,6 +50,10 @@ export function IssueDetailPage() {
...githubIssuePageQueryOptions(scope, input),
enabled: hasMounted,
});
const viewerQuery = useQuery({
...githubViewerQueryOptions(scope),
enabled: hasMounted,
});
useGitHubSignalStream(webhookRefreshTargets);

const issue = pageQuery.data?.detail;
Expand Down Expand Up @@ -90,6 +95,7 @@ export function IssueDetailPage() {
issueNumber={issueNumber}
scope={scope}
issueAuthor={issue.author}
viewerLogin={viewerQuery.data?.login}
/>
</>
}
Expand Down
Loading
Loading