-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Limit text selection to the code block #3072
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { ArrowsPointingOutIcon } from "@heroicons/react/20/solid"; | |
| import { Clipboard, ClipboardCheck } from "lucide-react"; | ||
| import type { Language, PrismTheme } from "prism-react-renderer"; | ||
| import { Highlight, Prism } from "prism-react-renderer"; | ||
| import { forwardRef, ReactNode, useCallback, useEffect, useState } from "react"; | ||
| import { forwardRef, ReactNode, useCallback, useEffect, useRef, useState } from "react"; | ||
| import { TextWrapIcon } from "~/assets/icons/TextWrapIcon"; | ||
| import { cn } from "~/utils/cn"; | ||
| import { highlightSearchText } from "~/utils/logUtils"; | ||
|
|
@@ -217,6 +217,29 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>( | |
| const [isModalOpen, setIsModalOpen] = useState(false); | ||
| const [isWrapped, setIsWrapped] = useState(false); | ||
|
|
||
| const restoreRef = useRef<(() => void) | null>(null); | ||
|
|
||
| const handleCodeMouseDown = useCallback((e: React.MouseEvent) => { | ||
| if (e.button !== 0) return; | ||
| const el = e.currentTarget as HTMLElement; | ||
| document.documentElement.style.userSelect = "none"; | ||
| el.style.userSelect = "text"; | ||
| const restore = () => { | ||
| document.documentElement.style.userSelect = ""; | ||
| el.style.userSelect = ""; | ||
| document.removeEventListener("mouseup", restore); | ||
| window.removeEventListener("blur", restore); | ||
| restoreRef.current = null; | ||
| }; | ||
| restoreRef.current = restore; | ||
| document.addEventListener("mouseup", restore, { once: true }); | ||
| window.addEventListener("blur", restore, { once: true }); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| return () => restoreRef.current?.(); | ||
| }, []); | ||
|
|
||
| const onCopied = useCallback( | ||
| (event: React.MouseEvent<HTMLButtonElement>) => { | ||
| event.preventDefault(); | ||
|
|
@@ -334,42 +357,44 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>( | |
| )} | ||
| </div> | ||
|
|
||
| {shouldHighlight ? ( | ||
| <HighlightCode | ||
| theme={theme} | ||
| code={code} | ||
| language={language} | ||
| showLineNumbers={showLineNumbers} | ||
| highlightLines={highlightLines} | ||
| maxLineWidth={maxLineWidth} | ||
| className="px-2 py-3" | ||
| preClassName="text-xs" | ||
| isWrapped={isWrapped} | ||
| searchTerm={searchTerm} | ||
| /> | ||
| ) : ( | ||
| <div | ||
| dir="ltr" | ||
| className={cn( | ||
| "px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600", | ||
| !isWrapped && "overflow-x-auto", | ||
| isWrapped && "overflow-y-auto" | ||
| )} | ||
| style={{ | ||
| maxHeight, | ||
| }} | ||
| > | ||
| <pre | ||
| <div onMouseDown={handleCodeMouseDown}> | ||
| {shouldHighlight ? ( | ||
| <HighlightCode | ||
| theme={theme} | ||
| code={code} | ||
| language={language} | ||
| showLineNumbers={showLineNumbers} | ||
| highlightLines={highlightLines} | ||
| maxLineWidth={maxLineWidth} | ||
| className="px-2 py-3" | ||
| preClassName="text-xs" | ||
| isWrapped={isWrapped} | ||
| searchTerm={searchTerm} | ||
| /> | ||
| ) : ( | ||
| <div | ||
| dir="ltr" | ||
| className={cn( | ||
| "relative mr-2 p-2 font-mono text-xs leading-relaxed", | ||
| isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words" | ||
| "px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600", | ||
| !isWrapped && "overflow-x-auto", | ||
| isWrapped && "overflow-y-auto" | ||
| )} | ||
| dir="ltr" | ||
| style={{ | ||
| maxHeight, | ||
| }} | ||
| > | ||
| {highlightSearchText(code, searchTerm)} | ||
| </pre> | ||
| </div> | ||
| )} | ||
| <pre | ||
| className={cn( | ||
| "relative mr-2 p-2 font-mono text-xs leading-relaxed", | ||
| isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words" | ||
| )} | ||
| dir="ltr" | ||
| > | ||
| {highlightSearchText(code, searchTerm)} | ||
| </pre> | ||
| </div> | ||
| )} | ||
| </div> | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </div> | ||
|
|
||
| <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}> | ||
mpcgrid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
@@ -390,28 +415,30 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>( | |
| </Button> | ||
| </DialogHeader> | ||
|
|
||
| {shouldHighlight ? ( | ||
| <HighlightCode | ||
| theme={theme} | ||
| code={code} | ||
| language={language} | ||
| showLineNumbers={showLineNumbers} | ||
| highlightLines={highlightLines} | ||
| maxLineWidth={maxLineWidth} | ||
| className="min-h-full" | ||
| preClassName="text-sm" | ||
| isWrapped={isWrapped} | ||
| /> | ||
| ) : ( | ||
| <div | ||
| dir="ltr" | ||
| className="overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" | ||
| > | ||
| <pre className="relative mr-2 p-2 font-mono text-base leading-relaxed" dir="ltr"> | ||
| {highlightSearchText(code, searchTerm)} | ||
| </pre> | ||
| </div> | ||
| )} | ||
| <div onMouseDown={handleCodeMouseDown} className="overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"> | ||
| {shouldHighlight ? ( | ||
| <HighlightCode | ||
| theme={theme} | ||
| code={code} | ||
| language={language} | ||
| showLineNumbers={showLineNumbers} | ||
| highlightLines={highlightLines} | ||
| maxLineWidth={maxLineWidth} | ||
| className="min-h-full" | ||
| preClassName="text-sm" | ||
| isWrapped={isWrapped} | ||
| /> | ||
| ) : ( | ||
| <div | ||
| dir="ltr" | ||
| className="overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" | ||
| > | ||
| <pre className="relative mr-2 p-2 font-mono text-base leading-relaxed" dir="ltr"> | ||
| {highlightSearchText(code, searchTerm)} | ||
| </pre> | ||
| </div> | ||
| )} | ||
| </div> | ||
|
Comment on lines
417
to
+441
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Modal code block has duplicated padding and scrollbar classes causing double padding In the modal's Root CauseBefore this PR, the // New outer wrapper (line 417-418)
<div onMouseDown={handleCodeMouseDown} className="overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
// HighlightCode internally renders (line 515-520):
// <div className="px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 overflow-x-auto min-h-full">Similarly, for the Impact: The modal code block will have double padding ( Prompt for agentsWas this helpful? React with π or π to provide feedback.
Comment on lines
417
to
+441
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π© Modal HighlightCode no longer receives overflow-auto directly β outer wrapper changes scroll behavior Before this PR, in the modal the Was this helpful? React with π or π to provide feedback.
Comment on lines
+418
to
+441
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double padding and nested scroll containers in the modal. The outer wrapper (line 418) applies
This results in ~ Suggested fix: move scroll/padding styling to children only- <div onMouseDown={handleCodeMouseDown} className="overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
+ <div onMouseDown={handleCodeMouseDown} className="overflow-auto">
{shouldHighlight ? (
<HighlightCode
theme={theme}This keeps the outer wrapper as a simple scroll host (or just the mousedown handler, mirroring the main view), letting π€ Prompt for AI Agents |
||
| </DialogContent> | ||
| </Dialog> | ||
| </> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global
userSelect = "none"can stick ifmouseupis missed.If the user switches away (e.g., Alt-Tab, context menu, or the component unmounts while the mouse is held),
mouseupnever fires, leavingdocument.documentElement.style.userSelect = "none"permanently. Two improvements:{ once: true }instead of manually removing the listener β simpler and avoids forgetting removal.windowblurlistener as a safety net to callrestore.useEffect(or store a ref torestore) so unmounting also cleans up.Suggested improvement
const handleCodeMouseDown = useCallback((e: React.MouseEvent) => { if (e.button !== 0) return; const el = e.currentTarget as HTMLElement; document.documentElement.style.userSelect = "none"; el.style.userSelect = "text"; const restore = () => { document.documentElement.style.userSelect = ""; el.style.userSelect = ""; - document.removeEventListener("mouseup", restore); + document.removeEventListener("mouseup", restore); + window.removeEventListener("blur", restore); }; - document.addEventListener("mouseup", restore); + document.addEventListener("mouseup", restore, { once: true }); + window.addEventListener("blur", restore, { once: true }); }, []);π€ Prompt for AI Agents
β Addressed in commit 99abbd8