slate/components/system/CodeBlock.js

415 lines
9.5 KiB
JavaScript
Raw Normal View History

import * as React from "react";
import * as Constants from "~/common/constants";
2021-03-25 07:28:14 +03:00
import * as SVG from "~/common/svg";
2021-03-22 21:17:42 +03:00
import Highlight, { defaultProps } from "prism-react-renderer";
2020-11-30 08:24:22 +03:00
import { css } from "@emotion/react";
// TODO:
// Refactor to https://github.com/FormidableLabs/prism-react-renderer
2021-03-22 21:17:42 +03:00
const customTheme = {
plain: {
2021-03-22 21:19:34 +03:00
backgroundColor: "#1f212a",
2021-03-22 21:17:42 +03:00
color: "#6f7278",
},
styles: [
{
types: ["comment", "prolog", "doctype", "cdata"],
style: {
2021-03-22 21:19:34 +03:00
color: "#6c6783eeebff",
2021-03-22 21:17:42 +03:00
},
},
{
types: ["punctuation"],
style: {
color: "#adadad",
},
},
{
types: ["namespace"],
style: {
opacity: 0.7,
},
},
{
types: ["tag", "operator", "number"],
style: {
color: "#e09142",
},
},
{
types: ["operator"],
style: {
color: "#adadad",
},
},
{
types: ["property", "function"],
style: {
2021-03-22 21:19:34 +03:00
color: "#eeebff",
2021-03-22 21:17:42 +03:00
},
},
{
2021-03-22 21:19:34 +03:00
types: ["tag-id", "selector", "atrul-id"],
2021-03-22 21:17:42 +03:00
style: {
color: "#eeebff",
},
},
{
types: ["attr-name"],
style: {
color: "#c4b9fe",
},
},
{
types: [
"entity",
"attr-value",
"keyword",
"control",
"directive",
"unit",
"statement",
"at-rule",
"placeholder",
"variable",
],
style: {
color: "#99ceff",
},
},
2021-03-22 21:19:34 +03:00
{
2021-03-25 07:28:14 +03:00
types: ["boolean", "string", "url", "regex"],
2021-03-22 21:19:34 +03:00
style: {
color: "#b5ffff",
},
},
2021-03-22 21:17:42 +03:00
{
types: ["deleted"],
style: {
textDecorationLine: "line-through",
},
},
{
types: ["inserted"],
style: {
textDecorationLine: "underline",
},
},
{
types: ["italic"],
style: {
fontStyle: "italic",
},
},
{
types: ["important", "bold"],
style: {
fontWeight: "bold",
},
},
{
types: ["important"],
style: {
color: "#c4b9fe",
},
},
],
};
2020-07-09 05:57:21 +03:00
const STYLES_CODE_BLOCK = css`
box-sizing: border-box;
font-family: ${Constants.font.code};
2021-01-11 03:59:36 +03:00
background-color: #1f212a;
2020-07-09 06:17:25 +03:00
color: ${Constants.system.white};
border-color: ${Constants.system.yellow};
font-size: 12px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
border-radius: 4px;
2021-03-25 07:28:14 +03:00
padding: 20px 24px;
* {
white-space: pre-wrap;
overflow-wrap: break-word;
::-webkit-scrollbar {
-webkit-appearance: none;
width: 0;
height: 0;
}
}
`;
2021-03-27 01:06:52 +03:00
const STYLES_CODE_BODY = css`
color: #666;
font-family: ${Constants.font.code};
flex-shrink: 0;
min-width: 32px;
user-select: none;
`;
const STYLES_CODE = css`
box-sizing: border-box;
user-select: text;
font-family: ${Constants.font.code};
color: ${Constants.system.gray};
width: 100%;
flex-grow: 1;
overflow-x: auto;
`;
2021-03-25 22:05:09 +03:00
const STYLES_CODE_BLOCK_PLAIN = css`
box-sizing: border-box;
2021-03-25 07:28:14 +03:00
font-family: ${Constants.font.code};
2021-07-07 22:58:14 +03:00
background-color: ${Constants.semantic.bgLight};
2021-03-25 07:28:14 +03:00
color: ${Constants.system.black};
border-color: ${Constants.system.yellow};
font-size: 12px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
border-radius: 4px;
padding: 20px 24px;
* {
white-space: pre-wrap;
overflow-wrap: break-word;
::-webkit-scrollbar {
-webkit-appearance: none;
width: 0;
height: 0;
}
}
`;
2020-07-09 02:55:46 +03:00
2021-03-25 07:28:14 +03:00
const STYLES_LINE_NUMBER = css`
text-align: right;
flex-grow: 0;
flex-shrink: 0;
width: 32px;
padding-right: 16px;
`;
const STYLES_LINE = css`
display: flex;
justify-content: space-between;
`;
const STYLES_TOPBAR = css`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 12px 20px 16px 20px;
background: ${Constants.system.black};
border-top-right-radius: 4px;
border-top-left-radius: 4px;
margin-bottom: -4px;
box-sizing: border-box;
`;
2021-03-25 22:05:09 +03:00
const STYLES_TOPBAR_PLAIN = css`
2021-03-25 07:28:14 +03:00
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 12px 20px 16px 20px;
background: ${Constants.system.green};
border-top-right-radius: 4px;
border-top-left-radius: 4px;
margin-bottom: -4px;
box-sizing: border-box;
`;
const STYLES_TOPBAR_TITLE = css`
text-transform: uppercase;
2021-07-07 22:24:01 +03:00
color: ${Constants.semantic.textGray};
2021-03-25 07:28:14 +03:00
font-size: ${Constants.typescale.lvlN1};
font-family: ${Constants.font.medium};
user-select: none;
pointer-events: none;
`;
const STYLES_LANGSWITCHER = css`
margin-left: auto;
display: flex;
align-items: center;
flex-shrink: 0;
2021-07-07 22:24:01 +03:00
color: ${Constants.semantic.textGray};
2021-03-25 07:28:14 +03:00
font-size: ${Constants.typescale.lvlN1};
margin-right: 12px;
2021-07-07 21:56:04 +03:00
border: 1px solid ${Constants.system.grayDark4};
2021-03-25 07:28:14 +03:00
border-radius: 4px;
cursor: pointer;
2021-03-25 09:38:02 +03:00
user-select: none;
2021-03-25 07:28:14 +03:00
`;
const STYLES_LANG = css`
display: flex;
align-items: center;
flex-shrink: 0;
2021-07-07 22:24:01 +03:00
color: ${Constants.semantic.textGray};
2021-03-25 07:28:14 +03:00
border-radius: 3px;
padding: 4px 8px;
`;
const STYLES_LANG_SELECTED = css`
display: flex;
align-items: center;
flex-shrink: 0;
color: ${Constants.system.white};
2021-07-07 21:56:04 +03:00
background: ${Constants.system.grayDark4};
2021-03-25 07:28:14 +03:00
border-radius: 3px;
padding: 4px 8px;
`;
const STYLES_COPY_BUTTON = css`
display: flex;
align-items: center;
2021-07-07 22:24:01 +03:00
color: ${Constants.semantic.textGray};
2021-03-25 07:28:14 +03:00
cursor: pointer;
padding: 4px;
border-radius: 4px;
2021-03-25 08:37:41 +03:00
position: relative;
2021-03-25 07:28:14 +03:00
:hover {
2021-07-07 21:56:04 +03:00
background: ${Constants.system.grayDark4};
2021-03-25 07:28:14 +03:00
color: ${Constants.system.white};
}
`;
const STYLES_HIDDEN = css`
position: absolute;
opacity: 0;
pointer-events: none;
`;
2021-03-25 08:37:41 +03:00
const STYLES_TOOLTIP = css`
position: absolute;
top: 32px;
left: 0;
z-index: ${Constants.zindex.tooltip};
padding: 12px;
2021-07-07 22:58:14 +03:00
background-color: ${Constants.semantic.bgBlurDark6};
2021-03-25 08:37:41 +03:00
border-radius: 4px;
color: ${Constants.system.white};
font-size: ${Constants.typescale.lvlN1};
font-family: ${Constants.font.text};
min-width: 88px;
@supports ((-webkit-backdrop-filter: blur(15px)) or (backdrop-filter: blur(15px))) {
-webkit-backdrop-filter: blur(15px);
backdrop-filter: blur(15px);
}
`;
class CodeBlock extends React.Component {
2021-03-25 07:28:14 +03:00
_ref = null;
2021-04-21 03:14:43 +03:00
static defaultProps = {
language: "javascript",
};
2021-03-25 07:28:14 +03:00
state = {
copyValue: "",
2021-03-25 08:37:41 +03:00
tooltip: false,
copied: false,
2021-03-25 07:28:14 +03:00
};
_handleCopy = (value) => {
2021-03-25 08:37:41 +03:00
this.setState({ copyValue: value, copied: true }, () => {
2021-03-25 07:28:14 +03:00
this._ref.select();
document.execCommand("copy");
});
2021-03-25 08:37:41 +03:00
setTimeout(() => {
this.setState({ copied: false });
}, 1500);
2021-03-25 07:28:14 +03:00
};
2021-03-25 09:38:02 +03:00
2021-03-27 01:06:52 +03:00
_handleSwitchLang = (lang) => {
this.setState({ language: lang });
this.props.onLanguageChange(lang);
2021-03-25 09:38:02 +03:00
};
render() {
2021-03-26 04:58:43 +03:00
let availableLanguages = this.props.multiLang ? Object.keys(this.props.children) : 1;
2021-03-25 22:05:09 +03:00
let showTopBar = this.props.title || availableLanguages.length > 1;
2021-03-25 08:37:41 +03:00
let copyText = this.state.copied ? "Copied" : "Copy code";
return (
2021-03-25 07:28:14 +03:00
<React.Fragment>
2021-03-25 22:05:09 +03:00
{showTopBar && (
2021-03-25 07:28:14 +03:00
<div css={STYLES_TOPBAR} style={this.props.style}>
2021-03-25 22:05:09 +03:00
{this.props.title && <div css={STYLES_TOPBAR_TITLE}>{this.props.title}</div>}
{availableLanguages.length > 1 && (
2021-03-25 07:28:14 +03:00
<div css={STYLES_LANGSWITCHER}>
2021-03-25 22:05:09 +03:00
{availableLanguages.map((language, index) => {
return (
<div
key={index}
2021-04-21 03:14:43 +03:00
css={language === this.props.language ? STYLES_LANG_SELECTED : STYLES_LANG}
2021-03-25 22:05:09 +03:00
onClick={() => {
this._handleSwitchLang(language);
}}
>
{language}
</div>
);
})}
2021-03-25 07:28:14 +03:00
</div>
)}
2021-03-25 08:37:41 +03:00
<div
css={STYLES_COPY_BUTTON}
2021-03-30 05:19:47 +03:00
onClick={() =>
this._handleCopy(
availableLanguages.length > 1
2021-04-21 03:14:43 +03:00
? this.props.children[this.props.language]
2021-03-30 05:19:47 +03:00
: this.props.children
)
}
2021-03-25 08:37:41 +03:00
onMouseEnter={() => this.setState({ tooltip: true })}
onMouseLeave={() => this.setState({ tooltip: false })}
>
2021-03-25 07:28:14 +03:00
<SVG.CopyAndPaste height="16px" />
2021-03-25 08:37:41 +03:00
{this.state.tooltip && <div css={STYLES_TOOLTIP}>{copyText}</div>}
2021-03-25 07:28:14 +03:00
</div>
2021-03-25 08:37:41 +03:00
<input
2021-03-25 07:28:14 +03:00
css={STYLES_HIDDEN}
ref={(c) => {
this._ref = c;
}}
readOnly
value={this.state.copyValue}
2021-03-25 08:37:41 +03:00
/>
2021-03-25 07:28:14 +03:00
</div>
)}
<div css={STYLES_CODE_BLOCK} style={this.props.style}>
<Highlight
{...defaultProps}
theme={customTheme}
2021-03-25 22:05:09 +03:00
code={
availableLanguages.length > 1
2021-04-21 03:14:43 +03:00
? this.props.children[this.props.language]
2021-03-25 22:05:09 +03:00
: this.props.children
}
2021-04-21 03:14:43 +03:00
language={this.props.language}
2021-03-25 07:28:14 +03:00
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} css={STYLES_CODE_BODY}>
{tokens.map((line, i) => (
<div css={STYLES_LINE} key={i} {...getLineProps({ line, key: i })}>
<div css={STYLES_LINE_NUMBER}>{i + 1}</div>
<div css={STYLES_CODE}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
</div>
))}
</pre>
)}
</Highlight>
</div>
</React.Fragment>
);
}
}
export default CodeBlock;