feat(messages): code blocks get a Wrap toggle next to Copy (#131)
Verified upstream first: the Copy chip (with a 'Copied' state) already exists on every code block. Added the missing half: a Wrap chip that switches long lines to pre-wrap (default stays no-wrap + horizontal scroll); the choice is remembered for the session, not persisted. Markdown/HTML output, layout widths and the syntax theme are untouched. Verified headless: scrollWidth 1016 → 612 px with wrapping on, 'Copied' state on copy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -333,13 +333,10 @@ const extractTextFromChildren = (nodes: ChildNode[]): string => {
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CodeBlock({
|
export // Session memory for the code-block Wrap toggle (#131).
|
||||||
children,
|
const codeWrapPreference = { value: false };
|
||||||
opts,
|
|
||||||
}: {
|
function CodeBlock({ children, opts }: { children: ChildNode[]; opts: HTMLReactParserOptions }) {
|
||||||
children: ChildNode[];
|
|
||||||
opts: HTMLReactParserOptions;
|
|
||||||
}) {
|
|
||||||
const code = children[0];
|
const code = children[0];
|
||||||
const attribs = code instanceof Element && code.name === 'code' ? code.attribs : undefined;
|
const attribs = code instanceof Element && code.name === 'code' ? code.attribs : undefined;
|
||||||
const languageClass = attribs?.class;
|
const languageClass = attribs?.class;
|
||||||
@@ -357,6 +354,13 @@ export function CodeBlock({
|
|||||||
|
|
||||||
const [expanded, setExpand] = useState(false);
|
const [expanded, setExpand] = useState(false);
|
||||||
const [copied, setCopied] = useTimeoutToggle();
|
const [copied, setCopied] = useTimeoutToggle();
|
||||||
|
// [Gitea #131] Line-wrap toggle for long lines; the choice is remembered for
|
||||||
|
// the session (module-level), not persisted.
|
||||||
|
const [wrap, setWrap] = useState(codeWrapPreference.value);
|
||||||
|
const toggleWrap = () => {
|
||||||
|
codeWrapPreference.value = !wrap;
|
||||||
|
setWrap(!wrap);
|
||||||
|
};
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
copyToClipboard(extractTextFromChildren(children));
|
copyToClipboard(extractTextFromChildren(children));
|
||||||
@@ -376,6 +380,16 @@ export function CodeBlock({
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No" gap="200">
|
<Box shrink="No" gap="200">
|
||||||
|
<Chip
|
||||||
|
variant={wrap ? 'Primary' : 'Surface'}
|
||||||
|
fill="None"
|
||||||
|
radii="Pill"
|
||||||
|
onClick={toggleWrap}
|
||||||
|
aria-pressed={wrap}
|
||||||
|
title={wrap ? 'Scroll long lines instead of wrapping' : 'Wrap long lines'}
|
||||||
|
>
|
||||||
|
<Text size="B300">Wrap</Text>
|
||||||
|
</Chip>
|
||||||
<Chip
|
<Chip
|
||||||
variant={copied ? 'Success' : 'Surface'}
|
variant={copied ? 'Success' : 'Surface'}
|
||||||
fill="None"
|
fill="None"
|
||||||
@@ -410,7 +424,11 @@ export function CodeBlock({
|
|||||||
visibility="Hover"
|
visibility="Hover"
|
||||||
hideTrack
|
hideTrack
|
||||||
>
|
>
|
||||||
<div id="code-block-content" className={css.CodeBlockInternal}>
|
<div
|
||||||
|
id="code-block-content"
|
||||||
|
className={css.CodeBlockInternal}
|
||||||
|
style={wrap ? { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } : undefined}
|
||||||
|
>
|
||||||
{domToReact(children as unknown as DOMNode[], opts)}
|
{domToReact(children as unknown as DOMNode[], opts)}
|
||||||
</div>
|
</div>
|
||||||
</Scroll>
|
</Scroll>
|
||||||
|
|||||||
Reference in New Issue
Block a user