From 212818edb6f7f8decb656ffc3f959cf4816119cc Mon Sep 17 00:00:00 2001 From: code-qtzl Date: Fri, 1 May 2026 12:51:13 -0500 Subject: [PATCH 1/2] fix(packages/components/src): MDX indentation Co-authored-by: Copilot --- .../code-block/code-block.stories.tsx | 40 +++++++++++++++++++ packages/components/src/utils/shiki/lib.ts | 32 ++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/components/src/components/code-block/code-block.stories.tsx b/packages/components/src/components/code-block/code-block.stories.tsx index b7dc7ca3..bb303593 100644 --- a/packages/components/src/components/code-block/code-block.stories.tsx +++ b/packages/components/src/components/code-block/code-block.stories.tsx @@ -464,3 +464,43 @@ export const WithCustomClassName: Story = { ), }; + +export const MDXIndents: Story = { + render: () => ( + + {`import { + a, + b, + } from 'pkg'; + + async function main() { + console.log('hello'); + }`} + + ), +}; + +export const MDXIndentsDeeplyNested: Story = { + render: () => ( + + {`function outer() { + function middle() { + function inner() { + function deepest() { + if (true) { + for (let i = 0; i < 10; i++) { + while (i > 0) { + return 42; + } + } + } + } + return deepest(); + } + return inner(); + } + return middle(); + }`} + + ), +}; diff --git a/packages/components/src/utils/shiki/lib.ts b/packages/components/src/utils/shiki/lib.ts index 99c9bab0..64e6f4d7 100644 --- a/packages/components/src/utils/shiki/lib.ts +++ b/packages/components/src/utils/shiki/lib.ts @@ -3,6 +3,8 @@ import { type ReactNode, useMemo } from "react"; import { getNodeText } from "@/utils/get-node-text"; import { SHIKI_CLASSNAME } from "@/utils/shiki/constants"; +const lineIndentRegex = /^( *)/; + function findShikiClassName(children: unknown): boolean { if (!children || typeof children !== "object") { return false; @@ -37,6 +39,34 @@ function findShikiClassName(children: unknown): boolean { return false; } +function dedentCode(code: string): string { + const lines = code.split("\n"); + if (lines.length <= 1) { + return code; + } + + const relevantLines = lines.slice(1).filter((line) => line.trim() !== ""); + if (relevantLines.length === 0) { + return code; + } + + const minIndent = Math.min( + ...relevantLines.map((line) => { + const match = line.match(lineIndentRegex); + return match ? match[1].length : 0; + }) + ); + + if (minIndent === 0) { + return code; + } + + return [ + lines[0], + ...lines.slice(1).map((line) => line.slice(minIndent)), + ].join("\n"); +} + function getCodeString( children: ReactNode, className?: string, @@ -50,7 +80,7 @@ function getCodeString( const codeString = getNodeText(children); - return codeString; + return dedentCode(codeString); } function calculateCodeLinesFromHtml(html: string | undefined): number { From 728aecfc42051b23c05328563a45012b14eac130 Mon Sep 17 00:00:00 2001 From: code-qtzl Date: Fri, 1 May 2026 14:22:48 -0500 Subject: [PATCH 2/2] fix(packages/components/src): fix dedent and copy for template-literal code blocks --- .../src/components/code-block/code-block.tsx | 4 +- .../src/components/code-group/code-group.tsx | 8 +++- packages/components/src/utils/shiki/lib.ts | 43 ++++++++++++++----- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/components/src/components/code-block/code-block.tsx b/packages/components/src/components/code-block/code-block.tsx index 61ac6772..047d1e0c 100644 --- a/packages/components/src/components/code-block/code-block.tsx +++ b/packages/components/src/components/code-block/code-block.tsx @@ -2,8 +2,8 @@ import type { ReactNode, RefObject } from "react"; import { Classes } from "@/constants/selectors"; import { cn } from "@/utils/cn"; -import { getNodeText } from "@/utils/get-node-text"; import type { CodeBlockTheme, CodeStyling } from "@/utils/shiki/code-styling"; +import { getCodeString } from "@/utils/shiki/lib"; import { BaseCodeBlock } from "./base-code-block"; import { CodeHeader } from "./code-header"; @@ -98,7 +98,7 @@ const CodeBlock = function CodeBlock(params: CodeBlockProps) { copyButtonProps, } = params; - const codeString = getNodeText(children); + const codeString = getCodeString(children, className, true); const hasGrayBackgroundContainer = !!filename || !!icon; return ( diff --git a/packages/components/src/components/code-group/code-group.tsx b/packages/components/src/components/code-group/code-group.tsx index 6f285ccd..c41ce8ad 100644 --- a/packages/components/src/components/code-group/code-group.tsx +++ b/packages/components/src/components/code-group/code-group.tsx @@ -18,8 +18,8 @@ import { import { Icon as ComponentIcon } from "@/components/icon"; import { Classes } from "@/constants/selectors"; import { cn } from "@/utils/cn"; -import { getNodeText } from "@/utils/get-node-text"; import type { CodeBlockTheme, CodeStyling } from "@/utils/shiki/code-styling"; +import { getCodeString } from "@/utils/shiki/lib"; import { LanguageDropdown } from "./language-dropdown"; @@ -220,7 +220,11 @@ const CodeGroup = ({ {feedbackButton && feedbackButton} {askAiButton && askAiButton} diff --git a/packages/components/src/utils/shiki/lib.ts b/packages/components/src/utils/shiki/lib.ts index 64e6f4d7..0accfe7d 100644 --- a/packages/components/src/utils/shiki/lib.ts +++ b/packages/components/src/utils/shiki/lib.ts @@ -4,6 +4,12 @@ import { getNodeText } from "@/utils/get-node-text"; import { SHIKI_CLASSNAME } from "@/utils/shiki/constants"; const lineIndentRegex = /^( *)/; +const closingStructureRegex = /^([}\])]|<\/)/; + +function getIndent(line: string): number { + const match = line.match(lineIndentRegex); + return match ? match[1].length : 0; +} function findShikiClassName(children: unknown): boolean { if (!children || typeof children !== "object") { @@ -45,26 +51,41 @@ function dedentCode(code: string): string { return code; } - const relevantLines = lines.slice(1).filter((line) => line.trim() !== ""); + const relevantLines = lines.filter((line) => line.trim() !== ""); if (relevantLines.length === 0) { return code; } - const minIndent = Math.min( - ...relevantLines.map((line) => { - const match = line.match(lineIndentRegex); - return match ? match[1].length : 0; - }) - ); + const firstLine = relevantLines[0]; + const lastLine = relevantLines.at(-1) ?? firstLine; + const firstIndent = getIndent(firstLine); + const lastIndent = getIndent(lastLine); + const isTemplatePolluted = + firstIndent < lastIndent && closingStructureRegex.test(lastLine.trim()); + + if (isTemplatePolluted) { + const firstNonEmptyIndex = lines.findIndex((line) => line.trim() !== ""); + const tail = relevantLines.slice(1); + if (tail.length === 0) { + return code; + } + const minIndent = Math.min(...tail.map(getIndent)); + if (minIndent === 0) { + return code; + } + return lines + .map((line, i) => + i <= firstNonEmptyIndex ? line : line.slice(minIndent) + ) + .join("\n"); + } + const minIndent = Math.min(...relevantLines.map(getIndent)); if (minIndent === 0) { return code; } - return [ - lines[0], - ...lines.slice(1).map((line) => line.slice(minIndent)), - ].join("\n"); + return lines.map((line) => line.slice(minIndent)).join("\n"); } function getCodeString(