/* Copyright 2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React, { useState } from "react"; import classNames from "classnames"; import { TooltipProvider } from "@vector-im/compound-web"; import { useSettingValue } from "../../../hooks/useSettings.ts"; import { CopyTextButton } from "../elements/CopyableText.tsx"; const MAX_HIGHLIGHT_LENGTH = 4096; const MAX_LINES_BEFORE_COLLAPSE = 5; interface Props { children: HTMLElement; onHeightChanged?(): void; } const ExpandCollapseButton: React.FC<{ expanded: boolean; onClick(): void; }> = ({ expanded, onClick }) => { return ( ); }; const CodeBlock: React.FC = ({ children, onHeightChanged }) => { const enableSyntaxHighlightLanguageDetection = useSettingValue("enableSyntaxHighlightLanguageDetection"); const showCodeLineNumbers = useSettingValue("showCodeLineNumbers"); const expandCodeByDefault = useSettingValue("expandCodeByDefault"); const [expanded, setExpanded] = useState(expandCodeByDefault); let expandCollapseButton: JSX.Element | undefined; if (children.textContent && children.textContent.split("\n").length >= MAX_LINES_BEFORE_COLLAPSE) { expandCollapseButton = ( { setExpanded(!expanded); // By expanding/collapsing we changed the height, therefore we call this onHeightChanged?.(); }} /> ); } let lineNumbers: JSX.Element | undefined; if (showCodeLineNumbers) { // Calculate number of lines in pre const number = children.innerHTML.replace(/\n(<\/code>)?$/, "").split(/\n/).length; // Iterate through lines starting with 1 (number of the first line is 1) lineNumbers = ( {Array.from({ length: number }, (_, i) => i + 1).map((i) => ( {i} ))} ); } async function highlightCode(div: HTMLElement | null): Promise { const code = div?.getElementsByTagName("code")[0]; if (!code) return; const { default: highlight } = await import("highlight.js"); if (code.textContent && code.textContent.length > MAX_HIGHLIGHT_LENGTH) { console.log( `Code block is bigger than highlight limit (${code.textContent.length} > ${MAX_HIGHLIGHT_LENGTH}): not highlighting`, ); return; } let advertisedLang: string | undefined; for (const cl of code.className.split(/\s+/)) { if (cl.startsWith("language-")) { const maybeLang = cl.split("-", 2)[1]; if (highlight.getLanguage(maybeLang)) { advertisedLang = maybeLang; break; } } } if (advertisedLang) { // If the code says what language it is, highlight it in that language // We don't use highlightElement here because we can't force language detection // off. It should use the one we've found in the CSS class but we'd rather pass // it in explicitly to make sure. code.innerHTML = highlight.highlight(code.textContent ?? "", { language: advertisedLang }).value; } else if (enableSyntaxHighlightLanguageDetection) { // User has language detection enabled, so highlight the block with auto-highlighting enabled. // We pass highlightjs the text to highlight rather than letting it // work on the DOM with highlightElement because that also adds CSS // classes to the pre/code element that we don't want (the CSS // conflicts with our own). code.innerHTML = highlight.highlightAuto(code.textContent ?? "").value; } } return (
                {lineNumbers}
                
{expandCollapseButton} children.getElementsByTagName("code")[0]?.textContent ?? null} className={classNames("mx_EventTile_button mx_EventTile_copyButton", { mx_EventTile_buttonBottom: !!expandCollapseButton, })} />
); }; export default CodeBlock;