🐛 fix(hightlight): Fix prompt highlight
parent
fa109670e3
commit
a2698af034
File diff suppressed because one or more lines are too long
|
|
@ -107,13 +107,15 @@
|
|||
"remark-cli": "^11",
|
||||
"semantic-release": "^21",
|
||||
"semver": "^7.5.4",
|
||||
"shiki-es": "^0.14",
|
||||
"styled-components": "^6",
|
||||
"stylelint": "^15.10",
|
||||
"swr": "^2",
|
||||
"terser": "^5",
|
||||
"typescript": "^5",
|
||||
"vite": "^4",
|
||||
"zustand": "^4"
|
||||
"zustand": "^4.4.1",
|
||||
"zustand-utils": "^1.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": ">=5",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { SyntaxHighlighter } from '@lobehub/ui';
|
||||
import { useScroll, useSize } from 'ahooks';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useExternalTextareaObserver } from '@/hooks/useExternalTextareaObserver';
|
||||
|
||||
import SyntaxHighlighter from './SyntaxHighlighter';
|
||||
import grammar from './prompt.tmLanguage.json';
|
||||
import { themeConfig } from './promptTheme';
|
||||
import { useStyles } from './style';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import { Icon } from '@lobehub/ui';
|
||||
import { useThemeMode } from 'antd-style';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { Center } from 'react-layout-kit';
|
||||
import { type HighlighterOptions } from 'shiki-es';
|
||||
|
||||
import { useStyles } from './style';
|
||||
import { useHighlight } from './useHighlight';
|
||||
|
||||
export interface SyntaxHighlighterProps {
|
||||
children: string;
|
||||
language: string;
|
||||
options?: HighlighterOptions;
|
||||
}
|
||||
|
||||
const SyntaxHighlighter = memo<SyntaxHighlighterProps>(({ children, language, options }) => {
|
||||
const { styles } = useStyles();
|
||||
const { isDarkMode } = useThemeMode();
|
||||
const [codeToHtml, isLoading] = useHighlight((s) => [s.codeToHtml, !s.highlighter]);
|
||||
|
||||
useEffect(() => {
|
||||
useHighlight.getState().initHighlighter(options);
|
||||
}, [options]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<code>{children}</code>
|
||||
) : (
|
||||
<div
|
||||
className={styles.shiki}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: codeToHtml(children, language, isDarkMode) || '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<Center className={styles.loading} gap={8} horizontal>
|
||||
<Icon icon={Loader2} spin />
|
||||
Highlighting...
|
||||
</Center>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default SyntaxHighlighter;
|
||||
|
|
@ -1,23 +1,63 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => ({
|
||||
container: css`
|
||||
pointer-events: none;
|
||||
export const useStyles = createStyles(({ css, token, cx, stylish, prefixCls }) => {
|
||||
const prefix = `${prefixCls}-highlighter`;
|
||||
return {
|
||||
container: css`
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
padding: 8px;
|
||||
padding: 8px;
|
||||
|
||||
pre {
|
||||
font-family: ${token.fontFamilyCode} !important;
|
||||
font-size: 13px;
|
||||
line-height: 18.2px;
|
||||
color: ${token.colorSuccess};
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
pre {
|
||||
font-family: ${token.fontFamilyCode} !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.5 !important;
|
||||
color: ${token.colorSuccess};
|
||||
text-overflow: ellipsis !important;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
vertical-align: bottom !important;
|
||||
}
|
||||
`,
|
||||
loading: cx(
|
||||
stylish.blur,
|
||||
css`
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 34px;
|
||||
padding: 0 8px;
|
||||
|
||||
font-family: ${token.fontFamilyCode};
|
||||
color: ${token.colorTextTertiary};
|
||||
|
||||
border-radius: ${token.borderRadius};
|
||||
`,
|
||||
),
|
||||
shiki: cx(
|
||||
`${prefix}-shiki`,
|
||||
css`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.shiki {
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none !important;
|
||||
}
|
||||
`,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import { type Highlighter, type HighlighterOptions, getHighlighter } from 'shiki-es';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { createWithEqualityFn } from 'zustand/traditional';
|
||||
|
||||
export const languageMap = [] as const;
|
||||
|
||||
/**
|
||||
* @title 代码高亮的存储对象
|
||||
*/
|
||||
interface Store {
|
||||
/**
|
||||
* @title Convert code to HTML string
|
||||
* @param text - The code text
|
||||
* @param language - The language of the code
|
||||
* @param isDarkMode - Whether it's in dark mode or not
|
||||
* @returns HTML string
|
||||
*/
|
||||
codeToHtml: (text: string, language: string, isDarkMode: boolean) => string;
|
||||
/**
|
||||
* @title Highlighter object
|
||||
*/
|
||||
highlighter?: Highlighter;
|
||||
/**
|
||||
* @title Initialize the highlighter object
|
||||
* @returns Initialization promise object
|
||||
*/
|
||||
initHighlighter: (options?: HighlighterOptions) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useHighlight = createWithEqualityFn<Store>(
|
||||
(set, get) => ({
|
||||
codeToHtml: (text, language, isDarkMode) => {
|
||||
const { highlighter } = get();
|
||||
|
||||
if (!highlighter) return '';
|
||||
|
||||
try {
|
||||
return highlighter?.codeToHtml(text, {
|
||||
lang: language,
|
||||
theme: isDarkMode ? 'dark' : 'light',
|
||||
});
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
highlighter: undefined,
|
||||
|
||||
initHighlighter: async(options) => {
|
||||
if (!get().highlighter) {
|
||||
const highlighter = await getHighlighter({
|
||||
langs: options?.langs,
|
||||
themes: options?.themes,
|
||||
});
|
||||
|
||||
set({ highlighter });
|
||||
}
|
||||
},
|
||||
}),
|
||||
shallow,
|
||||
);
|
||||
|
|
@ -242,12 +242,13 @@ export const useStyles = createStyles(
|
|||
|
||||
overflow-y: auto;
|
||||
|
||||
padding: 8px;
|
||||
padding: 8px !important;
|
||||
|
||||
font-family: ${token.fontFamilyCode};
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: bottom;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.5 !important;
|
||||
word-wrap: break-word !important;
|
||||
white-space: pre-wrap !important;
|
||||
|
||||
transition:
|
||||
all 0.3s,
|
||||
|
|
|
|||
Loading…
Reference in New Issue