node 5 years ago
parent
commit
ed51f5b850

+ 4
- 0
README.md View File

@@ -21,6 +21,9 @@ import Comment, { Editor, RenderText } from 'comment';
21 21
 | showList   | boolean | true                               | false    | 是否显示评论列表                             |
22 22
 | showEditor | boolean | true                               | false    | 是否显示评论输入框                           |
23 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 27
 | token      | string  |                                    | false    |  [deprecate] token,用于身份认证,非必须。默认使用 cookie |
25 28
 
26 29
 
@@ -33,6 +36,7 @@ import Comment, { Editor, RenderText } from 'comment';
33 36
 | placeholder   | string          | 说点什么吧... | false    | 评论的中的提示文字                                                                                |
34 37
 | showEmoji     | boolean         | true          | false    | 是否显示 Toolbar 中表情工具                                                                       |
35 38
 | showUpload    | boolean         | true          | false    | 是否显示 Toolbar 中 上传图片工具                                                                  |
39
+| maxUpload    | number         |  1          | false    | 最大能够上传的图片数量                                                                  |
36 40
 | value         | string          |               | false    | 编辑器的值。如果设置了该属性,则编辑器变为受控组件,需要父组件来维护 value                        |
37 41
 | onChange      | function(value) |               | false    | 编辑器内容改变的回调函数                                                                          |
38 42
 | onSubmit      | function({ text, files }) |               | false    | 点击提交按钮的回调函数,text 为编辑器的文本,files 为上传的文件列表                           |

+ 17
- 5
src/App.js View File

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

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

@@ -1,5 +1,6 @@
1 1
 import React, { Component } from "react";
2 2
 import PropTypes from "prop-types";
3
+import { IMAGE_SPLIT } from "../../constant";
3 4
 import Comment from "../../Comment";
4 5
 
5 6
 class CommentInput extends Component {
@@ -17,11 +18,15 @@ class CommentInput extends Component {
17 18
   handleSubmit({ text, files }, cb) {
18 19
     let value = text;
19 20
     if (files && files.length) {
20
-      value += "<br /><br />";
21
+      value += IMAGE_SPLIT;
21 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 30
     const { action, commentId, replyId, callback } = this.props;
26 31
     if (action === "comment") {
27 32
       this.props.app.sCreateComment(

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

@@ -8,6 +8,7 @@ import Comment from "../../Comment";
8 8
 import CommentInput from "../CommentInput";
9 9
 import avatar from "../../avatar";
10 10
 import { renderContent } from "../../helper";
11
+import { IMAGE_SPLIT } from "../../constant";
11 12
 import "./index.css";
12 13
 
13 14
 dayjs.locale("zh-cn");
@@ -31,9 +32,16 @@ class CommentItem extends Component {
31 32
     let newText = text;
32 33
     const { reply } = content;
33 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 45
       if (reply.reply) {
38 46
         return this.renderTextWithReply(newText, reply);
39 47
       }
@@ -54,6 +62,14 @@ class CommentItem extends Component {
54 62
 
55 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 73
     return (
58 74
       <div className="comment-item-box">
59 75
         <div className="comment-item-left">
@@ -80,10 +96,22 @@ class CommentItem extends Component {
80 96
             className="comment-item-content"
81 97
             dangerouslySetInnerHTML={{
82 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 115
           <div className="comment-item-bottom">
88 116
             {content.reply_count ? (
89 117
               <div>

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

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

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

@@ -2,7 +2,6 @@ import React from "react";
2 2
 import PropTypes from "prop-types";
3 3
 import { Icon, Button, Popover, Input } from "antd";
4 4
 import { OSS_LINK } from "../../constant";
5
-import { MAX_UPLOAD_NUMBER } from "../../constant";
6 5
 import { isFunction } from "../../helper";
7 6
 import Upload from "./Upload";
8 7
 import Emoji from "./Emoji";
@@ -124,6 +123,7 @@ class Editor extends React.Component {
124 123
       rows,
125 124
       showEmoji,
126 125
       showUpload,
126
+      maxUpload,
127 127
       btnSubmitText,
128 128
       btnLoading,
129 129
       btnDisabled,
@@ -170,11 +170,16 @@ class Editor extends React.Component {
170 170
                 overlayStyle={{ zIndex: 999 }}
171 171
                 content={
172 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 179
                     <Upload
176 180
                       onChangeFileList={this.handleChangeFileList}
177 181
                       onUpload={this.handleUpload}
182
+                      maxUpload={maxUpload}
178 183
                       fileList={this.state.fileList}
179 184
                     />
180 185
                   </div>
@@ -184,10 +189,11 @@ class Editor extends React.Component {
184 189
                   <div style={{ margin: "5px auto" }}>
185 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 197
                     </span>
192 198
                     <Icon
193 199
                       type="close"
@@ -244,6 +250,7 @@ Editor.propTypes = {
244 250
   placeholder: PropTypes.string,
245 251
   showEmoji: PropTypes.bool,
246 252
   showUpload: PropTypes.bool,
253
+  maxUpload: PropTypes.number,
247 254
   value: PropTypes.string,
248 255
   onChange: PropTypes.func,
249 256
   onSubmit: PropTypes.func,
@@ -260,6 +267,7 @@ Editor.defaultProps = {
260 267
   placeholder: "说点什么吧...",
261 268
   showEmoji: true,
262 269
   showUpload: true,
270
+  maxUpload: 1,
263 271
   btnSubmitText: "发表",
264 272
   btnLoading: false,
265 273
   btnDisabled: false

+ 2
- 0
src/constant.js View File

@@ -13,3 +13,5 @@ export const MAX_UPLOAD_NUMBER = 4;
13 13
 export const REGEXP = /\[.+?\]/g;
14 14
 
15 15
 export const AVATAR = "";
16
+
17
+export const IMAGE_SPLIT = "IMAGE_SPLIT";

+ 17
- 7
src/helper.js View File

@@ -1,6 +1,8 @@
1
-import { REGEXP } from "./constant";
1
+import { REGEXP, IMAGE_SPLIT } from "./constant";
2 2
 import emoji, { prefixUrl, ext } from "./emoji";
3 3
 
4
+const emojiObejct = arrayToObject(emoji, "title");
5
+
4 6
 export function isFunction(functionToCheck) {
5 7
   return (
6 8
     functionToCheck && {}.toString.call(functionToCheck) === "[object Function]"
@@ -33,8 +35,7 @@ export function arrayToObject(array, keyField) {
33 35
  */
34 36
 export function htmlEncode(str) {
35 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 39
     return "&#" + i.charCodeAt(0) + ";";
39 40
   });
40 41
 }
@@ -45,13 +46,22 @@ export function htmlEncode(str) {
45 46
  * @param {strig} content
46 47
  */
47 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 57
     const src = a.slice(1, -1);
58
+
59
+    // 兼容旧的评
60
+    // 因为旧的评论用 [img url] 方式存储的
50 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 65
     return `<img src="${prefixUrl}${value}.${ext}" alt="${value}" />`;
56 66
   });
57 67
 }

+ 1
- 1
src/index.js View File

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