Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 99b09e8

Browse files
committed
feat: complete topic comment & reply
1 parent 1676b5c commit 99b09e8

File tree

14 files changed

+384
-130
lines changed

14 files changed

+384
-130
lines changed

‎config/config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { defineConfig } from 'umi';
55
export default defineConfig({
66
singular: true,
77
fastRefresh: {},
8-
mfsu: {},
8+
// mfsu: {},
99

1010
nodeModulesTransform: {
1111
type: 'none',

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@
4848
"umi": "^3.5.20",
4949
"yorkie": "^2.0.0"
5050
}
51-
}
51+
}

‎src/access.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function (initialState: InitialState) {
2+
const { token } = initialState;
3+
4+
return {
5+
canPostComment: !!token,
6+
};
7+
}

‎src/component/Markdown/index.less‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.editor {
2+
border: none;
3+
4+
:global {
5+
.editor-container {
6+
> .sec-html {
7+
> .html-wrap {
8+
padding: 0;
9+
}
10+
}
11+
}
12+
}
13+
}

‎src/component/Markdown/index.tsx‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import MarkdownIt from 'markdown-it';
3+
import MdEditor from 'react-markdown-editor-lite';
4+
import 'react-markdown-editor-lite/lib/index.css';
5+
6+
import * as styles from './index.less';
7+
8+
const mdParser = new MarkdownIt();
9+
10+
const Markdown: React.FC<Props> = (props) => {
11+
const { value, type, onChange } = props;
12+
13+
let view;
14+
15+
if (type === 'render') {
16+
view = {
17+
menu: false,
18+
md: false,
19+
html: true,
20+
};
21+
}
22+
23+
if (type === 'editor') {
24+
view = {
25+
menu: true,
26+
md: true,
27+
html: false,
28+
};
29+
}
30+
31+
return (
32+
<MdEditor
33+
className={styles.editor}
34+
readOnly={type === 'render'}
35+
view={view}
36+
value={value}
37+
renderHTML={(text) => mdParser.render(text)}
38+
onChange={(data) => {
39+
onChange && onChange(data.text);
40+
}}
41+
/>
42+
);
43+
};
44+
45+
export default Markdown;
46+
47+
interface Props {
48+
type: 'editor' | 'render';
49+
value: string;
50+
onChange?: (text: string) => void;
51+
}

‎src/layout.tsx‎

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { Link, history } from 'umi';
3-
import { Avatar, Tooltip } from 'antd';
3+
import { Avatar, Tooltip,Button } from 'antd';
44

