Feat: web app dark mode (#14732)

This commit is contained in:
KVOJJJin
2025-03-03 14:44:51 +08:00
committed by GitHub
parent e53052ab7a
commit d0d0bf570e
98 changed files with 3006 additions and 2496 deletions

View File

@@ -7,11 +7,14 @@ import RehypeKatex from 'rehype-katex'
import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
import {
atelierHeathDark,
atelierHeathLight,
} from 'react-syntax-highlighter/dist/esm/styles/hljs'
import { Component, memo, useMemo, useRef, useState } from 'react'
import { flow } from 'lodash-es'
import cn from '@/utils/classnames'
import CopyBtn from '@/app/components/base/copy-btn'
import ActionButton from '@/app/components/base/action-button'
import CopyIcon from '@/app/components/base/copy-icon'
import SVGBtn from '@/app/components/base/svg'
import Flowchart from '@/app/components/base/mermaid'
import ImageGallery from '@/app/components/base/image-gallery'
@@ -22,6 +25,9 @@ import SVGRenderer from '@/app/components/base/svg-gallery'
import MarkdownButton from '@/app/components/base/markdown-blocks/button'
import MarkdownForm from '@/app/components/base/markdown-blocks/form'
import ThinkBlock from '@/app/components/base/markdown-blocks/think-block'
import { Theme } from '@/types/app'
import { useAppContext } from '@/context/app-context'
import cn from '@/utils/classnames'
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
@@ -100,7 +106,8 @@ export function PreCode(props: { children: any }) {
// visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message
// or use the non-minified dev environment for full errors and additional helpful warnings.
const CodeBlock: any = memo(({ inline, className, children, ...props }) => {
const CodeBlock: any = memo(({ inline, className, children, ...props }: any) => {
const { theme } = useAppContext()
const [isSVG, setIsSVG] = useState(true)
const match = /language-(\w+)/.exec(className || '')
const language = match?.[1]
@@ -140,10 +147,12 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }) => {
return (
<SyntaxHighlighter
{...props}
style={atelierHeathLight}
style={theme === Theme.light ? atelierHeathLight : atelierHeathDark}
customStyle={{
paddingLeft: 12,
backgroundColor: '#fff',
borderBottomLeftRadius: '10px',
borderBottomRightRadius: '10px',
backgroundColor: 'var(--color-components-input-bg-normal)',
}}
language={match?.[1]}
showLineNumbers
@@ -159,21 +168,14 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }) => {
return <code {...props} className={className}>{children}</code>
return (
<div>
<div
className='flex justify-between h-8 items-center p-1 pl-3 border-b'
style={{
borderColor: 'rgba(0, 0, 0, 0.05)',
}}
>
<div className='text-[13px] text-gray-500 font-normal'>{languageShowName}</div>
<div style={{ display: 'flex' }}>
<div className='relative'>
<div className='bg-components-input-bg-normal rounded-t-[10px] flex justify-between h-8 items-center p-1 pl-3 border-b border-divider-subtle'>
<div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div>
<div className='flex items-center gap-1'>
{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
<CopyBtn
className='mr-1'
value={String(children).replace(/\n$/, '')}
isPlain
/>
<ActionButton>
<CopyIcon content={String(children).replace(/\n$/, '')}/>
</ActionButton>
</div>
</div>
{renderCodeContent}
@@ -182,16 +184,16 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }) => {
})
CodeBlock.displayName = 'CodeBlock'
const VideoBlock: any = memo(({ node }) => {
const srcs = node.children.filter(child => 'properties' in child).map(child => (child as any).properties.src)
const VideoBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0)
return null
return <VideoGallery key={srcs.join()} srcs={srcs} />
})
VideoBlock.displayName = 'VideoBlock'
const AudioBlock: any = memo(({ node }) => {
const srcs = node.children.filter(child => 'properties' in child).map(child => (child as any).properties.src)
const AudioBlock: any = memo(({ node }: any) => {
const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
if (srcs.length === 0)
return null
return <AudioGallery key={srcs.join()} srcs={srcs} />
@@ -243,7 +245,7 @@ export function Markdown(props: { content: string; className?: string }) {
preprocessLaTeX,
])(props.content)
return (
<div className={cn(props.className, 'markdown-body')}>
<div className={cn('markdown-body', '!text-text-primary', props.className)}>
<ReactMarkdown
remarkPlugins={[
RemarkGfm,
@@ -282,7 +284,7 @@ export function Markdown(props: { content: string; className?: string }) {
p: Paragraph,
button: MarkdownButton,
form: MarkdownForm,
script: ScriptBlock,
script: ScriptBlock as any,
details: ThinkBlock,
}}
>