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
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { mermaid } from "@streamdown/mermaid";
import type { RendererContext } from "@superset/panes";
import { Avatar, AvatarFallback, AvatarImage } from "@superset/ui/avatar";
import {
type ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { LuCheck, LuCopy } from "react-icons/lu";
import { LuCheck } from "react-icons/lu";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {
Expand All @@ -30,84 +29,18 @@ interface CommentPaneProps {

export function CommentPane({ context }: CommentPaneProps) {
const data = context.pane.data as CommentPaneData;
const [copied, setCopied] = useState(false);
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isMountedRef = useRef(true);

useEffect(() => {
return () => {
isMountedRef.current = false;
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
};
}, []);

const handleCopyAll = useCallback(() => {
void electronTrpcClient.external.copyText
.mutate(data.body)
.then(() => {
if (!isMountedRef.current) return;
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
setCopied(true);
copyTimerRef.current = setTimeout(() => {
if (!isMountedRef.current) return;
setCopied(false);
copyTimerRef.current = null;
}, 1500);
})
.catch((err) => {
console.warn("Failed to copy comment text", err);
});
}, [data.body]);

return (
<div className="flex h-full w-full flex-col overflow-hidden">
<div className="flex items-center gap-2 border-b border-border px-4 py-2.5">
<Avatar className="size-5 shrink-0">
{data.avatarUrl ? (
<AvatarImage src={data.avatarUrl} alt={data.authorLogin} />
) : null}
<AvatarFallback className="text-[10px] font-medium">
{data.authorLogin.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<span className="text-sm font-medium text-foreground">
{data.authorLogin}
</span>
{data.path && (
<span className="truncate text-xs text-muted-foreground">
{data.path}
{data.line != null ? `:${data.line}` : ""}
</span>
)}
<button
type="button"
onClick={handleCopyAll}
className="ml-auto flex shrink-0 items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
<div className="comment-pane-markdown min-h-0 min-w-0 flex-1 overflow-y-auto select-text">
<article className="w-full px-6 py-5">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
components={commentComponents}
>
{copied ? (
<>
<LuCheck className="size-3" />
Copied
</>
) : (
<>
<LuCopy className="size-3" />
Copy All
</>
)}
</button>
</div>
<div className="comment-pane-markdown min-h-0 flex-1 overflow-y-auto select-text">
<article className="w-full px-6 py-5">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
components={commentComponents}
>
{data.body}
</ReactMarkdown>
</article>
</div>
{data.body}
</ReactMarkdown>
</article>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { RendererContext } from "@superset/panes";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { useCallback, useEffect, useRef, useState } from "react";
import { FaGithub } from "react-icons/fa";
import { LuCheck, LuCopy } from "react-icons/lu";
import { electronTrpcClient } from "renderer/lib/trpc-client";
import type { CommentPaneData, PaneViewerData } from "../../../../../../types";

interface CommentPaneHeaderExtrasProps {
context: RendererContext<PaneViewerData>;
}

export function CommentPaneHeaderExtras({
context,
}: CommentPaneHeaderExtrasProps) {
const data = context.pane.data as CommentPaneData;
const [copied, setCopied] = useState(false);
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isMountedRef = useRef(true);

useEffect(() => {
return () => {
isMountedRef.current = false;
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
};
}, []);

const handleCopyAll = useCallback(() => {
void electronTrpcClient.external.copyText
.mutate(data.body)
.then(() => {
if (!isMountedRef.current) return;
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
setCopied(true);
copyTimerRef.current = setTimeout(() => {
if (!isMountedRef.current) return;
setCopied(false);
copyTimerRef.current = null;
}, 1500);
})
.catch((err) => {
console.warn("Failed to copy comment text", err);
});
}, [data.body]);

return (
<>
{data.url && (
<Tooltip>
<TooltipTrigger asChild>
<a
href={data.url}
target="_blank"
rel="noopener noreferrer"
aria-label="Open on GitHub"
className="rounded p-1 text-muted-foreground/60 transition-colors hover:text-muted-foreground"
>
<FaGithub className="size-3.5" />
</a>
</TooltipTrigger>
<TooltipContent side="bottom" showArrow={false}>
Open on GitHub
</TooltipContent>
</Tooltip>
)}
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label="Copy comment"
onClick={handleCopyAll}
className="rounded p-1 text-muted-foreground/60 transition-colors hover:text-muted-foreground"
>
{copied ? (
<LuCheck className="size-3.5" />
) : (
<LuCopy className="size-3.5" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="bottom" showArrow={false}>
{copied ? "Copied" : "Copy comment"}
</TooltipContent>
</Tooltip>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CommentPaneHeaderExtras } from "./CommentPaneHeaderExtras";
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { RendererContext } from "@superset/panes";
import { cn } from "@superset/ui/utils";
import { MessageSquare } from "lucide-react";
import type { CommentPaneData, PaneViewerData } from "../../../../../../types";

interface CommentPaneTitleProps {
context: RendererContext<PaneViewerData>;
}

export function CommentPaneTitle({ context }: CommentPaneTitleProps) {
const data = context.pane.data as CommentPaneData;
const { isActive } = context;

return (
<div className="flex min-w-0 flex-1 items-center gap-2">
{data.avatarUrl ? (
<img
src={data.avatarUrl}
alt=""
className="size-3.5 shrink-0 rounded-full"
/>
) : (
<MessageSquare className="size-3.5 shrink-0" />
)}
<span
className={cn(
"shrink-0 text-xs transition-colors duration-150",
isActive ? "text-foreground" : "text-muted-foreground",
)}
title={data.authorLogin}
>
{data.authorLogin}
</span>
{data.path && (
<span
className="min-w-0 truncate text-xs text-muted-foreground"
title={`${data.path}${data.line != null ? `:${data.line}` : ""}`}
>
{data.path}
{data.line != null ? `:${data.line}` : ""}
</span>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CommentPaneTitle } from "./CommentPaneTitle";
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ import {
TerminalSquare,
} from "lucide-react";
import { useMemo } from "react";
import { FaGithub } from "react-icons/fa";
import {
LuArrowDownToLine,
LuArrowUpRight,
LuClipboard,
LuClipboardCopy,
LuEraser,
Expand Down Expand Up @@ -50,6 +48,8 @@ import type {
import { BrowserPane, BrowserPaneToolbar } from "./components/BrowserPane";
import { ChatPane } from "./components/ChatPane";
import { CommentPane } from "./components/CommentPane";
import { CommentPaneHeaderExtras } from "./components/CommentPane/components/CommentPaneHeaderExtras";
import { CommentPaneTitle } from "./components/CommentPane/components/CommentPaneTitle";
import { DiffPane } from "./components/DiffPane";
import { FilePane } from "./components/FilePane";
import { FilePaneHeaderExtras } from "./components/FilePane/components/FilePaneHeaderExtras";
Expand Down Expand Up @@ -482,25 +482,15 @@ export function usePaneRegistry(
const data = pane.data as CommentPaneData;
return data.authorLogin;
},
renderTitle: (ctx: RendererContext<PaneViewerData>) => (
<CommentPaneTitle context={ctx} />
),
renderPane: (ctx: RendererContext<PaneViewerData>) => (
<CommentPane context={ctx} />
),
renderHeaderExtras: (ctx: RendererContext<PaneViewerData>) => {
const data = ctx.pane.data as CommentPaneData;
if (!data.url) return null;
return (
<a
href={data.url}
target="_blank"
rel="noopener noreferrer"
className="flex shrink-0 items-center gap-0.5 rounded p-0.5 text-muted-foreground/60 transition-colors hover:text-muted-foreground"
aria-label="View on GitHub"
>
<FaGithub className="size-3.5" />
<LuArrowUpRight className="size-3" />
</a>
);
},
renderHeaderExtras: (ctx: RendererContext<PaneViewerData>) => (
<CommentPaneHeaderExtras context={ctx} />
),
contextMenuActions: (_ctx, defaults) =>
defaults.map((d) =>
d.key === "close-pane" ? { ...d, label: "Close Comment" } : d,
Expand Down
Loading