fix: render GitHub alert syntax in update notes

This commit is contained in:
Tunglies
2026-05-07 13:44:07 +08:00
parent d0198a92e8
commit 8c1c143b5e
+132 -2
View File
@@ -1,4 +1,4 @@
import { Box, Button, LinearProgress } from '@mui/material'
import { alpha, Box, Button, LinearProgress } from '@mui/material'
import { relaunch } from '@tauri-apps/plugin-process'
import { open as openUrl } from '@tauri-apps/plugin-shell'
import type { DownloadEvent } from '@tauri-apps/plugin-updater'
@@ -15,6 +15,95 @@ import { portableFlag } from '@/pages/_layout'
import { showNotice } from '@/services/notice-service'
import { useSetUpdateState, useUpdateState } from '@/services/states'
type MarkdownNode = {
type: string
value?: string
children?: MarkdownNode[]
data?: {
hProperties?: Record<string, unknown>
}
}
const GITHUB_ALERTS = {
note: { label: 'Note', color: '#0969da' },
tip: { label: 'Tip', color: '#1a7f37' },
important: { label: 'Important', color: '#8250df' },
warning: { label: 'Warning', color: '#9a6700' },
caution: { label: 'Caution', color: '#cf222e' },
} as const
type GitHubAlertType = keyof typeof GITHUB_ALERTS
const GITHUB_ALERT_PATTERN =
/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][\t ]*\n?/i
const GITHUB_ALERT_CLASS_PATTERN =
/markdown-alert-(note|tip|important|warning|caution)/
const getAlertTypeFromClassName = (
className: unknown,
): GitHubAlertType | null => {
const value = Array.isArray(className)
? className.join(' ')
: typeof className === 'string'
? className
: ''
const match = value.match(GITHUB_ALERT_CLASS_PATTERN)
return match?.[1] as GitHubAlertType | null
}
const findFirstTextNode = (node: MarkdownNode): MarkdownNode | null => {
if (node.type === 'text') return node
for (const child of node.children ?? []) {
const result = findFirstTextNode(child)
if (result) return result
}
return null
}
const remarkGitHubAlerts = () => {
const visit = (node: MarkdownNode) => {
for (const child of node.children ?? []) {
visit(child)
}
if (node.type !== 'blockquote') return
const firstTextNode = findFirstTextNode(node)
const match = firstTextNode?.value?.match(GITHUB_ALERT_PATTERN)
if (!firstTextNode?.value || !match) return
const alertType = match[1].toLowerCase() as GitHubAlertType
firstTextNode.value = firstTextNode.value
.replace(GITHUB_ALERT_PATTERN, '')
.replace(/^\n+/, '')
node.data = {
...(node.data ?? {}),
hProperties: {
...(node.data?.hProperties ?? {}),
className: ['markdown-alert', `markdown-alert-${alertType}`],
},
}
node.children?.unshift({
type: 'paragraph',
data: {
hProperties: {
className: 'markdown-alert-title',
},
},
children: [
{
type: 'text',
value: GITHUB_ALERTS[alertType].label,
},
],
})
}
return visit
}
export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation()
@@ -140,16 +229,57 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
>
<Box sx={{ height: 'calc(100% - 10px)', overflow: 'auto' }}>
<ReactMarkdown
remarkPlugins={[remarkGitHubAlerts]}
rehypePlugins={[rehypeRaw]}
components={{
a: ({ ...props }) => {
const { children } = props
return (
<a {...props} target="_blank">
<a {...props} target="_blank" rel="noreferrer">
{children}
</a>
)
},
blockquote: ({ className, children }) => {
const alertType = getAlertTypeFromClassName(className)
if (!alertType) {
return <blockquote className={className}>{children}</blockquote>
}
return (
<Box
component="blockquote"
className={className}
sx={(theme) => {
const color = GITHUB_ALERTS[alertType].color
return {
m: '12px 0 18px',
px: 2,
py: 1,
borderLeft: `4px solid ${color}`,
borderRadius: 1,
bgcolor: alpha(
color,
theme.palette.mode === 'dark' ? 0.16 : 0.08,
),
'& p': {
my: 0.75,
},
'& .markdown-alert-title': {
display: 'flex',
alignItems: 'center',
gap: 0.75,
fontWeight: 700,
lineHeight: 1.4,
},
}
}}
>
{children}
</Box>
)
},
}}
>
{markdownContent}