Skip to content

Commit

Permalink
✨ feat: 完善并导出 Highlighter 语法高亮组件
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Feb 14, 2023
1 parent 2c3f479 commit 4c524d7
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 98 deletions.
39 changes: 1 addition & 38 deletions src/builtins/SourceCode/index.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,14 @@
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { Button, ConfigProvider, Tooltip } from 'antd';
import copy from 'copy-to-clipboard';
import { FC } from 'react';

import { Highlighter } from '../../components/Highlighter';
import { useCopied } from '../../hooks/useCopied';
import { useStyles } from './style';

interface SourceCodeProps {
lang: string;
children: string;
}

const SourceCode: FC<SourceCodeProps> = ({ children, lang }) => {
const { copied, setCopied } = useCopied();
const { styles, theme } = useStyles();

return (
<div className={styles.container}>
<ConfigProvider theme={{ token: { colorBgContainer: theme.colorBgElevated } }}>
<Tooltip
placement={'left'}
arrow={false}
title={
copied ? (
<>
<CheckOutlined style={{ color: theme.colorSuccess }} /> 复制成功
</>
) : (
'复制'
)
}
>
<Button
icon={<CopyOutlined />}
className={styles.button}
onClick={() => {
copy(children);
setCopied();
}}
/>
</Tooltip>
</ConfigProvider>

<Highlighter language={lang}>{children}</Highlighter>
</div>
);
return <Highlighter language={lang}>{children}</Highlighter>;
};