55
import {
66
DefaultFooter,
@@ -17,7 +17,18 @@ const RightContent: React.FC<{
1717
const user = props?.user;
1818

1919
if (!user) {
20-
return null;
20+
return (
21+
<div className="cnode-header-right">
22+
<Button
23+
type="link"
24+
onClick={() => {
25+
history.push('/auth');
26+
}}
27+
>
28+
登录
29+
</Button>
30+
</div>
31+
);
2132
}
2233

2334
const { loginname, avatar_url } = user;
@@ -36,8 +47,8 @@ const layoutConfig = ({
3647
initialState: InitialState;
3748
}): BasicLayoutProps => {
3849
const { title, logo, description } = config;
50+
3951
return {
40-
// common
4152
navTheme: 'light',
4253
layout: 'top',
4354
headerHeight: 64,
@@ -47,15 +58,10 @@ const layoutConfig = ({
4758
logo,
4859
title,
4960

50-
// waterMarkProps: {
51-
// content: config.title,
52-
// },
53-
5461
menuHeaderRender: () => {
5562
return <Brand title={title} description={description} logo={logo} />;
5663
},
5764

58-
// heander
5965
menuDataRender: (menuData: MenuDataItem[]) => {
6066
let menus: MenuDataItem[] = [];
6167
const apps: MenuDataItem[] = [];
@@ -83,29 +89,16 @@ const layoutConfig = ({
8389
menuItemRender: (item) =>
8490
item.path && <Link to={item.path}>{item.name}</Link>,
8591

86-
// right
8792
rightContentRender: () => {
8893
return <RightContent user={initialState.user} />;
8994
},
9095

91-
// footer
9296
footerRender: () => (
9397
<DefaultFooter
9498
links={false}
9599
copyright={`${new Date().getFullYear()} - CNodejs.org`}
96100
/>
97101
),
98-
99-
onPageChange: () => {
100-
const { user, token } = initialState || {};
101-
102-
// 非登录页面
103-
if (history.location.pathname !== '/auth') {
104-
if (!user || !token) {
105-
history.push('/auth');
106-
}
107-
}
108-
},
109102
};
110103
};
111104

‎src/layout/index.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ const Layout: React.FC<React.PropsWithChildren<Props>> = (props) => {
6262
<ProCard gutter={16} bordered={false} ghost>
6363
<ProCard bordered={false}>{props.children}</ProCard>
6464
<ProCard
65-
title=""
6665
layout="center"
6766
bordered={false}
67+
ghost
6868
colSpan={{
6969
xs: '50px',
7070
sm: '100px',
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Comment, Avatar, Button } from 'antd';
3+
import Markdown from '@/component/Markdown';
4+
5+
const CommentForm: React.FC<Props> = (props) => {
6+
const { data, user, onSubmit, onSubmitText = '提交评论' } = props;
7+
const { loginname, avatar_url } = user;
8+
const [value, setValue] = useState('');
9+
10+
useEffect(() => {
11+
if (!data) {
12+
return;
13+
}
14+
setValue(data);
15+
}, [data]);
16+
17+
return (
18+
<Comment
19+
author={<span>{loginname}</span>}
20+
avatar={<Avatar src={avatar_url} alt={loginname} />}
21+
actions={[
22+
<Button
23+
type="primary"
24+
size="small"
25+
onClick={async () => {
26+
if (!value) {
27+
return;
28+
}
29+
30+
await onSubmit(value);
31+
}}
32+
>
33+
{onSubmitText}
34+
</Button>,
35+
]}
36+
content={<Markdown type="editor" value={value} onChange={setValue} />}
37+
/>
38+
);
39+
};
40+
41+
export default CommentForm;
42+
43+
interface Props {
44+
data?: string;
45+
user: {
46+
loginname: string;
47+
avatar_url: string;
48+
};
49+
onSubmit: (value: string) => Promise<void>;
50+
onSubmitText?: string;
51+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.list {
2+
img {
3+
max-width: 100%;
4+
overflow: hidden;
5+
}
6+
}
7+
8+
.detail {
9+
* {
10+
overflow: hidden;
11+
}
12+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { Fragment } from 'react';
2+
import dayjs from 'dayjs';
3+
import Markdown from '@/component/Markdown';
4+
5+
import { Comment, Avatar, Divider } from 'antd';
6+
import {
7+
LikeFilled,
8+
EditFilled,
9+
DeleteFilled,
10+
CommentOutlined,
11+
} from '@ant-design/icons';
12+
13+
import * as styles from './index.less';
14+
15+
const unflatten = (array: Node[], parent?: Node, tree?: Node[]) => {
16+
let _parent = parent || { id: null, children: [] };
17+
let _tree = tree || [];
18+
19+
const children = array.filter((child) => child.reply_id === _parent.id);
20+
21+
if (children.length > 0) {
22+
if (!_parent.id) {
23+
_tree = children;
24+
} else {
25+
_parent.children = children;
26+
}
27+
children.forEach((child) => unflatten(array, child));
28+
}
29+
30+
return _tree;
31+
};
32+
33+
const CommentList: React.FC<Props> = (props) => {
34+
const { list, onReply, replyRender } = props;
35+
const tree = unflatten(list);
36+
37+
const ComentDetail: React.FC<{
38+
data: Node;
39+
}> = ({ data }) => {
40+
const { id, author, content, create_at, children } = data;
41+
42+
return (
43+
<Fragment key={`fragment-${id}`}>
44+
<Divider type="horizontal" key={`divider-${id}`} />
45+
46+
<Comment
47+
key={id}
48+
actions={[
49+
<LikeFilled />,
50+
<EditFilled />,
51+
<DeleteFilled />,
52+
<CommentOutlined
53+
onClick={() => {
54+
onReply(data);
55+
}}
56+
/>,
57+
]}
58+
author={<span>{author.loginname}</span>}
59+
datetime={
60+
<span>{dayjs(create_at).format('YYYY-MM-DD hh:mm:ss')}</span>
61+
}
62+
avatar={<Avatar src={author.avatar_url} alt={author.loginname} />}
63+
content={
64+
<div className={styles.detail}>
65+
<Markdown type="render" value={content} />
66+
</div>
67+
}
68+
>
69+
{replyRender(id)}
70+
71+
{children?.map((item) => (
72+
<ComentDetail key={`detail-${id}-${item.id}`} data={item} />
73+
))}
74+
</Comment>
75+
</Fragment>
76+
);
77+
};
78+
79+
return (
80+
<div className={styles.list}>
81+
{tree.map((item) => (
82+
<ComentDetail key={`list-${item.id}`} data={item} />
83+
))}
84+
</div>
85+
);
86+
};
87+
88+
export default CommentList;
89+
90+
interface Props {
91+
list: ReplyModel[];
92+
93+
onReply: (record: Node) => void;
94+
replyRender: (id: string) => React.ReactNode;
95+
}
96+
97+
interface Node extends ReplyModel {
98+
children?: Node[];
99+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /