mirror of
https://github.com/itplr-kosit/validator.git
synced 2026-05-25 16:55:39 +00:00
119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import type { PrismTheme, Language } from "prism-react-renderer";
|
|
import Highlight, { defaultProps } from "prism-react-renderer";
|
|
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
import clsx from "clsx";
|
|
import downloadFile from "js-file-download";
|
|
import styles from "./Codeblock.module.css";
|
|
|
|
type ThemeValue = "light" | "dark";
|
|
|
|
const getTheme = () =>
|
|
(document.documentElement.dataset.theme || "light") as ThemeValue;
|
|
|
|
function useGlobalTheme() {
|
|
const [theme, setTheme] = useState<ThemeValue>(getTheme);
|
|
useEffect(() => {
|
|
const mo = new MutationObserver(() => {
|
|
setTheme(getTheme());
|
|
});
|
|
mo.observe(document.documentElement, {
|
|
subtree: false,
|
|
attributeFilter: ["data-theme"],
|
|
});
|
|
return () => mo.disconnect();
|
|
});
|
|
return theme;
|
|
}
|
|
|
|
function Codeblock({
|
|
children,
|
|
language = "markup",
|
|
enableCopy = false,
|
|
download,
|
|
}: {
|
|
children: string;
|
|
language?: Language;
|
|
enableCopy?: boolean;
|
|
download?: { fileName: string; mime: string };
|
|
}): JSX.Element {
|
|
const { siteConfig } = useDocusaurusContext();
|
|
const theme = useGlobalTheme();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const codeThemeLight = (siteConfig.themeConfig.prism as any)
|
|
.theme as PrismTheme;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const codeThemeDark = (siteConfig.themeConfig.prism as any)
|
|
.darkTheme as PrismTheme;
|
|
const codeTheme = theme === "light" ? codeThemeLight : codeThemeDark;
|
|
|
|
const handleCopy = async () => {
|
|
try {
|
|
navigator.clipboard.writeText(children);
|
|
} catch {
|
|
// Copying did unfortunately not work, but we'll not crash the app
|
|
// beacause of that...
|
|
}
|
|
};
|
|
const handleDownload = () => {
|
|
if (!download || !download.fileName || !download.mime) return;
|
|
downloadFile(children, download.fileName, download.mime);
|
|
};
|
|
|
|
return (
|
|
<div className={styles.wrapper}>
|
|
<Highlight
|
|
{...defaultProps}
|
|
code={children}
|
|
language={language}
|
|
theme={codeTheme}
|
|
>
|
|
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
|
<pre className={clsx(className, styles.codeblock)} style={style}>
|
|
{tokens.map((line, i) => (
|
|
// eslint-disable-next-line react/jsx-key
|
|
<div {...getLineProps({ line, key: i })}>
|
|
{line.map((token, key) => (
|
|
// eslint-disable-next-line react/jsx-key
|
|
<span {...getTokenProps({ token, key })} />
|
|
))}
|
|
</div>
|
|
))}
|
|
</pre>
|
|
)}
|
|
</Highlight>
|
|
{(enableCopy || download) && (
|
|
<div className={styles.buttonWrapper}>
|
|
{enableCopy && (
|
|
<button
|
|
className={styles.button}
|
|
type="button"
|
|
aria-label="Copy content"
|
|
title="Copy content"
|
|
onClick={handleCopy}
|
|
>
|
|
<svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
{download && (
|
|
<button
|
|
className={styles.button}
|
|
type="button"
|
|
aria-label="Download content as file"
|
|
title="Download content as file"
|
|
onClick={handleDownload}
|
|
>
|
|
<svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Codeblock;
|