Skip to content

Commit a029f21

Browse files
committed
feat: markdown in comments
1 parent 087dc7c commit a029f21

File tree

5 files changed

+80
-10
lines changed

5 files changed

+80
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"react-router-dom": "6",
5353
"react-spring": "^9.4.5",
5454
"react-use": "^17.4.0",
55+
"remark-breaks": "^3.0.2",
5556
"remark-gfm": "^3.0.1",
5657
"snakecase-keys": "^5.4.4",
5758
"swr": "^2.0.0-rc.3",

src/components/Markdown.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import clsx from 'clsx'
2+
import { uniq } from 'lodash-es'
3+
import ReactMarkdown from 'react-markdown'
4+
import { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown'
5+
import remarkBreaks from 'remark-breaks'
6+
import remarkGfm from 'remark-gfm'
7+
8+
interface MarkdownProps extends ReactMarkdownOptions {
9+
className?: string
10+
}
11+
12+
export function Markdown({
13+
className,
14+
remarkPlugins,
15+
components,
16+
children,
17+
...props
18+
}: MarkdownProps) {
19+
return (
20+
<ReactMarkdown
21+
className={clsx(
22+
className,
23+
'markdown-body !text-sm !bg-transparent [&_img]:!bg-transparent',
24+
)}
25+
remarkPlugins={uniq([remarkGfm, remarkBreaks, ...(remarkPlugins ?? [])])}
26+
components={{
27+
...components,
28+
29+
a: ({ node, children, ...props }) => {
30+
// set target="_blank" for external links, see: https://github.com/remarkjs/react-markdown/issues/12#issuecomment-1479195975
31+
if (props.href?.startsWith('http')) {
32+
props.target = '_blank'
33+
props.rel = 'noopener noreferrer'
34+
}
35+
36+
// by default, ReactMarkdown already handles dangerous URIs and replaces them with "javascript:void(0)",
37+
// but React warns about passing such a string to the href, so we'll handle this
38+
if (props.href?.startsWith('javascript:')) {
39+
props.href = '#'
40+
props.onClick = (e) => e.preventDefault()
41+
}
42+
43+
return <a {...props}>{children}</a>
44+
},
45+
}}
46+
{...props}
47+
>
48+
{children}
49+
</ReactMarkdown>
50+
)
51+
}

src/components/announcement/AnnDialog.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import {
88
} from '@blueprintjs/core'
99

1010
import { FC } from 'react'
11-
import ReactMarkdown, { Components } from 'react-markdown'
12-
import remarkGfm from 'remark-gfm'
11+
import { Components } from 'react-markdown'
1312

1413
import { announcementBaseURL } from '../../apis/announcement'
1514
import {
1615
AnnouncementSection,
1716
AnnouncementSectionMeta,
1817
} from '../../models/announcement'
1918
import { formatDateTime, formatRelativeTime } from '../../utils/times'
19+
import { Markdown } from '../Markdown'
2020

2121
interface AnnDialogProps extends DialogProps {
2222
sections?: AnnouncementSection[]
@@ -68,9 +68,7 @@ export const AnnDialog: FC<AnnDialogProps> = ({ sections, ...dialogProps }) => {
6868
<Dialog className="" title="公告" icon="info-sign" {...dialogProps}>
6969
<DialogBody className="">
7070
{content ? (
71-
<ReactMarkdown
72-
className="markdown-body !text-sm !bg-transparent [&_img]:!bg-transparent"
73-
remarkPlugins={[remarkGfm]}
71+
<Markdown
7472
rehypePlugins={[attachMetaPlugin]}
7573
components={{
7674
h1: Heading,
@@ -84,7 +82,7 @@ export const AnnDialog: FC<AnnDialogProps> = ({ sections, ...dialogProps }) => {
8482
transformImageUri={transformUri}
8583
>
8684
{content || ''}
87-
</ReactMarkdown>
85+
</Markdown>
8886
) : (
8987
<NonIdealState icon="help" title="暂无公告" />
9088
)}

src/components/viewer/comment/CommentArea.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { authAtom } from '../../../store/auth'
3030
import { formatError } from '../../../utils/error'
3131
import { formatDateTime } from '../../../utils/times'
3232
import { wrapErrorMessage } from '../../../utils/wrapErrorMessage'
33+
import { Markdown } from '../../Markdown'
3334
import { OutlinedIcon } from '../../OutlinedIcon'
3435
import { withSuspensable } from '../../Suspensable'
3536
import { CommentForm } from './CommentForm'
@@ -143,7 +144,7 @@ const MainComment = ({
143144
comment: MainCommentInfo
144145
children?: ReactNode
145146
}) => {
146-
const { message, uploader, uploadTime } = comment
147+
const { uploader, uploadTime } = comment
147148

148149
return (
149150
<Card className={clsx(className)}>
@@ -152,7 +153,7 @@ const MainComment = ({
152153
<div className="font-bold mr-2">{uploader}</div>
153154
<div>{formatDateTime(uploadTime)}</div>
154155
</div>
155-
<div className="text-base">{message}</div>
156+
<CommentContent comment={comment} />
156157
<CommentActions comment={comment} />
157158
</div>
158159
{children}
@@ -169,7 +170,7 @@ const SubComment = ({
169170
comment: SubCommentInfo
170171
fromComment?: SubCommentInfo
171172
}) => {
172-
const { message, uploader, uploadTime } = comment
173+
const { uploader, uploadTime } = comment
173174

174175
return (
175176
<div className={clsx(className, 'pl-8')}>
@@ -190,7 +191,7 @@ const SubComment = ({
190191
:&nbsp;
191192
</>
192193
)}
193-
<span>{message}</span>
194+
<CommentContent comment={comment} />
194195
</div>
195196
<CommentActions comment={comment} />
196197
</div>
@@ -199,6 +200,16 @@ const SubComment = ({
199200
)
200201
}
201202

203+
const CommentContent = ({
204+
className,
205+
comment: { message },
206+
}: {
207+
className?: string
208+
comment: CommentInfo
209+
}) => {
210+
return <Markdown className={clsx(className)}>{message}</Markdown>
211+
}
212+
202213
const CommentActions = ({
203214
className,
204215
comment,

yarn.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4534,6 +4534,15 @@ regexpp@^3.2.0:
45344534
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
45354535
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
45364536

4537+
remark-breaks@^3.0.2:
4538+
version "3.0.2"
4539+
resolved "https://registry.yarnpkg.com/remark-breaks/-/remark-breaks-3.0.2.tgz#f466b9d3474d7323146c0149fc1496dabadd908e"
4540+
integrity sha512-x96YDJ9X+Ry0/JNZFKfr1hpcAKvGYWfUTszxY9RbxKEqq6uzPPoLCuHdZsLPZZUdAv3nCROyc7FPrQLWr2rxyw==
4541+
dependencies:
4542+
"@types/mdast" "^3.0.0"
4543+
unified "^10.0.0"
4544+
unist-util-visit "^4.0.0"
4545+
45374546
remark-gfm@^3.0.1:
45384547
version "3.0.1"
45394548
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"

0 commit comments

Comments
 (0)