node 6 years ago
parent
commit
ed51f5b850

+ 4
- 0
README.md View File

21
 | showList   | boolean | true                               | false    | 是否显示评论列表                             |
21
 | showList   | boolean | true                               | false    | 是否显示评论列表                             |
22
 | showEditor | boolean | true                               | false    | 是否显示评论输入框                           |
22
 | showEditor | boolean | true                               | false    | 是否显示评论输入框                           |
23
 | showHeader | boolean | true                               | false    | 是否显示评论顶部的提示                       |
23
 | showHeader | boolean | true                               | false    | 是否显示评论顶部的提示                       |
24
+| showAlertComment | boolean | false                               | false    | 评论成功之后,是否通过 Antd 的 Message 组件进行提示  |
25
+| showAlertReply | boolean | false                               | false    | 回复成功之后,是否通过 Antd 的 Message 组件进行提示  |
26
+| showAlertFavor | boolean | false                               | false    | 点赞/取消点赞成功之后,是否通过 Antd 的 Message 组件进行提示  |
24
 | token      | string  |                                    | false    |  [deprecate] token,用于身份认证,非必须。默认使用 cookie |
27
 | token      | string  |                                    | false    |  [deprecate] token,用于身份认证,非必须。默认使用 cookie |
25
 
28
 
26
 
29
 
33
 | placeholder   | string          | 说点什么吧... | false    | 评论的中的提示文字                                                                                |
36
 | placeholder   | string          | 说点什么吧... | false    | 评论的中的提示文字                                                                                |
34
 | showEmoji     | boolean         | true          | false    | 是否显示 Toolbar 中表情工具                                                                       |
37
 | showEmoji     | boolean         | true          | false    | 是否显示 Toolbar 中表情工具                                                                       |
35
 | showUpload    | boolean         | true          | false    | 是否显示 Toolbar 中 上传图片工具                                                                  |
38
 | showUpload    | boolean         | true          | false    | 是否显示 Toolbar 中 上传图片工具                                                                  |
39
+| maxUpload    | number         |  1          | false    | 最大能够上传的图片数量                                                                  |
36
 | value         | string          |               | false    | 编辑器的值。如果设置了该属性,则编辑器变为受控组件,需要父组件来维护 value                        |
40
 | value         | string          |               | false    | 编辑器的值。如果设置了该属性,则编辑器变为受控组件,需要父组件来维护 value                        |
37
 | onChange      | function(value) |               | false    | 编辑器内容改变的回调函数                                                                          |
41
 | onChange      | function(value) |               | false    | 编辑器内容改变的回调函数                                                                          |
38
 | onSubmit      | function({ text, files }) |               | false    | 点击提交按钮的回调函数,text 为编辑器的文本,files 为上传的文件列表                           |
42
 | onSubmit      | function({ text, files }) |               | false    | 点击提交按钮的回调函数,text 为编辑器的文本,files 为上传的文件列表                           |

+ 17
- 5
src/App.js View File

171
       withCredentials: true
171
       withCredentials: true
172
     })
172
     })
173
       .then(response => {
173
       .then(response => {
174
-        message.success("评论成功!");
174
+        if (this.props.showAlertComment) {
175
+          message.success("评论成功!");
176
+        }
175
         if (isFunction(cb)) cb();
177
         if (isFunction(cb)) cb();
176
         // 将数据写入到 list 中
178
         // 将数据写入到 list 中
177
         // 临时插入
179
         // 临时插入
210
       withCredentials: true
212
       withCredentials: true
211
     })
213
     })
212
       .then(response => {
214
       .then(response => {
213
-        message.success("回复成功!");
215
+        if (this.props.showAlertReply) {
216
+          message.success("回复成功!");
217
+        }
214
         if (isFunction(cb)) cb();
218
         if (isFunction(cb)) cb();
215
         // 将数据写入到 list 中
219
         // 将数据写入到 list 中
216
         // 临时插入
220
         // 临时插入
253
       withCredentials: true
257
       withCredentials: true
254
     })
258
     })
255
       .then(response => {
259
       .then(response => {
256
-        message.success(favored ? "取消点赞成功!" : "点赞成功!");
260
+        if (this.props.showAlertFavor) {
261
+          message.success(favored ? "取消点赞成功!" : "点赞成功!");
262
+        }
257
         // 更新 list 中的该项数据的 favored
263
         // 更新 list 中的该项数据的 favored
258
         const list = this.state.list.map(item => {
264
         const list = this.state.list.map(item => {
259
           if (item.id === commentId) {
265
           if (item.id === commentId) {
392
   API: PropTypes.string, // 评论的 API 前缀
398
   API: PropTypes.string, // 评论的 API 前缀
393
   showList: PropTypes.bool, // 是否显示评论列表
399
   showList: PropTypes.bool, // 是否显示评论列表
394
   showEditor: PropTypes.bool, // 是否显示评论输入框
400
   showEditor: PropTypes.bool, // 是否显示评论输入框
395
-  showHeader: PropTypes.bool // 是否显示评论顶部的提示
401
+  showHeader: PropTypes.bool, // 是否显示评论顶部的提示
402
+  showAlertComment: PropTypes.bool, // 评论成功之后,是否通过 Antd 的 Message 组件进行提示
403
+  showAlertReply: PropTypes.bool, // 回复成功之后,是否通过 Antd 的 Message 组件进行提示
404
+  showAlertFavor: PropTypes.bool // 点赞/取消点赞成功之后,是否通过 Antd 的 Message 组件进行提示
396
 };
405
 };
397
 
406
 
398
 App.defaultProps = {
407
 App.defaultProps = {
399
   API: "http://api.links123.net/comment/v1",
408
   API: "http://api.links123.net/comment/v1",
400
   showList: true,
409
   showList: true,
401
   showEditor: true,
410
   showEditor: true,
402
-  showHeader: true
411
+  showHeader: true,
412
+  showAlertComment: false,
413
+  showAlertReply: false,
414
+  showAlertFavor: false
403
 };
415
 };
404
 
416
 
405
 export { Editor, RenderText };
417
 export { Editor, RenderText };

+ 7
- 2
src/components/CommentInput/index.js View File

1
 import React, { Component } from "react";
1
 import React, { Component } from "react";
2
 import PropTypes from "prop-types";
2
 import PropTypes from "prop-types";
3
+import { IMAGE_SPLIT } from "../../constant";
3
 import Comment from "../../Comment";
4
 import Comment from "../../Comment";
4
 
5
 
5
 class CommentInput extends Component {
6
 class CommentInput extends Component {
17
   handleSubmit({ text, files }, cb) {
18
   handleSubmit({ text, files }, cb) {
18
     let value = text;
19
     let value = text;
19
     if (files && files.length) {
20
     if (files && files.length) {
20
-      value += "<br /><br />";
21
+      value += IMAGE_SPLIT;
21
       files.forEach(file => {
22
       files.forEach(file => {
22
-        value += `[${file}]`;
23
+        value += `${file},`;
23
       });
24
       });
24
     }
25
     }
26
+    if (value.substr(-1) === ",") {
27
+      value = value.slice(0, -1);
28
+    }
29
+
25
     const { action, commentId, replyId, callback } = this.props;
30
     const { action, commentId, replyId, callback } = this.props;
26
     if (action === "comment") {
31
     if (action === "comment") {
27
       this.props.app.sCreateComment(
32
       this.props.app.sCreateComment(

+ 32
- 4
src/components/ContentItem/index.js View File

8
 import CommentInput from "../CommentInput";
8
 import CommentInput from "../CommentInput";
9
 import avatar from "../../avatar";
9
 import avatar from "../../avatar";
10
 import { renderContent } from "../../helper";
10
 import { renderContent } from "../../helper";
11
+import { IMAGE_SPLIT } from "../../constant";
11
 import "./index.css";
12
 import "./index.css";
12
 
13
 
13
 dayjs.locale("zh-cn");
14
 dayjs.locale("zh-cn");
31
     let newText = text;
32
     let newText = text;
32
     const { reply } = content;
33
     const { reply } = content;
33
     if (reply) {
34
     if (reply) {
34
-      newText = `${newText} //@<a href="/${reply.user_id}">${
35
-        reply.user_name
36
-      }</a> ${reply.content}`;
35
+      // newText = `${newText} //@<a href="/${reply.user_id}">${
36
+      //   reply.user_name
37
+      // }</a> ${reply.content}`;
38
+      newText = `${newText} //@${reply.user_name} ${reply.content}`;
39
+      // newText = (
40
+      //   <span>
41
+      //     {newText}
42
+      //     @<a href={`/${reply.user_id}`}>{reply.user_name}</a>{reply.content}
43
+      //   </span>
44
+      // )
37
       if (reply.reply) {
45
       if (reply.reply) {
38
         return this.renderTextWithReply(newText, reply);
46
         return this.renderTextWithReply(newText, reply);
39
       }
47
       }
54
 
62
 
55
     const { showInput } = this.state;
63
     const { showInput } = this.state;
56
 
64
 
65
+    let newContent = content.content;
66
+    let images = "";
67
+    if (newContent.indexOf(IMAGE_SPLIT) !== -1) {
68
+      newContent = newContent.split(IMAGE_SPLIT);
69
+      images = newContent.pop();
70
+      newContent = newContent.join("");
71
+    }
72
+
57
     return (
73
     return (
58
       <div className="comment-item-box">
74
       <div className="comment-item-box">
59
         <div className="comment-item-left">
75
         <div className="comment-item-left">
80
             className="comment-item-content"
96
             className="comment-item-content"
81
             dangerouslySetInnerHTML={{
97
             dangerouslySetInnerHTML={{
82
               __html: renderContent(
98
               __html: renderContent(
83
-                this.renderTextWithReply(content.content, content)
99
+                this.renderTextWithReply(newContent, content)
84
               )
100
               )
85
             }}
101
             }}
86
           />
102
           />
103
+          <div className="comment-item-image">
104
+            {images.split(",").map((item, index) => {
105
+              return (
106
+                <img
107
+                  key={index}
108
+                  src={item}
109
+                  alt={item}
110
+                  style={{ maxWidth: "100%" }}
111
+                />
112
+              );
113
+            })}
114
+          </div>
87
           <div className="comment-item-bottom">
115
           <div className="comment-item-bottom">
88
             {content.reply_count ? (
116
             {content.reply_count ? (
89
               <div>
117
               <div>

+ 3
- 4
src/components/Editor/Upload.js View File

6
   OSS_ENDPOINT,
6
   OSS_ENDPOINT,
7
   OSS_BUCKET,
7
   OSS_BUCKET,
8
   DRIVER_LICENSE_PATH,
8
   DRIVER_LICENSE_PATH,
9
-  ERROR_DEFAULT,
10
-  MAX_UPLOAD_NUMBER
9
+  ERROR_DEFAULT
11
 } from "../../constant";
10
 } from "../../constant";
12
 import Comment from "../../Comment";
11
 import Comment from "../../Comment";
13
 import "./Upload.css";
12
 import "./Upload.css";
97
 
96
 
98
   render() {
97
   render() {
99
     const { previewVisible, previewImage } = this.state;
98
     const { previewVisible, previewImage } = this.state;
100
-    const { fileList } = this.props;
99
+    const { fileList, maxUpload } = this.props;
101
     const uploadButton = (
100
     const uploadButton = (
102
       <div>
101
       <div>
103
         <Icon type="plus" />
102
         <Icon type="plus" />
114
           onPreview={this.handlePreview}
113
           onPreview={this.handlePreview}
115
           onChange={this.handleChange}
114
           onChange={this.handleChange}
116
         >
115
         >
117
-          {fileList.length >= MAX_UPLOAD_NUMBER ? null : uploadButton}
116
+          {fileList.length >= maxUpload ? null : uploadButton}
118
         </Upload>
117
         </Upload>
119
         <Modal
118
         <Modal
120
           visible={previewVisible}
119
           visible={previewVisible}

+ 14
- 6
src/components/Editor/index.js View File

2
 import PropTypes from "prop-types";
2
 import PropTypes from "prop-types";
3
 import { Icon, Button, Popover, Input } from "antd";
3
 import { Icon, Button, Popover, Input } from "antd";
4
 import { OSS_LINK } from "../../constant";
4
 import { OSS_LINK } from "../../constant";
5
-import { MAX_UPLOAD_NUMBER } from "../../constant";
6
 import { isFunction } from "../../helper";
5
 import { isFunction } from "../../helper";
7
 import Upload from "./Upload";
6
 import Upload from "./Upload";
8
 import Emoji from "./Emoji";
7
 import Emoji from "./Emoji";
124
       rows,
123
       rows,
125
       showEmoji,
124
       showEmoji,
126
       showUpload,
125
       showUpload,
126
+      maxUpload,
127
       btnSubmitText,
127
       btnSubmitText,
128
       btnLoading,
128
       btnLoading,
129
       btnDisabled,
129
       btnDisabled,
170
                 overlayStyle={{ zIndex: 999 }}
170
                 overlayStyle={{ zIndex: 999 }}
171
                 content={
171
                 content={
172
                   <div
172
                   <div
173
-                    style={{ width: 112 * MAX_UPLOAD_NUMBER, minHeight: 100 }}
173
+                    style={{
174
+                      width: 112 * maxUpload,
175
+                      minHeight: 100,
176
+                      margin: "0 auto"
177
+                    }}
174
                   >
178
                   >
175
                     <Upload
179
                     <Upload
176
                       onChangeFileList={this.handleChangeFileList}
180
                       onChangeFileList={this.handleChangeFileList}
177
                       onUpload={this.handleUpload}
181
                       onUpload={this.handleUpload}
182
+                      maxUpload={maxUpload}
178
                       fileList={this.state.fileList}
183
                       fileList={this.state.fileList}
179
                     />
184
                     />
180
                   </div>
185
                   </div>
184
                   <div style={{ margin: "5px auto" }}>
189
                   <div style={{ margin: "5px auto" }}>
185
                     <span>
190
                     <span>
186
                       上传图片
191
                       上传图片
187
-                      <span style={{ color: "#666", fontWeight: 400 }}>
188
-                        (您还能上传{MAX_UPLOAD_NUMBER -
189
-                          this.state.fileList.length}张图片)
190
-                      </span>
192
+                      {maxUpload >= 2 ? (
193
+                        <span style={{ color: "#666", fontWeight: 400 }}>
194
+                          (您还能上传{maxUpload - this.state.fileList.length}张图片)
195
+                        </span>
196
+                      ) : null}
191
                     </span>
197
                     </span>
192
                     <Icon
198
                     <Icon
193
                       type="close"
199
                       type="close"
244
   placeholder: PropTypes.string,
250
   placeholder: PropTypes.string,
245
   showEmoji: PropTypes.bool,
251
   showEmoji: PropTypes.bool,
246
   showUpload: PropTypes.bool,
252
   showUpload: PropTypes.bool,
253
+  maxUpload: PropTypes.number,
247
   value: PropTypes.string,
254
   value: PropTypes.string,
248
   onChange: PropTypes.func,
255
   onChange: PropTypes.func,
249
   onSubmit: PropTypes.func,
256
   onSubmit: PropTypes.func,
260
   placeholder: "说点什么吧...",
267
   placeholder: "说点什么吧...",
261
   showEmoji: true,
268
   showEmoji: true,
262
   showUpload: true,
269
   showUpload: true,
270
+  maxUpload: 1,
263
   btnSubmitText: "发表",
271
   btnSubmitText: "发表",
264
   btnLoading: false,
272
   btnLoading: false,
265
   btnDisabled: false
273
   btnDisabled: false

+ 2
- 0
src/constant.js View File

13
 export const REGEXP = /\[.+?\]/g;
13
 export const REGEXP = /\[.+?\]/g;
14
 
14
 
15
 export const AVATAR = "";
15
 export const AVATAR = "";
16
+
17
+export const IMAGE_SPLIT = "IMAGE_SPLIT";

+ 17
- 7
src/helper.js View File

1
-import { REGEXP } from "./constant";
1
+import { REGEXP, IMAGE_SPLIT } from "./constant";
2
 import emoji, { prefixUrl, ext } from "./emoji";
2
 import emoji, { prefixUrl, ext } from "./emoji";
3
 
3
 
4
+const emojiObejct = arrayToObject(emoji, "title");
5
+
4
 export function isFunction(functionToCheck) {
6
 export function isFunction(functionToCheck) {
5
   return (
7
   return (
6
     functionToCheck && {}.toString.call(functionToCheck) === "[object Function]"
8
     functionToCheck && {}.toString.call(functionToCheck) === "[object Function]"
33
  */
35
  */
34
 export function htmlEncode(str) {
36
 export function htmlEncode(str) {
35
   if (!str) return "";
37
   if (!str) return "";
36
-  // /[\u00A0-\u9999<>\&]/gim  // 中文和 HTML 字符
37
-  return str.replace(/[<>\&]/gim, function(i) {
38
+  return str.replace(/<\/?(?!a)\w*\b[^>]*>/gim, function(i) {
38
     return "&#" + i.charCodeAt(0) + ";";
39
     return "&#" + i.charCodeAt(0) + ";";
39
   });
40
   });
40
 }
41
 }
45
  * @param {strig} content
46
  * @param {strig} content
46
  */
47
  */
47
 export function renderContent(content, onClick) {
48
 export function renderContent(content, onClick) {
48
-  return htmlEncode(content).replace(REGEXP, function(a, b) {
49
+  let newContent = content;
50
+  if (newContent.indexOf(IMAGE_SPLIT) !== -1) {
51
+    newContent = newContent.split(IMAGE_SPLIT);
52
+    newContent.pop();
53
+    newContent = newContent.join("");
54
+  }
55
+
56
+  return htmlEncode(newContent).replace(REGEXP, function(a, b) {
49
     const src = a.slice(1, -1);
57
     const src = a.slice(1, -1);
58
+
59
+    // 兼容旧的评
60
+    // 因为旧的评论用 [img url] 方式存储的
50
     if (isUrl(src)) {
61
     if (isUrl(src)) {
51
-      return `<img src="${src}" alt="${src}" style="max-width: 300px" />`;
62
+      return `<br/><img src="${src}" alt="${src}" style="max-width: 100%" />`;
52
     }
63
     }
53
-    const emojiObejct = arrayToObject(emoji, "title");
54
-    const value = emojiObejct[src].value;
64
+    const value = emojiObejct[src] ? emojiObejct[src].value : src;
55
     return `<img src="${prefixUrl}${value}.${ext}" alt="${value}" />`;
65
     return `<img src="${prefixUrl}${value}.${ext}" alt="${value}" />`;
56
   });
66
   });
57
 }
67
 }

+ 1
- 1
src/index.js View File

34
     // 最简单的用法
34
     // 最简单的用法
35
     return (
35
     return (
36
       <App type={1} businessId="test">
36
       <App type={1} businessId="test">
37
-        <Editor />
37
+        <Editor maxUpload={4} />
38
       </App>
38
       </App>
39
     );
39
     );
40
 
40