| ... | @@ -22,7 +22,7 @@ import ChatMarkdown from "~/components/ChatMarkdown"; | ... | @@ -22,7 +22,7 @@ import ChatMarkdown from "~/components/ChatMarkdown"; |
| 22 | import { OpenInPicker } from "~/components/chat/OpenInPicker"; | 22 | import { OpenInPicker } from "~/components/chat/OpenInPicker"; |
| 23 | import { useClientSettings } from "~/hooks/useSettings"; | 23 | import { useClientSettings } from "~/hooks/useSettings"; |
| 24 | import { useTheme } from "~/hooks/useTheme"; | 24 | import { useTheme } from "~/hooks/useTheme"; |
| 25 | import { getLocalStorageItem, setLocalStorageItem } from "~/hooks/useLocalStorage"; | 25 | import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "~/hooks/useLocalStorage"; |
| 26 | import { resolveDiffThemeName } from "~/lib/diffRendering"; | 26 | import { resolveDiffThemeName } from "~/lib/diffRendering"; |
| 27 | import { cn } from "~/lib/utils"; | 27 | import { cn } from "~/lib/utils"; |
| 28 | import { isPreviewSupportedInRuntime } from "~/previewStateStore"; | 28 | import { isPreviewSupportedInRuntime } from "~/previewStateStore"; |
| ... | @@ -79,6 +79,7 @@ interface FilePreviewPanelProps { | ... | @@ -79,6 +79,7 @@ interface FilePreviewPanelProps { |
| 79 | } | 79 | } |
| 80 | | 80 | |
| 81 | const FILE_EXPLORER_STORAGE_KEY = "t3code.fileExplorerOpen"; | 81 | const FILE_EXPLORER_STORAGE_KEY = "t3code.fileExplorerOpen"; |
| | 82 | const RENDER_MARKDOWN_STORAGE_KEY = "t3code.renderMarkdown"; |
| 82 | const FILE_SAVE_DEBOUNCE_MS = 500; | 83 | const FILE_SAVE_DEBOUNCE_MS = 500; |
| 83 | const FILE_LINK_REVEAL_ATTRIBUTE = "data-file-link-reveal"; | 84 | const FILE_LINK_REVEAL_ATTRIBUTE = "data-file-link-reveal"; |
| 84 | const FILE_LINK_REVEAL_UNSAFE_CSS = ` | 85 | const FILE_LINK_REVEAL_UNSAFE_CSS = ` |
| ... | @@ -680,16 +681,27 @@ export default function FilePreviewPanel({ | ... | @@ -680,16 +681,27 @@ export default function FilePreviewPanel({ |
| 680 | const isImage = relativePath !== null && isWorkspaceImagePreviewPath(relativePath); | 681 | const isImage = relativePath !== null && isWorkspaceImagePreviewPath(relativePath); |
| 681 | const file = useProjectFileQuery(environmentId, cwd, relativePath, !isImage); | 682 | const file = useProjectFileQuery(environmentId, cwd, relativePath, !isImage); |
| 682 | const [explorerOpen, setExplorerOpen] = useState(initialExplorerOpen); | 683 | const [explorerOpen, setExplorerOpen] = useState(initialExplorerOpen); |
| 683 | const [markdownView, setMarkdownView] = useState<{ | 684 | // Reading markdown rendered is a preference, not a property of one file. Keeping |
| 684 | path: string | null; | 685 | // it on the panel meant a thread switch dropped it and forced source back. |
| 685 | revealRequestId: number | null; | 686 | const [renderMarkdownPreferred, setRenderMarkdownPreferred] = useLocalStorage( |
| 686 | }>({ path: null, revealRequestId: null }); | 687 | RENDER_MARKDOWN_STORAGE_KEY, |
| | 688 | false, |
| | 689 | Schema.Boolean, |
| | 690 | ); |
| | 691 | // Paired with the path on purpose: each file surface counts its reveals from |
| | 692 | // one, so a bare id would let a dismissed reveal on one file swallow the first |
| | 693 | // reveal on the next. |
| | 694 | const [handledReveal, setHandledReveal] = useState<{ path: string; requestId: number } | null>( |
| | 695 | null, |
| | 696 | ); |
| 687 | const breadcrumbRef = useRef<HTMLDivElement>(null); | 697 | const breadcrumbRef = useRef<HTMLDivElement>(null); |
| 688 | const isMarkdown = relativePath ? isMarkdownPreviewFile(relativePath) : false; | 698 | const isMarkdown = relativePath ? isMarkdownPreviewFile(relativePath) : false; |
| | 699 | // A reveal still wins over the preference: the line only exists in the source. |
| 689 | const renderMarkdown = | 700 | const renderMarkdown = |
| 690 | isMarkdown && | 701 | isMarkdown && |
| 691 | markdownView.path === relativePath && | 702 | renderMarkdownPreferred && |
| 692 | (revealLine === null || markdownView.revealRequestId === revealRequestId); | 703 | (revealLine === null || |
| | 704 | (handledReveal?.path === relativePath && handledReveal.requestId === revealRequestId)); |
| 693 | const canOpenInBrowser = | 705 | const canOpenInBrowser = |
| 694 | relativePath !== null && isPreviewSupportedInRuntime() && isBrowserPreviewFile(relativePath); | 706 | relativePath !== null && isPreviewSupportedInRuntime() && isBrowserPreviewFile(relativePath); |
| 695 | const absolutePath = relativePath ? resolvePathLinkTarget(relativePath, cwd) : null; | 707 | const absolutePath = relativePath ? resolvePathLinkTarget(relativePath, cwd) : null; |
| ... | @@ -796,10 +808,12 @@ export default function FilePreviewPanel({ | ... | @@ -796,10 +808,12 @@ export default function FilePreviewPanel({ |
| 796 | className="shrink-0" | 808 | className="shrink-0" |
| 797 | pressed={renderMarkdown} | 809 | pressed={renderMarkdown} |
| 798 | onPressedChange={(pressed) => { | 810 | onPressedChange={(pressed) => { |
| 799 | setMarkdownView({ | 811 | setRenderMarkdownPreferred(pressed); |
| 800 | path: pressed ? relativePath : null, | 812 | setHandledReveal( |
| 801 | revealRequestId: pressed ? revealRequestId : null, | 813 | pressed && relativePath !== null |
| 802 | }); | 814 | ? { path: relativePath, requestId: revealRequestId } |
| | 815 | : null, |
| | 816 | ); |
| 803 | }} | 817 | }} |
| 804 | aria-label={renderMarkdown ? "Show markdown source" : "Show rendered markdown"} | 818 | aria-label={renderMarkdown ? "Show markdown source" : "Show rendered markdown"} |
| 805 | variant="ghost" | 819 | variant="ghost" |