diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index b5c117ffc8..10b61dd4a9 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -95,6 +95,28 @@ const CodeBlockButton = ({ type MdxContent = () => ReactNode; +/** + * Reads the source of a rendered Docusaurus code block back out of the DOM. + * + * Each highlighted line is rendered as an element with the `token-line` class. + * Prism splits the source on newlines, so the breaks between lines are not part + * of the token text and have to be added back when joining. + */ +function getCodeBlockText(codeBlock: HTMLElement) { + const lines = codeBlock.querySelectorAll('.token-line'); + + // Not a highlighted code block, so there are no lines to join. + if (lines.length === 0) { + return codeBlock.textContent; + } + + // Prism gives blank lines a synthetic '\n' token. Docusaurus currently blanks + // those out before rendering, but strip them in case that changes. + return Array.from(lines) + .map((line) => line.textContent.replace(/\n/g, '')) + .join('\n'); +} + /** * The advanced configuration of options when creating a * playground example with multiple files for a single usage target @@ -582,8 +604,7 @@ export default function Playground({ version, }; - // using outerText will preserve line breaks for formatting in StackBlitz editor - const codeBlock = codeRef.current?.querySelector('code')?.outerText ?? ''; + const codeBlock = getCodeBlockText(codeRef.current!.querySelector('code')!); if (hasUsageTargetOptions) { editorOptions.files = Object.keys((codeSnippets[usageTarget] ?? {}) as Record) @@ -591,24 +612,8 @@ export default function Playground({ const codeBlock = hostRef.current!.querySelector( `#${getCodeSnippetId(usageTarget, fileName)} code` ); - let code = codeBlock?.outerText ?? ''; - - if (code.trim().length === 0) { - /** - * Safari has an issue where accessing the `outerText` on a non-visible - * DOM element results in a string with only whitespace. To work around this, - * we create a clone of the element, not attached to the DOM, and parse - * the outerText from that. - * - * Only in Safari does this persist whitespace & line breaks, so we - * explicitly check for when the code is empty to use this workaround. - */ - const el = document.createElement('div'); - el.innerHTML = codeBlock?.innerHTML ?? ''; - code = el.outerText; - } return { - [fileName]: code, + [fileName]: getCodeBlockText(codeBlock!), }; }) .reduce((acc, curr) => ({ ...acc, ...curr }), {});