export default SourceCode;
2 changes: 1 addition & 1 deletion src/components/GithubButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const GithubButton: FC = () => {

return (
repoUrl && (
<Tooltip showArrow={false} title={'Github'}>
<Tooltip arrow={false} title={'Github'}>
<a href={repoUrl} target={'_blank'} rel="noreferrer">
<Button icon={<GithubFilled />} />
</a>
Expand Down
21 changes: 9 additions & 12 deletions src/components/Highlighter/Highlighter.style.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import { createStyles } from 'antd-style';

export const useStyles = createStyles(({ css, cx, prefixCls }) => {
export const useStyles = createStyles(({ css, token, cx, prefixCls }) => {
const prefix = `${prefixCls}-highlighter`;

return {
container: cx(
prefix,
css`
position: relative;
pre {
margin: 0 !important;
}
`,
),

shiki: cx(
`${prefix}-shiki`,
css`
.shiki {
overflow: scroll;
.line {
font-family: ${token.fontFamilyHighlighter};
}
}
`,
),

prism: cx(`${prefix}-prism`),

loading: css`
position: absolute;
bottom: 8px;
right: 8px;
right: 12px;
color: ${token.colorTextTertiary};
`,
};
});
60 changes: 32 additions & 28 deletions src/components/Highlighter/Highlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,50 @@ import { memo, useMemo, useState } from 'react';
import { Center, Flexbox } from 'react-layout-kit';

import { useStyles } from './Highlighter.style';

import type { HighlighterProps } from './index';
import { Prism } from './Prism';
import { useShiki } from './useShiki';

export interface HighlighterProps {
children: string;
language: string;
/**
* 语法高亮器类型
* @default 'shiki'
*/
type?: 'shiki' | 'prism';
}

const Highlighter = memo<HighlighterProps>(({ children, language }) => {
type SyntaxHighlighterProps = Pick<HighlighterProps, 'language' | 'type' | 'children'>;

const SyntaxHighlighter = memo<SyntaxHighlighterProps>(({ children, language, type = 'shiki' }) => {
const { styles, theme } = useStyles();
const { isDarkMode } = useThemeMode();
const [loading, setLoading] = useState(false);

const { codeToHtml } = useShiki({ onLoadingChange: setLoading });

const html = useMemo(
() => codeToHtml(children, language, isDarkMode) || '',
() => codeToHtml(children.trim(), language, isDarkMode) || '',
[codeToHtml, children, isDarkMode, language],
);

// const highlighter = type === 'prism' ? Prism : Highlighter;
return (
<Flexbox className={styles.container}>
{loading ? (
<Prism language={language}>{children}</Prism>
) : (
<div dangerouslySetInnerHTML={{ __html: html }} className={styles.shiki} />
)}
{loading && (
<Center className={styles.loading}>
<Loading spin style={{ color: theme.colorTextTertiary }} />
</Center>
)}
</Flexbox>
);
switch (type) {
case 'prism':
return (
<Flexbox className={styles.prism}>
<Prism language={language}>{children}</Prism>
</Flexbox>
);
default:
case 'shiki':
return (
<>
{loading ? (
<Prism language={language}>{children}</Prism>
) : (
<div dangerouslySetInnerHTML={{ __html: html }} className={styles.shiki} />
)}
{loading && (
<Center horizontal gap={8} className={styles.loading}>
<Loading spin style={{ color: theme.colorTextTertiary }} />
shiki 着色器准备中...
</Center>
)}
</>
);
}
});

export default Highlighter;
export default SyntaxHighlighter;
2 changes: 0 additions & 2 deletions src/components/Highlighter/index.ts

This file was deleted.

80 changes: 80 additions & 0 deletions src/components/Highlighter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { Button, ConfigProvider, Tooltip } from 'antd';
import copy from 'copy-to-clipboard';
import { CSSProperties, FC } from 'react';

import { useCopied } from '../../hooks/useCopied';
import SyntaxHighlighter from './Highlighter';
import { LanguageKeys } from './language';
import { useStyles } from './style';
export { Prism } from './Prism';

export interface HighlighterProps {
children: string;
language: LanguageKeys | string;
/**
* 语法高亮器类型
* @default 'shiki'
*/
type?: 'shiki' | 'prism';
/**
* 是否显示背景容器
* @default true
*/
background?: boolean;
className?: string;
style?: CSSProperties;
}

export const Highlighter: FC<HighlighterProps> = ({
children,
language,
background = true,
type,
className,
style,
}) => {
const { copied, setCopied } = useCopied();
const { styles, theme, cx } = useStyles();
const container = cx(styles.container, background && styles.withBackground, className);

return (
<div
// 用于标记是 markdown 中的代码块,避免和普通 code 的样式混淆
data-code-type="highlighter"
className={container}
style={style}
>
<ConfigProvider theme={{ token: { colorBgContainer: theme.colorBgElevated } }}>
<Tooltip
placement={'left'}
arrow={false}
title={
copied ? (
<>
<CheckOutlined style={{ color: theme.colorSuccess }} /> 复制成功
</>
) : (
'复制'
)
}
>
<Button
icon={<CopyOutlined />}
className={styles.button}
onClick={() => {
copy(children);
setCopied();
}}
/>
</Tooltip>
</ConfigProvider>

<SyntaxHighlighter language={language} type={type}>
{children}
</SyntaxHighlighter>
</div>
);
};

export default Highlighter;
25 changes: 16 additions & 9 deletions src/components/Highlighter/language.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import bash from 'react-syntax-highlighter/dist/cjs/languages/prism/bash';
import css from 'react-syntax-highlighter/dist/cjs/languages/prism/css';
import diff from 'react-syntax-highlighter/dist/cjs/languages/prism/diff';
import javascript from 'react-syntax-highlighter/dist/cjs/languages/prism/javascript';
import json from 'react-syntax-highlighter/dist/cjs/languages/prism/json';
import jsx from 'react-syntax-highlighter/dist/cjs/languages/prism/jsx';
import less from 'react-syntax-highlighter/dist/cjs/languages/prism/less';
import markdown from 'react-syntax-highlighter/dist/cjs/languages/prism/markdown';
import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx';
import typescript from 'react-syntax-highlighter/dist/cjs/languages/prism/typescript';

export const languageMap = {
javascript: javascript,
jsx: javascript,
json: json,
markdown: markdown,
less: less,
typescript: typescript,
javascript,
js: javascript,
jsx,
json,
markdown,
md: markdown,
less,
css,
typescript,
ts: typescript,
tsx: tsx,
diff: diff,
bash: bash,
tsx,
diff,
bash,
};

export type LanguageKeys = keyof typeof languageMap;
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { createStyles } from 'antd-style';

export const useStyles = createStyles(({ token, css, cx }) => {
const prefixCls = 'source-code';
const buttonHoverCls = `${prefixCls}-hover-btn`;
export const useStyles = createStyles(({ token, css, cx, prefixCls }) => {
const prefix = `${prefixCls}-highlighter`;
const buttonHoverCls = `${prefix}-hover-btn`;

return {
container: cx(
prefixCls,
prefix,
css`
position: relative;
pre {
background: ${token.colorFillTertiary} !important;
border-radius: 8px;
padding: 12px !important;
margin: 0 !important;
}
&:hover {
Expand All @@ -23,6 +21,17 @@ export const useStyles = createStyles(({ token, css, cx }) => {
}
`,
),
withBackground: cx(
`${prefix}-background`,
css`
pre {
background: ${token.colorFillTertiary} !important;
border-radius: 8px;
padding: 12px !important;
}
`,
),

button: cx(
buttonHoverCls,
css`
Expand Down
2 changes: 1 addition & 1 deletion src/slots/Content/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const useStyles = createStyles(({ token, responsive, isDarkMode, css }) =
}
// inline code
> :not(.source-code) code {
> :not([data-code-type='highlighter']) code {
padding: 2px 6px;
color: ${token.colorPrimaryText};
Expand Down
3 changes: 3 additions & 0 deletions src/styles/customToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface SiteToken {
gradientIconDefault: string;

colorSolid: string;
fontFamilyHighlighter: string;
}

export const getCustomToken: GetCustomToken<SiteToken> = ({ isDarkMode, token }) => {
Expand All @@ -38,6 +39,8 @@ export const getCustomToken: GetCustomToken<SiteToken> = ({ isDarkMode, token })
sidebarWidth: 240,
tocWidth: 176,
contentMaxWidth: 1152,
fontFamilyHighlighter:
"'Fira Code', 'Fira Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace",

colorSolid,

Expand Down

0 comments on commit 4c524d7

Please sign in to comment.