You've already forked element-web
mirror of
https://github.com/element-hq/element-web.git
synced 2025-08-09 14:42:51 +03:00
* Harden Settings using mapped types Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix issues found during hardening Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove oidc native flow stale key Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
137 lines
5.1 KiB
TypeScript
137 lines
5.1 KiB
TypeScript
/*
|
|
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 (
|
|
<span
|
|
className={classNames("mx_EventTile_button", {
|
|
mx_EventTile_expandButton: !expanded,
|
|
mx_EventTile_collapseButton: expanded,
|
|
})}
|
|
onClick={onClick}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const CodeBlock: React.FC<Props> = ({ 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 = (
|
|
<ExpandCollapseButton
|
|
expanded={expanded}
|
|
onClick={() => {
|
|
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 = (
|
|
<span className="mx_EventTile_lineNumbers">
|
|
{Array.from({ length: number }, (_, i) => i + 1).map((i) => (
|
|
<span key={i}>{i}</span>
|
|
))}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
async function highlightCode(div: HTMLElement | null): Promise<void> {
|
|
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 (
|
|
<TooltipProvider>
|
|
<pre
|
|
className={classNames({
|
|
mx_EventTile_collapsedCodeBlock: !expanded,
|
|
})}
|
|
>
|
|
{lineNumbers}
|
|
<div
|
|
style={{ display: "contents" }}
|
|
dangerouslySetInnerHTML={{ __html: children.innerHTML }}
|
|
ref={highlightCode}
|
|
/>
|
|
</pre>
|
|
{expandCollapseButton}
|
|
<CopyTextButton
|
|
getTextToCopy={() => children.getElementsByTagName("code")[0]?.textContent ?? null}
|
|
className={classNames("mx_EventTile_button mx_EventTile_copyButton", {
|
|
mx_EventTile_buttonBottom: !!expandCollapseButton,
|
|
})}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
};
|
|
|
|
export default CodeBlock;
|