Skip to content

Commit 5376ab7

Browse files
committed
feat(frontend): use shiki instead of prismjs
1 parent 91f8877 commit 5376ab7

File tree

9 files changed

+714
-417
lines changed

9 files changed

+714
-417
lines changed

src/GZCTF/ClientApp/package.json

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
"dependencies": {
1515
"@babel/core": "^7.27.4",
1616
"@emotion/react": "^11.14.0",
17-
"@mantine/carousel": "^8.0.2",
18-
"@mantine/colors-generator": "^8.0.2",
19-
"@mantine/core": "^8.0.2",
20-
"@mantine/dates": "^8.0.2",
21-
"@mantine/dropzone": "^8.0.2",
22-
"@mantine/emotion": "^8.0.2",
23-
"@mantine/form": "^8.0.2",
24-
"@mantine/hooks": "^8.0.2",
25-
"@mantine/modals": "^8.0.2",
26-
"@mantine/notifications": "^8.0.2",
17+
"@mantine/carousel": "^8.1.0",
18+
"@mantine/colors-generator": "^8.1.0",
19+
"@mantine/core": "^8.1.0",
20+
"@mantine/dates": "^8.1.0",
21+
"@mantine/dropzone": "^8.1.0",
22+
"@mantine/emotion": "^8.1.0",
23+
"@mantine/form": "^8.1.0",
24+
"@mantine/hooks": "^8.1.0",
25+
"@mantine/modals": "^8.1.0",
26+
"@mantine/notifications": "^8.1.0",
2727
"@marsidev/react-turnstile": "^1.1.0",
2828
"@mdi/js": "^7.4.47",
2929
"@mdi/react": "^1.6.1",
@@ -44,46 +44,45 @@
4444
"katex": "^0.16.22",
4545
"lz-string": "^1.5.0",
4646
"marked": "^15.0.12",
47-
"marked-highlight": "^2.2.1",
4847
"pdfjs-dist": "4.8.69",
49-
"prismjs": "^1.30.0",
5048
"react": "^19.1.0",
5149
"react-dom": "^19.1.0",
5250
"react-error-boundary": "^6.0.0",
5351
"react-i18next": "^15.5.2",
5452
"react-pdf": "^9.2.1",
5553
"react-router": "^7.6.2",
54+
"shiki": "^3.6.0",
5655
"swr": "^2.3.3"
5756
},
5857
"devDependencies": {
58+
"@shikijs/transformers": "^3.6.0",
5959
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
6060
"@types/chroma-js": "^3.1.1",
6161
"@types/katex": "^0.16.7",
62-
"@types/node": "^22.15.30",
62+
"@types/node": "^24.0.0",
6363
"@types/prismjs": "^1.26.5",
64-
"@types/react": "^19.1.6",
64+
"@types/react": "^19.1.7",
6565
"@types/react-dom": "^19.1.6",
66-
"@vitejs/plugin-react": "^4.5.1",
66+
"@vitejs/plugin-react": "^4.5.2",
6767
"axios": "^1.9.0",
6868
"babel-plugin-prismjs": "^2.1.0",
6969
"form-data": "~4.0.3",
7070
"globals": "^16.2.0",
7171
"lodash": "^4.17.21",
72-
"oxlint": "^0.18.0",
72+
"oxlint": "^1.0.0",
7373
"postcss": "^8.5.4",
7474
"postcss-preset-mantine": "^1.17.0",
7575
"postcss-simple-vars": "^7.0.1",
7676
"prettier": "~3.5.3",
7777
"prettier-plugin-sort-json": "^4.1.1",
7878
"rollup": "^4.42.0",
79-
"swagger-typescript-api": "^13.2.0",
79+
"swagger-typescript-api": "^13.2.1",
8080
"tslib": "^2.8.1",
8181
"typescript": "5.8.3",
8282
"vite": "npm:rolldown-vite@^6.3.18",
8383
"vite-plugin-banner": "^0.8.1",
8484
"vite-plugin-optimize-css-modules": "^1.2.0",
8585
"vite-plugin-pages": "^0.33.0",
86-
"vite-plugin-prismjs": "^0.0.11",
8786
"vite-plugin-webfont-dl": "^3.10.5",
8887
"vite-tsconfig-paths": "^5.1.4"
8988
},

src/GZCTF/ClientApp/pnpm-lock.yaml

