diff --git a/packages/ui/src/features/code-editor/components/CodeEditorPanel.tsx b/packages/ui/src/features/code-editor/components/CodeEditorPanel.tsx index 841af0ea91..08600db06d 100644 --- a/packages/ui/src/features/code-editor/components/CodeEditorPanel.tsx +++ b/packages/ui/src/features/code-editor/components/CodeEditorPanel.tsx @@ -1,4 +1,3 @@ -import { Check, Code, Copy, Eye } from "@phosphor-icons/react"; import { getRenderableKind } from "@posthog/core/code-editor/fileKind"; import { collapseFileState, @@ -14,11 +13,9 @@ import { parseImageDataUrl, } from "@posthog/shared"; import type { Task } from "@posthog/shared/domain-types"; -import { Box, Flex, IconButton, Text } from "@radix-ui/themes"; -import { useCallback, useMemo, useState } from "react"; +import { Box, Flex } from "@radix-ui/themes"; +import { useCallback, useMemo } from "react"; import type { Components } from "react-markdown"; -import ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; import { PanelMessage } from "../../../primitives/PanelMessage"; import { SafeImagePreview } from "../../../primitives/SafeImagePreview"; import { Tooltip } from "../../../primitives/Tooltip"; @@ -37,7 +34,9 @@ import { } from "../hooks/useFileContent"; import { useFileEnrichment } from "../hooks/useFileEnrichment"; import { CodeMirrorEditor } from "./CodeMirrorEditor"; +import { DocumentPreviewHeader } from "./DocumentPreviewHeader"; import { EnrichmentPopover } from "./EnrichmentPopover"; +import { MarkdownDocumentPreview } from "./MarkdownDocumentPreview"; import { SelectionCommentOverlay, useSelectionComposer, @@ -119,7 +118,6 @@ export function CodeEditorPanel({ const toggleKind = useFilePreviewStore((s) => s.toggleKind); const openFileInSplit = usePanelLayoutStore((s) => s.openFileInSplit); const expandToFile = useFileTreeStore((s) => s.expandToFile); - const [copied, setCopied] = useState(false); const composer = useSelectionComposer(); const handleAddSelectionToChat = useCallback( @@ -306,11 +304,6 @@ export function CodeEditorPanel({ ); if (isRenderable) { - const handleCopySource = () => { - navigator.clipboard.writeText(fileContent); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; const handleToggleRendered = () => { if (renderableKind) { toggleKind(renderableKind); @@ -319,58 +312,20 @@ export function CodeEditorPanel({ return ( - - - {filePath} - - - - - {showRendered ? : } - - - - - {copied ? : } - - - - + {!showRendered ? ( {sourceView} ) : renderableKind === "markdown" ? ( - - - {fileContent} - - + ) : ( diff --git a/packages/ui/src/features/code-editor/components/DocumentPreviewHeader.tsx b/packages/ui/src/features/code-editor/components/DocumentPreviewHeader.tsx new file mode 100644 index 0000000000..8a336f8914 --- /dev/null +++ b/packages/ui/src/features/code-editor/components/DocumentPreviewHeader.tsx @@ -0,0 +1,64 @@ +import { Check, Code, Copy, Eye } from "@phosphor-icons/react"; +import { Flex, IconButton, Text } from "@radix-ui/themes"; +import { useState } from "react"; +import { Tooltip } from "../../../primitives/Tooltip"; + +export function DocumentPreviewHeader({ + label, + content, + showRendered, + onToggleRendered, +}: { + label: string; + content: string; + showRendered: boolean; + onToggleRendered: () => void; +}) { + const [copied, setCopied] = useState(false); + + const handleCopySource = () => { + navigator.clipboard.writeText(content); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + + {label} + + + + + {showRendered ? : } + + + + + {copied ? : } + + + + + ); +} diff --git a/packages/ui/src/features/code-editor/components/MarkdownDocumentPreview.tsx b/packages/ui/src/features/code-editor/components/MarkdownDocumentPreview.tsx new file mode 100644 index 0000000000..99cd4ecab9 --- /dev/null +++ b/packages/ui/src/features/code-editor/components/MarkdownDocumentPreview.tsx @@ -0,0 +1,19 @@ +import type { Components } from "react-markdown"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +export function MarkdownDocumentPreview({ + content, + components, +}: { + content: string; + components?: Components; +}) { + return ( +
+ + {content} + +
+ ); +} diff --git a/packages/ui/src/features/sessions/components/ArtifactPreview.test.tsx b/packages/ui/src/features/sessions/components/ArtifactPreview.test.tsx index 4946346cf3..27c56eb6e8 100644 --- a/packages/ui/src/features/sessions/components/ArtifactPreview.test.tsx +++ b/packages/ui/src/features/sessions/components/ArtifactPreview.test.tsx @@ -4,7 +4,6 @@ import { ArtifactPreview } from "./ArtifactPreview"; import { artifactHtmlDocument, artifactPreviewBlob, - markdownDocument, } from "./artifactPreviewDocument"; const previewBlob = new Blob(["

Artifact content

"], { @@ -34,6 +33,12 @@ vi.mock("@tanstack/react-query", () => ({ useQuery, })); +vi.mock("../../code-editor/components/CodeMirrorEditor", () => ({ + CodeMirrorEditor: ({ content }: { content: string }) => ( +
{content}
+ ), +})); + describe("ArtifactPreview", () => { beforeEach(() => { auth.identity = "auth-1"; @@ -215,17 +220,35 @@ describe("ArtifactPreview", () => { ).toBeInTheDocument(); }); - it("renders GFM Markdown while escaping embedded HTML", () => { - const document = markdownDocument( - "# Report\n\n**Ready**\n\n| Name | Value |\n| --- | --- |\n| Cost | 12 |\n\n", + it("renders Markdown artifacts with the file preview styling", () => { + useQuery.mockReturnValue({ + data: "# Report\n\n**Ready**\n\n| Name | Value |\n| --- | --- |\n| Cost | 12 |", + isLoading: false, + isError: false, + }); + + const { container } = render( + , ); - expect(document).toContain("

Report

"); - expect(document).toContain("Ready"); - expect(document).toContain(""); - expect(document).toContain("<script>"); - expect(document).not.toContain("