Lines changed: 565 additions & 271 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GZCTF/ClientApp/src/components/ChallengeModal.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ import {
1010
Title,
1111
useMantineTheme,
1212
ScrollAreaAutosize,
13-
Skeleton,
1413
} from '@mantine/core'
1514
import { mdiLightbulbOnOutline, mdiOpenInNew, mdiPackageVariantClosed } from '@mdi/js'
1615
import Icon from '@mdi/react'
1716
import { FC, useCallback, useEffect, useState } from 'react'
1817
import { useTranslation } from 'react-i18next'
1918
import { InstanceEntry } from '@Components/InstanceEntry'
20-
import { InlineMarkdown, Markdown } from '@Components/MarkdownRenderer'
19+
import { ContentPlaceholder, InlineMarkdown, Markdown } from '@Components/MarkdownRenderer'
2120
import { ChallengeCategoryItemProps } from '@Utils/Shared'
2221
import { ChallengeDetailModel, ChallengeType } from '@Api'
2322
import classes from '@Styles/ChallengeModal.module.css'
@@ -90,15 +89,7 @@ export const ChallengeModal: FC<ChallengeModalProps> = (props) => {
9089
const content = (
9190
<ScrollAreaAutosize mah="50vh" maw="100%" scrollbars="y" scrollbarSize={6} type="scroll">
9291
{challenge?.content === undefined ? (
93-
<>
94-
<Skeleton height={14} mt={8} radius="xl" />
95-
<Skeleton height={14} mt={8} radius="xl" />
96-
<Skeleton height={14} mt={8} width="60%" radius="xl" />
97-
98-
<Skeleton height={14} mt={8 + 14} radius="xl" />
99-
<Skeleton height={14} mt={8} radius="xl" />
100-
<Skeleton height={14} mt={8} width="30%" radius="xl" />
101-
</>
92+
<ContentPlaceholder />
10293
) : (
10394
<>
10495
<Markdown source={challenge.content ?? ''} />
Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { Text, TextProps, TypographyStylesProvider } from '@mantine/core'
1+
import { Skeleton, Text, TextProps, TypographyStylesProvider } from '@mantine/core'
22
import 'katex/dist/katex.min.css'
33
import { Marked } from 'marked'
4-
import { markedHighlight } from 'marked-highlight'
5-
import Prism from 'prismjs'
6-
import { forwardRef } from 'react'
7-
import { KatexExtension } from '@Utils/KatexExtension'
4+
import { forwardRef, useMemo, use, Suspense, FC } from 'react'
5+
import { KatexExtension } from '@Utils/marked/KatexExtension'
6+
import { ShikiExtension } from '@Utils/marked/ShikiExtension'
87
import classes from '@Styles/Typography.module.css'
98

109
export interface MarkdownProps extends React.ComponentPropsWithoutRef<'div'> {
@@ -17,48 +16,62 @@ interface InlineMarkdownProps extends TextProps {
1716

1817
export const InlineMarkdown = forwardRef<HTMLParagraphElement, InlineMarkdownProps>((props, ref) => {
1918
const { source, ...others } = props
20-
const marked = new Marked()
2119

22-
marked.use(KatexExtension()).setOptions({
23-
silent: true,
24-
})
20+
const inlineMarked = useMemo(() => {
21+
const instance = new Marked()
22+
instance.use(KatexExtension()).setOptions({ silent: true })
23+
return instance
24+
}, [])
2525

2626
return (
2727
<Text
2828
ref={ref}
2929
{...others}
3030
className={classes.inline}
3131
dangerouslySetInnerHTML={{
32-
__html: marked.parseInline(source) ?? '',
32+
__html: inlineMarked.parseInline(source) ?? '',
3333
}}
3434
/>
3535
)
3636
})
3737

38-
export const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props, ref) => {
39-
const { source, ...others } = props
38+
export const ContentPlaceholder: FC = () => (
39+
<>
40+
<Skeleton height={14} mt={8} radius="xl" />
41+
<Skeleton height={14} mt={8} radius="xl" />
42+
<Skeleton height={14} mt={8} width="60%" radius="xl" />
43+
<Skeleton height={14} mt={8 + 14} radius="xl" />
44+
<Skeleton height={14} mt={8} radius="xl" />
45+
<Skeleton height={14} mt={8} width="30%" radius="xl" />
46+
<Skeleton height={14} mt={8 + 14} radius="xl" />
47+
<Skeleton height={14} mt={8} width="80%" radius="xl" />
48+
</>
49+
)
4050

41-
Prism.manual = true
51+
const MarkdownRenderer: FC<Pick<MarkdownProps, 'source'>> = (props) => {
52+
const { source } = props
4253

43-
const marked = new Marked(
44-
markedHighlight({
45-
highlight(code, lang) {
46-
if (lang && Prism.languages[lang]) {
47-
return Prism.highlight(code, Prism.languages[lang], lang)
48-
} else {
49-
return code
50-
}
51-
},
52-
})
53-
)
54+
const marked = useMemo(() => {
55+
const instance = new Marked()
56+
instance.use(KatexExtension()).use(ShikiExtension()).setOptions({ silent: true })
57+
return instance
58+
}, [])
59+
60+
const htmlPromise = useMemo(() => {
61+
return marked.parse(source) as Promise<string>
62+
}, [marked, source])
5463

55-
marked.use(KatexExtension()).setOptions({
56-
silent: true,
57-
})
64+
return <div className={classes.root} dangerouslySetInnerHTML={{ __html: use(htmlPromise) }} />
65+
}
66+
67+
export const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props, ref) => {
68+
const { source, ...others } = props
5869

5970
return (
6071
<TypographyStylesProvider ref={ref} {...others}>
61-
<div className={classes.root} dangerouslySetInnerHTML={{ __html: marked.parse(source) }} />
72+
<Suspense fallback={<ContentPlaceholder />}>
73+
<MarkdownRenderer source={source} />
74+
</Suspense>
6275
</TypographyStylesProvider>
6376
)
6477
})

src/GZCTF/ClientApp/src/components/WsrxProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { mdiClose } from '@mdi/js'
44
import { Icon } from '@mdi/react'
55
import { Wsrx, WsrxError, WsrxErrorKind, WsrxFeature, WsrxOptions, WsrxState } from '@xdsec/wsrx'
66
import { t } from 'i18next'
7-
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
7+
import { createContext, useCallback, use, useEffect, useMemo, useState } from 'react'
88
import { showErrorMsg } from '@Utils/Shared'
99
import { useConfig } from '@Hooks/useConfig'
1010

@@ -159,7 +159,7 @@ export const WsrxProvider: React.FC<React.PropsWithChildren> = ({ children }) =>
159159
}
160160

161161
export const useWsrx = () => {
162-
const context = useContext(WsrxContext)
162+
const context = use(WsrxContext)
163163
if (!context) {
164164
throw new Error('useWsrx must be used within a WsrxProvider')
165165
}

src/GZCTF/ClientApp/src/styles/shared/Typography.module.css

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -85,85 +85,6 @@
8585
line-height: var(--mantine-line-height-sm);
8686
tab-size: 4;
8787
hyphens: none;
88-
89-
& :global(.namespace) {
90-
opacity: 0.8;
91-
}
92-
93-
& :global(.token.id),
94-
& :global(.token.important),
95-
& :global(.token.keyword) {
96-
font-weight: bold;
97-
}
98-
99-
& :global(.token.atrule),
100-
& :global(.token.boolean),
101-
& :global(.token.constant),
102-
& :global(.token.function),
103-
& :global(.token.id),
104-
& :global(.token.important),
105-
& :global(.token.keyword),
106-
& :global(.token.symbol) {
107-
color: light-dark(#7c4dff, #c792ea);
108-
}
109-
110-
& :global(.token.attr-name),
111-
& :global(.token.builtin),
112-
& :global(.token.class) {
113-
color: light-dark(#39adb5, #ffcb6b);
114-
}
115-
116-
& :global(.token.attr-value),
117-
& :global(.token.attribute),
118-
& :global(.token.pseudo-class),
119-
& :global(.token.pseudo-element) {
120-
color: light-dark(#f6a434, #a5e844);
121-
}
122-
123-
& :global(.token.cdata),
124-
& :global(.token.char),
125-
& :global(.token.inserted),
126-
& :global(.token.operator),
127-
& :global(.token.property) {
128-
color: light-dark(#39adb5, #80cbc4);
129-
}
130-
131-
& :global(.token.class-name),
132-
& :global(.token.regex) {
133-
color: light-dark(#6182b8, #f2ff00);
134-
}
135-
136-
& :global(.token.comment),
137-
& :global(.token.doctype),
138-
& :global(.token.prolog) {
139-
color: light-dark(#aabfc9, #616161);
140-
}
141-
142-
& :global(.token.deleted),
143-
& :global(.token.entity),
144-
& :global(.token.selector),
145-
& :global(.token.category),
146-
& :global(.token.url),
147-
& :global(.token.variable) {
148-
color: light-dark(#e53935, #ff6666);
149-
}
150-
151-
& :global(.token.hexcode) {
152-
color: light-dark(#f76d47, #f2ff00);
153-
}
154-
155-
& :global(.token.number),
156-
& :global(.token.unit) {
157-
color: light-dark(#f76d47, #fd9170);
158-
}
159-
160-
& :global(.token.punctuation) {
161-
color: light-dark(#39adb5, #89ddff);
162-
}
163-
164-
& :global(.token.string) {
165-
color: light-dark(#84a657, #a5e844);
166-
}
16788
}
16889
}
16990

0 commit comments

Comments
 (0)