Browse Source

Merge branch 'feature/editor' of node/npmcomment into master

node 5 years ago
parent
commit
bc37f91407
53 changed files with 1277 additions and 835 deletions
  1. 27
    0
      CHANGELOG.md
  2. 125
    53
      README.md
  3. 13
    0
      lib/App.css
  4. 129
    39
      lib/App.js
  5. 1
    1
      lib/App.js.map
  6. 9
    0
      lib/avatar.js
  7. 1
    0
      lib/avatar.js.map
  8. 11
    10
      lib/components/CommentBox/index.css
  9. 9
    8
      lib/components/CommentBox/index.js
  10. 1
    1
      lib/components/CommentBox/index.js.map
  11. 24
    121
      lib/components/CommentInput/index.js
  12. 1
    1
      lib/components/CommentInput/index.js.map
  13. 2
    2
      lib/components/CommentList/index.css
  14. 11
    3
      lib/components/CommentList/index.js
  15. 1
    1
      lib/components/CommentList/index.js.map
  16. 20
    17
      lib/components/ContentItem/index.css
  17. 66
    35
      lib/components/ContentItem/index.js
  18. 1
    1
      lib/components/ContentItem/index.js.map
  19. 0
    2
      lib/components/Editor/Upload.js
  20. 1
    1
      lib/components/Editor/Upload.js.map
  21. 23
    29
      lib/components/Editor/index.css
  22. 158
    52
      lib/components/Editor/index.js
  23. 1
    1
      lib/components/Editor/index.js.map
  24. 2
    0
      lib/constant.js
  25. 1
    1
      lib/constant.js.map
  26. 1
    1
      lib/emoji.js
  27. 1
    1
      lib/emoji.js.map
  28. 97
    19
      lib/index.js
  29. 1
    1
      lib/index.js.map
  30. 1
    0
      lib/lang/index.js
  31. 1
    1
      lib/lang/index.js.map
  32. 25
    25
      lib/mock.js
  33. 1
    1
      lib/mock.js.map
  34. 2
    2
      package.json
  35. 13
    0
      src/App.css
  36. 101
    36
      src/App.js
  37. 0
    12
      src/CHANGELOG.md
  38. 4
    0
      src/avatar.js
  39. 11
    10
      src/components/CommentBox/index.css
  40. 20
    14
      src/components/CommentBox/index.js
  41. 24
    97
      src/components/CommentInput/index.js
  42. 2
    2
      src/components/CommentList/index.css
  43. 14
    9
      src/components/CommentList/index.js
  44. 20
    17
      src/components/ContentItem/index.css
  45. 58
    37
      src/components/ContentItem/index.js
  46. 0
    1
      src/components/Editor/Upload.js
  47. 23
    29
      src/components/Editor/index.css
  48. 139
    47
      src/components/Editor/index.js
  49. 2
    0
      src/constant.js
  50. 1
    1
      src/emoji.js
  51. 76
    21
      src/index.js
  52. 1
    0
      src/lang/index.js
  53. 0
    72
      src/mock.js

+ 27
- 0
CHANGELOG.md View File

@@ -0,0 +1,27 @@
1
+# CHANGELOG
2
+
3
+## 0.2.0
4
+
5
+- [x] feat: 添加 showHeader 属性
6
+- [x] feat: 添加 onSubmit 属性
7
+- [x] feat: 添加 btnSubmitText 属性
8
+- [x] feat: 添加 btnLoading 属性
9
+- [x] feat: 添加 btnDisable 属性
10
+- [x] feat: 添加 button 属性
11
+- [x] feat: 添加 emojiToolIcon 属性
12
+- [x] feat: 添加 imageToolIcon 属性
13
+- [x] refactor: 完全解藕编辑器组件,由 Comment 实现评论逻辑
14
+- [x] fixed: 发呆表情变成了 undefined
15
+- [x] fixed: 输入表情之后,之前的文字被覆盖了
16
+- [x] fixed: 统一前后端错误提示
17
+- [x] styles: 样式都以 `comment` 开头
18
+
19
+## 0.1.0
20
+
21
+- [x] 不兼容更新。
22
+- [x] 添加了很多 `Props`
23
+- [x] 组件导出为 `Comment` 和 `Editor`
24
+
25
+
26
+
27
+

+ 125
- 53
README.md View File

@@ -1,5 +1,116 @@
1 1
 # Comment
2
-通用评论系统
2
+
3
+通用评论系统及编辑器
4
+
5
+`version 0.2.0`
6
+
7
+```js
8
+import Comment, { Editor } from 'comment';
9
+```
10
+
11
+
12
+## Props
13
+
14
+#### Comment
15
+
16
+
17
+| props | type   | default  | required | description |
18
+| ----- | -------| -------- | -------- | ----------- |
19
+| type  | number |          | true     | 评论的 type  |
20
+| businessId | string |     | true     | 评论的 business id|
21
+| API  | string | http://api.links123.net/comment/v1 | false | API 前缀|
22
+| showList | boolean |   true  | false     | 是否显示评论列表|
23
+| showEditor | boolean |   true  | false     | 是否显示评论输入框|
24
+| showHeader | boolean |   true  | false     | 是否显示评论顶部的提示|
25
+
26
+
27
+
28
+##### Editor
29
+
30
+| props | type   | default  | required | description |
31
+| ----- | -------| -------- | -------- | ----------- |
32
+| rows  | number |       5   | false     | 编辑器的高度。默认情况下,回复评论/回复回复的编辑器会比评论的编辑器高度小一行 |
33
+| placeholder | string |  说点什么吧...   | false     | 评论的中的提示文字|
34
+| showEmoji | boolean |   true  | false     | 是否显示 Toolbar 中表情工具|
35
+| showUpload | boolean |   true  | false     | 是否显示 Toolbar 中 上传图片工具|
36
+| value | string |     | false     | 编辑器的值。如果设置了改属性,则编辑器变为受控组件,需要父组件来维护 value|
37
+| onChange | function(value) |     | false     | 编辑器内容改变的回调函数|
38
+| onSubmit | function(value)  |     | false     | 点击提交按钮的回调函数|
39
+| btnSubmitText | string  | 发表    | false     | 提交按钮的文字|
40
+| btnLoading | boolean  | false    | false     | 按钮是否正在加载中|
41
+| btnDisable | boolean  | false    | false     | 按钮是否禁用|
42
+| button | ReactNode  |     | false     | 按钮组件。如果上面几个 btn 相关的属性都无法满足要求,则可以使用 button 来自定义提交编辑器值的按钮|
43
+| emojiToolIcon | ReactNode  |     | false     | Toolbar 中表情的图标 |
44
+| imageToolIcon | ReactNode  |     | false     | Toolbar 中上传文件的图标 |
45
+
46
+### 什么时候不要使用 value/onChange/onSubmit
47
+
48
+如果将 `comment` 作为通用评论组件,则不要使用 `value` `onChange` `onSubmit`。因为组件内部,实现了通用评论的业务逻辑。
49
+
50
+**可以使用 value/onChange/onSubmit 的情况:**
51
+
52
+- 单独使用其中的 `Editor`。即 `import { Editor } from 'comment'`
53
+- 不需要展示评论列表,即设置 `showList: false`
54
+
55
+```jsx
56
+// 单独使用 Editor
57
+<Editor value="xxx" onChange={(v) => console.log(v)} />
58
+
59
+// 不需要展示评论列表
60
+
61
+<Comment type={1}  showList={false}>
62
+  <Editor value="xxx" onChange={(v) => console.log(v)} />
63
+</Comment>
64
+
65
+```
66
+
67
+### button 
68
+
69
+如果使用 `button`,则 `btnLoading` `btnDisable` `btnSubmitText` 都会失效。因为这些属性是针对于编辑器默认的提交按钮设置的。
70
+
71
+所以如果要提交编辑器的值,需要自己在 `Button` 组件上实现提交功能。编辑器的值,可以通过 `onChange` 方法获取到。
72
+
73
+如果使用了 `button` 属性,并且没有为其设置 `onClick` 方法,则 `onClick` 默认为发布评论,即点击按钮会发表评论:
74
+
75
+```jsx
76
+// 如下代码所示
77
+// 点击“自定义按钮”的时候,会发表评论。这是由 Comment 组件内部实现的业务逻辑
78
+<Comment type={1} businessId="test" showList={false}>
79
+  <Editor 
80
+    button={(
81
+      <Button
82
+        type="primary"
83
+        ghost
84
+      >
85
+        自定义按钮
86
+      </Button>
87
+    )}
88
+  />
89
+</Comment>
90
+```
91
+
92
+如果使用了 `button` 属性,并且设置了 `onClick` 方法,则会覆盖默认的 `onClick` 方法:
93
+
94
+```jsx
95
+// 下面的代码,点击的时候,不会提交评论
96
+// 而是会输出 state 的值(
97
+// 即编辑器的值,因为 onChange 将编辑器输入的值通过回调函数传递给了父组件)
98
+<Comment type={1} businessId="test" showList={false}>
99
+  <Editor 
100
+    button={(
101
+      <Button
102
+        type="primary"
103
+        ghost
104
+        value={this.state.value}
105
+        onChange={(value) => this.setState({ value })}
106
+        onClick={() => console.log(this.state.value)}
107
+      >
108
+        自定义按钮
109
+      </Button>
110
+    )}
111
+  />
112
+</Comment>
113
+```
3 114
 
4 115
 
5 116
 ## 使用
@@ -23,39 +134,24 @@ import Comment, { Editor } from 'comment'
23 134
 
24 135
 render() {
25 136
   return (
26
-    <Comment>
137
+    <Comment type={1} businessId="test">
27 138
       <Editor />
28 139
     </Comment>
29 140
   )
30 141
 }
31 142
 ```
32 143
 
33
-**注意:最,还需要在 HTML 文件里面引入阿里云 OSS SDK `<script src="http://gosspublic.alicdn.com/aliyun-oss-sdk.min.js"></script>`**
144
+**注意:最,还需要在 HTML 文件里面引入阿里云 OSS SDK `<script src="http://gosspublic.alicdn.com/aliyun-oss-sdk.min.js"></script>`**
34 145
 
35 146
 
36 147
 ### 作为静态文件引入
37 148
 
38
-作为静态问卷使用的话,首先需要在 `index.js` 里面设置好组件的 `props`:
149
+作为静态问卷使用的话,首先需要在 `index.js` 里面设置好组件的 `props`,主要是 `type` 和 `businessId`
39 150
 
40 151
 ```js
41 152
 // ...
42
-const props = {
43
-  type: 1,
44
-  businessId: "1",
45
-  API: "http://api.links123.net/comment/v1",
46
-  showList: false
47
-};
48
-
49
-const editorProps = {
50
-  showEmoji: true,
51
-  placeholder: "说点什么吧",
52
-  rows: 5,
53
-};
54
-
55
-
56
-// ...
57
-<App {...props}>
58
-    <Editor {...editorProps} />
153
+<App type={1} businessId="test">
154
+    <Editor />
59 155
 </App>
60 156
 ```
61 157
 
@@ -65,10 +161,10 @@ const editorProps = {
65 161
 $ yarn build
66 162
 ```
67 163
 
68
-打包后会得到静态问卷,分别通过 link 和 script 标签在你的项目里面引入打包后的文件。
164
+打包后会得到静态文件,分别通过 link 和 script 标签在你的项目里面引入打包后的文件。
69 165
 
70 166
 
71
-然后还需要引入 OSS SDK,用于直传文件到 OSS。
167
+接下来还需要引入 OSS SDK,用于直传文件到 OSS。
72 168
 
73 169
 
74 170
 最后创建一个 id 为 `root-comment` 的标签,作为渲染通用评论的跟元素。
@@ -93,33 +189,6 @@ $ yarn build
93 189
 </html>
94 190
 ```
95 191
 
96
-## Props
97
-
98
-#### Comment
99
-
100
-
101
-| props | type   | default  | required | description |
102
-| ----- | -------| -------- | -------- | ----------- |
103
-| type  | number |          | true     | 评论的 type  |
104
-| businessId | string |     | true     | 评论的 business id|
105
-| API  | string | http://api.links123.net/comment/v1 | false | API 前缀|
106
-| showList | boolean |   true  | true     | 是否显示评论列表|
107
-| showEditor | boolean |   true  | true     | 是否显示评论输入框|
108
-
109
-
110
-##### Editor
111
-
112
-| props | type   | default  | required | description |
113
-| ----- | -------| -------- | -------- | ----------- |
114
-| rows  | number |       5   | false     | 编辑器的高度  |
115
-| placeholder | string |  说点什么吧...   | false     | 评论的中的提示文字|
116
-| submitText | string |  发表  | false     | 提交按钮的文字|
117
-| showEmoji | boolean |   true  | false     | 是否显示 Toolbar 中表情工具|
118
-| showUpload | boolean |   true  | false     | 是否显示 Toolbar 中 上传图片工具|
119
-| onChange | function |     | false     | 编辑器内容改变的回调函数|
120
-| onSubmit | function |     | false     | TODO... 尚未完成。点击提交按钮的回调函数|
121
-
122
-
123 192
 ## 开发
124 193
 
125 194
 ```
@@ -130,12 +199,15 @@ $ yarn start
130 199
 ```
131 200
 
132 201
 - `yarn build` 将项目打包成一个单页应用
133
-- `yarn lib` 将项目打包成一个组件
202
+- `yarn lib` 将项目打包成一个 es5 组件
134 203
 - `yarn prettier` 格式化代码
135 204
 
136 205
 ## TODO
137 206
 
138
-- [ ] 前后端统一错误码
207
+- [x] 前后端统一错误码
139 208
 - [x] type 和 businessID 的定义
140
-- [ ] Editor onSubmit 回调
209
+- [x] Editor onSubmit 回调
210
+- [ ] 对评论的回复点赞,报错
211
+- [ ] oss/sts 接口报错
212
+- [ ] 头像 404 `https://links123-images.oss-cn-hangzhou.aliyuncs.com/avatar/`
141 213
 

+ 13
- 0
lib/App.css View File

@@ -7,3 +7,16 @@
7 7
   position: fixed;
8 8
   top: 45%;
9 9
 }
10
+.comment-header-tag {
11
+  border: 1px solid #cecece;
12
+  border-radius: 0;
13
+  color: #666;
14
+}
15
+.comment-header-tip {
16
+  color: #5198eb;
17
+  margin-right: 15px;
18
+  margin-left: 5px;
19
+}
20
+.comment-header-text {
21
+  color: #666;
22
+}

+ 129
- 39
lib/App.js View File

@@ -5,6 +5,10 @@ Object.defineProperty(exports, "__esModule", {
5 5
 });
6 6
 exports.Editor = undefined;
7 7
 
8
+var _tag = require("antd/es/tag");
9
+
10
+var _tag2 = _interopRequireDefault(_tag);
11
+
8 12
 var _message2 = require("antd/es/message");
9 13
 
10 14
 var _message3 = _interopRequireDefault(_message2);
@@ -13,6 +17,8 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument
13 17
 
14 18
 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
15 19
 
20
+require("antd/es/tag/style/css");
21
+
16 22
 require("antd/es/message/style/css");
17 23
 
18 24
 var _react = require("react");
@@ -45,6 +51,10 @@ var _Editor = require("./components/Editor");
45 51
 
46 52
 var _Editor2 = _interopRequireDefault(_Editor);
47 53
 
54
+var _lang = require("./lang");
55
+
56
+var _lang2 = _interopRequireDefault(_lang);
57
+
48 58
 require("./App.css");
49 59
 
50 60
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -82,6 +92,7 @@ var App = function (_Component) {
82 92
     _this.sCreateComment = _this.sCreateComment.bind(_this);
83 93
     _this.sCreateReply = _this.sCreateReply.bind(_this);
84 94
     _this.sCommentFavor = _this.sCommentFavor.bind(_this);
95
+    _this.sReplyFavor = _this.sReplyFavor.bind(_this);
85 96
     _this.sOssSts = _this.sOssSts.bind(_this);
86 97
     return _this;
87 98
   }
@@ -154,10 +165,10 @@ var App = function (_Component) {
154 165
         }
155 166
       }).catch(function (error) {
156 167
         if (error.response && error.response.data && error.response.data.msg) {
157
-          _message3.default.error(error.response.data.msg || _constant.ERROR_DEFAULT);
168
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
158 169
           return;
159 170
         }
160
-        _message3.default.error(error.message || _constant.ERROR_DEFAULT);
171
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
161 172
       }).finally(function () {
162 173
         _this2.handleChangeLoading("sGetComment", false);
163 174
       });
@@ -181,23 +192,25 @@ var App = function (_Component) {
181 192
       var API = this.props.API;
182 193
 
183 194
       _axios2.default.get(API + "/replies?comment_id=" + commentId + "&page=" + page + "&limit=" + _constant.LIMIT).then(function (response) {
184
-        var replies = response.data.list;
185
-
186
-        if (!replies) {
195
+        if (!response.data.list) {
187 196
           _message3.default.info("没有更多数据了!");
188 197
         }
189 198
         var list = _this3.state.list.map(function (item) {
190 199
           if (item.id === commentId) {
191 200
             if (!item.replies) item.replies = [];
192
-            if (replies) {
201
+            if (response.data.list) {
193 202
               if (page === 1) {
194 203
                 // 如果当前页数为第一页,则清空当前所有的 replies
195 204
                 // 并将获取到的 replies 存放在 state
196
-                item.replies = replies;
205
+                item.replies = response.data.list;
197 206
               } else {
198
-                item.replies = item.replies.concat(replies);
207
+                item.replies = item.replies.filter(function (o) {
208
+                  return !o.isTemporary;
209
+                }).concat(response.data.list);
199 210
                 // 如果当前页数非第一页,则合并 replies
200 211
               }
212
+              item.reply_count = response.data.total;
213
+              item.reply_page = response.data.page;
201 214
             } else {
202 215
               item.isNoMoreReply = true;
203 216
             }
@@ -207,10 +220,10 @@ var App = function (_Component) {
207 220
         _this3.setState({ list: list });
208 221
       }).catch(function (error) {
209 222
         if (error.response && error.response.data && error.response.data.msg) {
210
-          _message3.default.error(error.response.data.msg || _constant.ERROR_DEFAULT);
223
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
211 224
           return;
212 225
         }
213
-        _message3.default.error(error.message || _constant.ERROR_DEFAULT);
226
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
214 227
       }).finally(function () {
215 228
         _this3.handleChangeLoading("sGetReply", false);
216 229
       });
@@ -218,14 +231,17 @@ var App = function (_Component) {
218 231
 
219 232
     /**
220 233
      * 添加评论
221
-     * @param {string} content comment content
234
+     * @param {object} {content} comment content
222 235
      */
223 236
 
224 237
   }, {
225 238
     key: "sCreateComment",
226
-    value: function sCreateComment(content) {
239
+    value: function sCreateComment() {
227 240
       var _this4 = this;
228 241
 
242
+      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
243
+          content = _ref3.content;
244
+
229 245
       if (!content) return _message3.default.error("评论内容不能为空 ");
230 246
       this.handleChangeLoading("sCreateComment", true);
231 247
       var _props2 = this.props,
@@ -246,18 +262,20 @@ var App = function (_Component) {
246 262
         // 将数据写入到 list 中
247 263
         // 临时插入
248 264
         // 等到获取数据之后,删除临时数据
249
-        var list = _this4.state.list;
265
+        var _state = _this4.state,
266
+            list = _state.list,
267
+            total = _state.total;
250 268
 
251 269
         list.unshift(_extends({}, response.data, {
252 270
           isTemporary: true // 临时的数据
253 271
         }));
254
-        _this4.setState({ list: list });
272
+        _this4.setState({ list: list, total: total + 1 });
255 273
       }).catch(function (error) {
256 274
         if (error.response && error.response.data && error.response.data.msg) {
257
-          _message3.default.error(error.response.data.msg || _constant.ERROR_DEFAULT);
275
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
258 276
           return;
259 277
         }
260
-        _message3.default.error(error.message || _constant.ERROR_DEFAULT);
278
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
261 279
       }).finally(function () {
262 280
         _this4.handleChangeLoading("sCreateComment", false);
263 281
       });
@@ -274,6 +292,8 @@ var App = function (_Component) {
274 292
     value: function sCreateReply(data, cb) {
275 293
       var _this5 = this;
276 294
 
295
+      console.log("list: ", this.state.list);
296
+
277 297
       if (!data.content) return _message3.default.error("回复内容不能为空 ");
278 298
       this.handleChangeLoading("sCreateReply", true);
279 299
       var API = this.props.API;
@@ -283,32 +303,35 @@ var App = function (_Component) {
283 303
         data: data,
284 304
         withCredentials: true
285 305
       }).then(function (response) {
286
-        // // 将该条数据插入到 list 中
287
-        // const list = this.state.list.map(item => {
288
-        //   if (item.id === data.comment_id) {
289
-        //     if (!item.replies) item.replies = [];
290
-        //     item.reply_count += 1
291
-        //     item.replies.unshift(response.data);
292
-        //   }
293
-        //   return item;
294
-        // });
295
-        // this.setState({ list });
296
-        _this5.sGetReply({ commentId: data.comment_id });
297 306
         _message3.default.success("回复成功!");
298 307
         if ((0, _helper.isFunction)(cb)) cb();
308
+        // 将数据写入到 list 中
309
+        // 临时插入
310
+        // 等到获取数据之后,删除临时数据
311
+        var list = _this5.state.list.map(function (item) {
312
+          if (item.id === data.comment_id) {
313
+            if (!item.replies) item.replies = [];
314
+            item.replies.push(_extends({}, response.data, {
315
+              isTemporary: true // 临时的数据
316
+            }));
317
+            item.reply_count += 1;
318
+          }
319
+          return item;
320
+        });
321
+        _this5.setState({ list: list });
299 322
       }).catch(function (error) {
300 323
         if (error.response && error.response.data && error.response.data.msg) {
301
-          _message3.default.error(error.response.data.msg || _constant.ERROR_DEFAULT);
324
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
302 325
           return;
303 326
         }
304
-        _message3.default.error(error.message || _constant.ERROR_DEFAULT);
327
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
305 328
       }).finally(function () {
306 329
         _this5.handleChangeLoading("sCreateReply", false);
307 330
       });
308 331
     }
309 332
 
310 333
     /**
311
-     * 点赞/取消点赞
334
+     * 评论 点赞/取消点赞
312 335
      * @param {string} commentId { commentId }
313 336
      * @param {boolean} favored   是否已经点过赞
314 337
      */
@@ -337,15 +360,60 @@ var App = function (_Component) {
337 360
         _this6.setState({ list: list });
338 361
       }).catch(function (error) {
339 362
         if (error.response && error.response.data && error.response.data.msg) {
340
-          _message3.default.error(error.response.data.msg || _constant.ERROR_DEFAULT);
363
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
341 364
           return;
342 365
         }
343
-        _message3.default.error(error.message || _constant.ERROR_DEFAULT);
366
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
344 367
       }).finally(function () {
345 368
         _this6.handleChangeLoading("sCommentFavor", false);
346 369
       });
347 370
     }
348 371
 
372
+    /**
373
+     * 回复 点赞/取消点赞
374
+     * @param {string} replyId  replyId
375
+     * @param {string} commentId  commentId
376
+     * @param {boolean} favored   是否已经点过赞
377
+     */
378
+
379
+  }, {
380
+    key: "sReplyFavor",
381
+    value: function sReplyFavor(replyId, commentId, favored) {
382
+      var _this7 = this;
383
+
384
+      this.handleChangeLoading("sReplyFavor", true);
385
+      console.log("replyId, commentId ", replyId, commentId);
386
+
387
+      var API = this.props.API;
388
+
389
+      (0, _axios2.default)(API + "/replies/" + replyId + "/favor", {
390
+        method: favored ? "delete" : "put",
391
+        withCredentials: true
392
+      }).then(function (response) {
393
+        console.log("response: ", response);
394
+
395
+        _message3.default.success(favored ? "取消点赞成功!" : "点赞成功!");
396
+        // TODO: (2018.07.20 node) 对评论的回复点赞,报错
397
+        // // 更新 list 中的该项数据的 favored
398
+        // const list = this.state.list.map(item => {
399
+        //   if (item.id === replyId) {
400
+        //     item.favored = !favored;
401
+        //     item.favor_count += favored ? -1 : 1;
402
+        //   }
403
+        //   return item;
404
+        // });
405
+        // this.setState({ list });
406
+      }).catch(function (error) {
407
+        if (error.response && error.response.data && error.response.data.msg) {
408
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
409
+          return;
410
+        }
411
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
412
+      }).finally(function () {
413
+        _this7.handleChangeLoading("sReplyFavor", false);
414
+      });
415
+    }
416
+
349 417
     /**
350 418
      * 获取 OSS 上传的参数
351 419
      */
@@ -353,21 +421,21 @@ var App = function (_Component) {
353 421
   }, {
354 422
     key: "sOssSts",
355 423
     value: function sOssSts() {
356
-      var _this7 = this;
424
+      var _this8 = this;
357 425
 
358 426
       this.handleChangeLoading("sOssSts", true);
359 427
       var API = this.props.API;
360 428
 
361 429
       _axios2.default.get(API + "/oss/sts").then(function (response) {
362
-        _this7.setState({ oss: _extends({}, response.data) });
430
+        _this8.setState({ oss: _extends({}, response.data) });
363 431
       }).catch(function (error) {
364 432
         if (error.response && error.response.data && error.response.data.msg) {
365
-          _message3.default.error(error.response.data.msg || _constant.ERROR_DEFAULT);
433
+          _message3.default.error(_lang2.default[error.response.data.msg] || _constant.ERROR_DEFAULT);
366 434
           return;
367 435
         }
368
-        _message3.default.error(error.message || _constant.ERROR_DEFAULT);
436
+        _message3.default.error(_lang2.default[error.message] || _constant.ERROR_DEFAULT);
369 437
       }).finally(function () {
370
-        _this7.handleChangeLoading("sOssSts", false);
438
+        _this8.handleChangeLoading("sOssSts", false);
371 439
       });
372 440
     }
373 441
   }, {
@@ -378,6 +446,7 @@ var App = function (_Component) {
378 446
         sCreateComment: this.sCreateComment,
379 447
         sGetComment: this.sGetComment,
380 448
         sCommentFavor: this.sCommentFavor,
449
+        sReplyFavor: this.sReplyFavor,
381 450
         sCreateReply: this.sCreateReply,
382 451
         sGetReply: this.sGetReply,
383 452
         sOssSts: this.sOssSts
@@ -389,6 +458,25 @@ var App = function (_Component) {
389 458
         _react2.default.createElement(
390 459
           "div",
391 460
           { className: "comment" },
461
+          this.props.showHeader && _react2.default.createElement(
462
+            "div",
463
+            { style: { marginBottom: 15 } },
464
+            _react2.default.createElement(
465
+              _tag2.default,
466
+              { className: "comment-header-tag" },
467
+              "\u7559\u8A00"
468
+            ),
469
+            _react2.default.createElement(
470
+              "span",
471
+              { className: "comment-header-tip" },
472
+              "\u53E3\u7891"
473
+            ),
474
+            _react2.default.createElement(
475
+              "span",
476
+              { className: "comment-header-text" },
477
+              "(\u5168\u7AD9\u6311\u51FA\u6BDB\u75C5\u6216\u63D0\u51FA\u5408\u7406\u5EFA\u8BAE\uFF0C\u5956\u52B110\u5230100\u5143\u7EA2\u5305)"
478
+            )
479
+          ),
392 480
           this.props.showEditor && _react2.default.createElement(_CommentInput2.default, { content: this.props.children }),
393 481
           this.props.showList && _react2.default.createElement(
394 482
             "div",
@@ -408,13 +496,15 @@ App.propTypes = {
408 496
   businessId: _propTypes2.default.string.isRequired, // 评论的 business_id
409 497
   API: _propTypes2.default.string, // 评论的 API 前缀
410 498
   showList: _propTypes2.default.bool, // 是否显示评论列表
411
-  showEditor: _propTypes2.default.bool // 是否显示评论输入框
499
+  showEditor: _propTypes2.default.bool, // 是否显示评论输入框
500
+  showHeader: _propTypes2.default.bool // 是否显示评论顶部的提示
412 501
 };
413 502
 
414 503
 App.defaultProps = {
415 504
   API: "http://api.links123.net/comment/v1",
416 505
   showList: true,
417
-  showEditor: true
506
+  showEditor: true,
507
+  showHeader: true
418 508
 };
419 509
 
420 510
 exports.Editor = _Editor2.default;

+ 1
- 1
lib/App.js.map
File diff suppressed because it is too large
View File


+ 9
- 0
lib/avatar.js
File diff suppressed because it is too large
View File


+ 1
- 0
lib/avatar.js.map
File diff suppressed because it is too large
View File


+ 11
- 10
lib/components/CommentBox/index.css View File

@@ -1,4 +1,4 @@
1
-.showMore {
1
+.comment-show-more {
2 2
   color: #4a90e2;
3 3
   text-align: center;
4 4
   width: 100px;
@@ -8,40 +8,41 @@
8 8
   margin: 0 auto;
9 9
   transition: all 0.3s;
10 10
 }
11
-.showMore:hover {
11
+.comment-show-more:hover {
12 12
   background-color: #f5f5f5;
13 13
   color: #1890ff;
14 14
 }
15
-.moreBox {
15
+.comment-more-box {
16 16
   text-align: center;
17 17
   width: 90%;
18
-  margin-left: 40px;
18
+  margin-left: 50px;
19
+  margin-top: 10px;
19 20
   height: 40px;
20 21
   display: inline-block;
21 22
 }
22 23
 @media screen and (max-width: 616px) and (min-width: 449px) {
23
-  .moreBox {
24
+  .comment-more-box {
24 25
     text-align: center;
25 26
     width: 85%;
26
-    margin-left: 40px;
27
+    margin-left: 50px;
27 28
     height: 40px;
28 29
     display: inline-block;
29 30
   }
30 31
 }
31 32
 @media screen and (max-width: 449px) and (min-width: 365px) {
32
-  .moreBox {
33
+  .comment-more-box {
33 34
     text-align: center;
34 35
     width: 80%;
35
-    margin-left: 40px;
36
+    margin-left: 50px;
36 37
     height: 40px;
37 38
     display: inline-block;
38 39
   }
39 40
 }
40 41
 @media screen and (max-width: 365px) {
41
-  .moreBox {
42
+  .comment-more-box {
42 43
     text-align: center;
43 44
     width: 75%;
44
-    margin-left: 40px;
45
+    margin-left: 50px;
45 46
     height: 40px;
46 47
     display: inline-block;
47 48
   }

+ 9
- 8
lib/components/CommentBox/index.js View File

@@ -86,12 +86,13 @@ var CommentBox = function (_Component) {
86 86
     /**
87 87
      * 渲染回复 DOM
88 88
      * @param {array} replies 回复列表
89
+     * @param {number} replies 回复的数量
89 90
      * @param {boolean} isNoMoreReply 是否没有更多回复
90 91
      */
91 92
 
92 93
   }, {
93 94
     key: "renderReplies",
94
-    value: function renderReplies(replies, isNoMoreReply) {
95
+    value: function renderReplies(replies, replyCount, isNoMoreReply) {
95 96
       var _this2 = this;
96 97
 
97 98
       var commentId = this.props.commentId;
@@ -109,14 +110,14 @@ var CommentBox = function (_Component) {
109 110
                 replyId: item.id,
110 111
                 key: item.id,
111 112
                 content: item,
112
-                type: "reply"
113
+                action: "replyToReply" // 回复的回复
113 114
               }), _react2.default.createElement(
114 115
                 "div",
115
-                { className: "moreBox", key: "show_more_button" },
116
-                !isNoMoreReply && _react2.default.createElement(
116
+                { className: "comment-more-box", key: "show_more_button" },
117
+                !isNoMoreReply && replyCount !== len && _react2.default.createElement(
117 118
                   "span",
118 119
                   {
119
-                    className: "showMore",
120
+                    className: "comment-show-more",
120 121
                     onClick: function onClick() {
121 122
                       return _this2.handleGetMoreReply(commentId);
122 123
                     }
@@ -139,7 +140,7 @@ var CommentBox = function (_Component) {
139 140
               replyId: item.id,
140 141
               key: item.id,
141 142
               content: item,
142
-              type: "reply"
143
+              action: "replyToReply" // 评论的回复
143 144
             });
144 145
           })
145 146
         );
@@ -160,9 +161,9 @@ var CommentBox = function (_Component) {
160 161
           onShowReply: this.handleToggleReply,
161 162
           showReply: showReply,
162 163
           commentId: content.id,
163
-          type: "comment"
164
+          action: "reply" // 评论的回复
164 165
         }),
165
-        this.renderReplies(content.replies, content.isNoMoreReply)
166
+        this.renderReplies(content.replies, content.reply_count, content.isNoMoreReply)
166 167
       );
167 168
     }
168 169
   }]);

+ 1
- 1
lib/components/CommentBox/index.js.map
File diff suppressed because it is too large
View File


+ 24
- 121
lib/components/CommentInput/index.js View File

@@ -16,8 +16,6 @@ var _propTypes = require("prop-types");
16 16
 
17 17
 var _propTypes2 = _interopRequireDefault(_propTypes);
18 18
 
19
-var _constant = require("../../constant");
20
-
21 19
 var _Comment = require("../../Comment");
22 20
 
23 21
 var _Comment2 = _interopRequireDefault(_Comment);
@@ -38,160 +36,63 @@ var CommentInput = function (_Component) {
38 36
 
39 37
     var _this = _possibleConstructorReturn(this, (CommentInput.__proto__ || Object.getPrototypeOf(CommentInput)).call(this, props));
40 38
 
41
-    _this.state = {
42
-      value: "",
43
-
44
-      fileList: [], // 图片列表
45
-      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
46
-    };
47
-    _this.handleChange = _this.handleChange.bind(_this);
39
+    _this.state = {};
48 40
     _this.handleSubmit = _this.handleSubmit.bind(_this);
49
-    _this.handleChangeFileList = _this.handleChangeFileList.bind(_this);
50
-    _this.handleChangeEmoji = _this.handleChangeEmoji.bind(_this);
51
-    _this.handleUpload = _this.handleUpload.bind(_this);
52 41
     return _this;
53 42
   }
54 43
 
55 44
   _createClass(CommentInput, [{
56
-    key: "handleChange",
57
-    value: function handleChange(value) {
58
-      this.setState({ value: value });
59
-    }
60
-  }, {
61
-    key: "handleChangeFileList",
62
-    value: function handleChangeFileList(fileList) {
63
-      this.setState({ fileList: fileList });
64
-    }
65
-  }, {
66
-    key: "handleChangeEmoji",
67
-    value: function handleChangeEmoji(emojiId) {
68
-      var value = this.state.value;
69
-
70
-      value += "[" + emojiId + "]";
71
-      this.setState({ value: value });
72
-      _react2.default.Children.forEach(this.props.content, function (child) {
73
-        // 如果 Editor 的父组件传入了 onChange 事件,则需要将改变之后的值传递给父组件
74
-        if (child.props.onChange) {
75
-          child.props.onChange(value);
76
-        }
77
-      });
78
-    }
79
-  }, {
80
-    key: "handleUpload",
81
-    value: function handleUpload(_ref) {
82
-      var uid = _ref.uid,
83
-          path = _ref.path;
84
-      var fileMap = this.state.fileMap;
85
-
86
-      fileMap[uid] = path;
87
-      this.setState({ fileMap: fileMap });
88
-    }
89
-  }, {
90 45
     key: "handleSubmit",
91
-    value: function handleSubmit() {
92
-      var _state = this.state,
93
-          value = _state.value,
94
-          fileMap = _state.fileMap,
95
-          fileList = _state.fileList;
96
-
97
-      if (fileList.length) {
98
-        value += "<br/>";
99
-        fileList.forEach(function (item) {
100
-          value += "[" + _constant.OSS_LINK + fileMap[item.uid] + "]";
101
-        });
102
-      }
103
-
46
+    value: function handleSubmit(value) {
104 47
       var _props = this.props,
105
-          type = _props.type,
48
+          action = _props.action,
106 49
           commentId = _props.commentId,
107 50
           replyId = _props.replyId,
108
-          handleToggleInput = _props.handleToggleInput;
51
+          callback = _props.callback;
109 52
 
110
-      if (type === "normal") {
53
+      if (action === "comment") {
111 54
         this.props.app.sCreateComment({
112 55
           content: value
113 56
         });
114
-      } else if (type === "comment") {
57
+      } else if (action === "reply") {
115 58
         this.props.app.sCreateReply({
116 59
           comment_id: commentId,
117 60
           content: value
118 61
         }, function () {
119
-          return handleToggleInput();
62
+          return callback && callback();
120 63
         });
121
-      } else if (type === "reply") {
64
+      } else if (action === "replyToReply") {
122 65
         this.props.app.sCreateReply({
123 66
           comment_id: commentId,
124 67
           content: value,
125 68
           reply_id: replyId
126 69
         }, function () {
127
-          return handleToggleInput();
70
+          return callback && callback();
128 71
         });
129 72
       }
130
-
131
-      _react2.default.Children.forEach(this.props.content, function (child) {
132
-        // 如果 Editor 的父组件传入了 onSubmit 事件,则需要将改变之后的值传递给父组件
133
-        if (child.props.onSubmit) {
134
-          child.props.onSubmit(value);
135
-        }
136
-      });
137 73
     }
138 74
   }, {
139 75
     key: "render",
140 76
     value: function render() {
141 77
       var _this2 = this;
142 78
 
143
-      var type = this.props.type;
144
-      var _state2 = this.state,
145
-          value = _state2.value,
146
-          fileList = _state2.fileList;
147
-
148
-
149 79
       var childrenWithProps = _react2.default.Children.map(this.props.content, function (child) {
150 80
         return _react2.default.cloneElement(child, _extends({
151
-          value: value,
152
-          fileList: fileList,
153
-          onChange: _this2.handleChange,
154
-          onSubmit: _this2.handleSubmit,
155
-          onChangeFileList: _this2.handleChangeFileList,
156
-          onChangeEmoji: _this2.handleChangeEmoji,
157
-          onUpload: _this2.handleUpload,
158
-          loading: _this2.props.app.loading.sCreateComment
159
-        }, child.props));
81
+          // 编辑器本身不提交值,但 CommentInput 会提交
82
+          // CommentInput 主要是负责评论的业务逻辑,提交评论和回复
83
+          // 默认使用 CommentInput 的 onSubmit 来提交评论
84
+          // 但也可以使用 Editor 的 props 来覆盖 onSubmit
85
+          onSubmit: _this2.handleSubmit
86
+        }, child.props, {
87
+          // 如果当前的编辑器不是“评论”,则编辑器高度减小一些
88
+          rows: _this2.props.action === "comment" ? child.props.rows : child.props.rows - 1
89
+        }));
160 90
       });
161 91
 
162 92
       return _react2.default.createElement(
163 93
         "div",
164 94
         null,
165
-        type === "normal" ? _react2.default.createElement(
166
-          "div",
167
-          null,
168
-          _react2.default.createElement(
169
-            "span",
170
-            {
171
-              style: {
172
-                border: "1px solid #CECECE",
173
-                color: "#666",
174
-                padding: "2px 3px"
175
-              }
176
-            },
177
-            "\u56DE\u590D"
178
-          ),
179
-          _react2.default.createElement(
180
-            "span",
181
-            { style: { marginLeft: "20px", color: "#5198EB" } },
182
-            "\u53E3\u7891",
183
-            _react2.default.createElement(
184
-              "span",
185
-              { style: { marginLeft: "20px", color: "#666666" } },
186
-              "(\u5168\u7AD9\u6311\u51FA\u6BDB\u75C5\u6216\u63D0\u51FA\u5408\u7406\u5EFA\u8BAE\uFF0C\u5956\u52B110\u5230100\u5143\u7EA2\u5305)"
187
-            )
188
-          )
189
-        ) : null,
190
-        _react2.default.createElement(
191
-          "div",
192
-          { style: { marginTop: 40 } },
193
-          childrenWithProps
194
-        )
95
+        childrenWithProps
195 96
       );
196 97
     }
197 98
   }]);
@@ -200,12 +101,14 @@ var CommentInput = function (_Component) {
200 101
 }(_react.Component);
201 102
 
202 103
 CommentInput.propTypes = {
203
-  // normal 有切换回复/口碑的 header ; comment 评论输入框 / reply 回复输入框
204
-  type: _propTypes2.default.oneOf(["normal", "comment", "reply"])
104
+  // comment 评论
105
+  // reply 评论的回复
106
+  // replyToReply 回复的回复
107
+  action: _propTypes2.default.oneOf(["comment", "reply", "replyToReply"])
205 108
 };
206 109
 
207 110
 CommentInput.defaultProps = {
208
-  type: "normal"
111
+  action: "comment"
209 112
 };
210 113
 
211 114
 exports.default = (0, _Comment2.default)(CommentInput);

+ 1
- 1
lib/components/CommentInput/index.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
lib/components/CommentList/index.css View File

@@ -1,4 +1,4 @@
1
-.showMore {
1
+.comment-list-show-more {
2 2
   color: #4a90e2;
3 3
   text-align: center;
4 4
   width: 100%;
@@ -8,7 +8,7 @@
8 8
   margin: 40px auto;
9 9
   transition: all 0.3s;
10 10
 }
11
-.showMore:hover {
11
+.comment-list-show-more:hover {
12 12
   background-color: #f5f5f5;
13 13
   color: #1890ff;
14 14
 }

+ 11
- 3
lib/components/CommentList/index.js View File

@@ -56,26 +56,34 @@ var CommentList = function (_Component) {
56 56
     value: function render() {
57 57
       var _props$app = this.props.app,
58 58
           list = _props$app.list,
59
+          total = _props$app.total,
59 60
           page = _props$app.page,
60 61
           loading = _props$app.loading,
61 62
           isNoMoreComment = _props$app.isNoMoreComment,
62 63
           sGetComment = _props$app.sGetComment;
63 64
 
64 65
 
65
-      var spinning = Boolean(loading.sGetComment || loading.sCommentFavor);
66
+      var spinning = Boolean(loading.sGetComment || loading.sCommentFavor || loading.sReplyFavor);
66 67
       return _react2.default.createElement(
67 68
         "div",
68 69
         null,
69 70
         _react2.default.createElement(
70 71
           _spin2.default,
71 72
           { spinning: spinning },
73
+          _react2.default.createElement(
74
+            "div",
75
+            null,
76
+            "\u5171 ",
77
+            total,
78
+            " \u6761\u8BC4\u8BBA"
79
+          ),
72 80
           list.map(function (item) {
73 81
             return _react2.default.createElement(_CommentBox2.default, { content: item, key: item.id, commentId: item.id });
74 82
           }),
75
-          !isNoMoreComment && _react2.default.createElement(
83
+          !isNoMoreComment && list.length !== total && _react2.default.createElement(
76 84
             "div",
77 85
             {
78
-              className: "showMore",
86
+              className: "comment-list-show-more",
79 87
               onClick: function onClick() {
80 88
                 return sGetComment({ page: page + 1 });
81 89
               }

+ 1
- 1
lib/components/CommentList/index.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../../../src/components/CommentList/index.js"],"names":["CommentList","props","state","app","sGetComment","list","page","loading","isNoMoreComment","spinning","Boolean","sCommentFavor","map","item","id","Component","propTypes"],"mappings":";;;;;;;;;;;;;;AAAA;;;;AAEA;;;;AACA;;;;AACA;;;;;;;;;;IAEMA,W;;;AACJ,uBAAYC,KAAZ,EAAmB;AAAA;;AAAA,0HACXA,KADW;;AAEjB,UAAKC,KAAL,GAAa,EAAb;AAFiB;AAGlB;;;;yCAEoB;AACnB,WAAKD,KAAL,CAAWE,GAAX,CAAeC,WAAf;AACD;;;6BAEQ;AAAA,uBAOH,KAAKH,KAAL,CAAWE,GAPR;AAAA,UAELE,IAFK,cAELA,IAFK;AAAA,UAGLC,IAHK,cAGLA,IAHK;AAAA,UAILC,OAJK,cAILA,OAJK;AAAA,UAKLC,eALK,cAKLA,eALK;AAAA,UAMLJ,WANK,cAMLA,WANK;;;AASP,UAAMK,WAAWC,QAAQH,QAAQH,WAAR,IAAuBG,QAAQI,aAAvC,CAAjB;AACA,aACE;AAAA;AAAA;AACE;AAAA;AAAA,YAAM,UAAUF,QAAhB;AACGJ,eAAKO,GAAL,CAAS;AAAA,mBACR,8BAAC,oBAAD,IAAY,SAASC,IAArB,EAA2B,KAAKA,KAAKC,EAArC,EAAyC,WAAWD,KAAKC,EAAzD,GADQ;AAAA,WAAT,CADH;AAKG,WAACN,eAAD,IACC;AAAA;AAAA;AACE,yBAAU,UADZ;AAEE,uBAAS;AAAA,uBAAMJ,YAAY,EAAEE,MAAMA,OAAO,CAAf,EAAZ,CAAN;AAAA;AAFX;AAIE;AAAA;AAAA;AAAA;AAAA;AAJF;AANJ;AADF,OADF;AAkBD;;;;EAtCuBS,gB;;AAyC1Bf,YAAYgB,SAAZ,GAAwB,EAAxB;;kBAEe,uBAAQhB,WAAR,C","file":"index.js","sourcesContent":["import React, { Component } from \"react\";\nimport { Spin } from \"antd\";\nimport Comment from \"../../Comment\";\nimport CommentBox from \"../CommentBox\";\nimport \"./index.css\";\n\nclass CommentList extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {};\n  }\n\n  componentWillMount() {\n    this.props.app.sGetComment();\n  }\n\n  render() {\n    const {\n      list,\n      page,\n      loading,\n      isNoMoreComment,\n      sGetComment\n    } = this.props.app;\n\n    const spinning = Boolean(loading.sGetComment || loading.sCommentFavor);\n    return (\n      <div>\n        <Spin spinning={spinning}>\n          {list.map(item => (\n            <CommentBox content={item} key={item.id} commentId={item.id} />\n          ))}\n\n          {!isNoMoreComment && (\n            <div\n              className=\"showMore\"\n              onClick={() => sGetComment({ page: page + 1 })}\n            >\n              <span>查看更多评论</span>\n            </div>\n          )}\n        </Spin>\n      </div>\n    );\n  }\n}\n\nCommentList.propTypes = {};\n\nexport default Comment(CommentList);\n"]}
1
+{"version":3,"sources":["../../../src/components/CommentList/index.js"],"names":["CommentList","props","state","app","sGetComment","list","total","page","loading","isNoMoreComment","spinning","Boolean","sCommentFavor","sReplyFavor","map","item","id","length","Component","propTypes"],"mappings":";;;;;;;;;;;;;;AAAA;;;;AAEA;;;;AACA;;;;AACA;;;;;;;;;;IAEMA,W;;;AACJ,uBAAYC,KAAZ,EAAmB;AAAA;;AAAA,0HACXA,KADW;;AAEjB,UAAKC,KAAL,GAAa,EAAb;AAFiB;AAGlB;;;;yCAEoB;AACnB,WAAKD,KAAL,CAAWE,GAAX,CAAeC,WAAf;AACD;;;6BAEQ;AAAA,uBAQH,KAAKH,KAAL,CAAWE,GARR;AAAA,UAELE,IAFK,cAELA,IAFK;AAAA,UAGLC,KAHK,cAGLA,KAHK;AAAA,UAILC,IAJK,cAILA,IAJK;AAAA,UAKLC,OALK,cAKLA,OALK;AAAA,UAMLC,eANK,cAMLA,eANK;AAAA,UAOLL,WAPK,cAOLA,WAPK;;;AAUP,UAAMM,WAAWC,QACfH,QAAQJ,WAAR,IAAuBI,QAAQI,aAA/B,IAAgDJ,QAAQK,WADzC,CAAjB;AAGA,aACE;AAAA;AAAA;AACE;AAAA;AAAA,YAAM,UAAUH,QAAhB;AACE;AAAA;AAAA;AAAA;AAAQJ,iBAAR;AAAA;AAAA,WADF;AAEGD,eAAKS,GAAL,CAAS;AAAA,mBACR,8BAAC,oBAAD,IAAY,SAASC,IAArB,EAA2B,KAAKA,KAAKC,EAArC,EAAyC,WAAWD,KAAKC,EAAzD,GADQ;AAAA,WAAT,CAFH;AAMG,WAACP,eAAD,IACCJ,KAAKY,MAAL,KAAgBX,KADjB,IAEG;AAAA;AAAA;AACE,yBAAU,wBADZ;AAEE,uBAAS;AAAA,uBAAMF,YAAY,EAAEG,MAAMA,OAAO,CAAf,EAAZ,CAAN;AAAA;AAFX;AAIE;AAAA;AAAA;AAAA;AAAA;AAJF;AARN;AADF,OADF;AAoBD;;;;EA3CuBW,gB;;AA8C1BlB,YAAYmB,SAAZ,GAAwB,EAAxB;;kBAEe,uBAAQnB,WAAR,C","file":"index.js","sourcesContent":["import React, { Component } from \"react\";\nimport { Spin } from \"antd\";\nimport Comment from \"../../Comment\";\nimport CommentBox from \"../CommentBox\";\nimport \"./index.css\";\n\nclass CommentList extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {};\n  }\n\n  componentWillMount() {\n    this.props.app.sGetComment();\n  }\n\n  render() {\n    const {\n      list,\n      total,\n      page,\n      loading,\n      isNoMoreComment,\n      sGetComment\n    } = this.props.app;\n\n    const spinning = Boolean(\n      loading.sGetComment || loading.sCommentFavor || loading.sReplyFavor\n    );\n    return (\n      <div>\n        <Spin spinning={spinning}>\n          <div>共 {total} 条评论</div>\n          {list.map(item => (\n            <CommentBox content={item} key={item.id} commentId={item.id} />\n          ))}\n\n          {!isNoMoreComment &&\n            list.length !== total && (\n              <div\n                className=\"comment-list-show-more\"\n                onClick={() => sGetComment({ page: page + 1 })}\n              >\n                <span>查看更多评论</span>\n              </div>\n            )}\n        </Spin>\n      </div>\n    );\n  }\n}\n\nCommentList.propTypes = {};\n\nexport default Comment(CommentList);\n"]}

+ 20
- 17
lib/components/ContentItem/index.css View File

@@ -1,50 +1,53 @@
1
-.left {
1
+.comment-item-box {
2
+  margin: 10px 0 0 0;
3
+  padding: 15px 5px 0 5px;
4
+  border-top: 1px solid #eee;
5
+}
6
+.comment-item-left {
2 7
   display: inline-block;
3 8
   vertical-align: top;
4 9
   width: 40px;
5 10
 }
6
-.right {
11
+.comment-item-right {
7 12
   display: inline-block;
8 13
   width: 90%;
9 14
   margin-left: 10px;
15
+  margin-bottom: 20px;
10 16
 }
11
-.box {
12
-  margin: 10px;
13
-  padding: 15px 5px;
14
-  border-top: 1px solid #eee;
15
-}
16
-.content {
17
+.comment-item-content {
17 18
   margin: 10px 0;
18 19
 }
19
-.itemLeft {
20
+.comment-item-bottom {
21
+  margin: 20px auto;
22
+}
23
+.comment-item-bottom-left {
20 24
   float: left;
25
+  user-select: none;
21 26
 }
22
-.itemRight {
27
+.comment-item-bottom-right {
23 28
   float: right;
24 29
   margin-left: 5px;
30
+  cursor: pointer;
25 31
 }
26
-.bottom {
27
-  margin: 20px auto;
28
-}
29
-.favored {
32
+.comment-favored {
30 33
   color: #4a90e2;
31 34
 }
32 35
 @media screen and (max-width: 616px) and (min-width: 449px) {
33
-  .right {
36
+  .comment-item-right {
34 37
     display: inline-block;
35 38
     width: 85%;
36 39
     margin-left: 10px;
37 40
   }
38 41
 }
39 42
 @media screen and (max-width: 449px) and (min-width: 365px) {
40
-  .right {
43
+  .comment-item-right {
41 44
     display: inline-block;
42 45
     width: 80%;
43 46
     margin-left: 10px;
44 47
   }
45 48
 }
46 49
 @media screen and (max-width: 365px) {
47
-  .right {
50
+  .comment-item-right {
48 51
     display: inline-block;
49 52
     width: 75%;
50 53
     margin-left: 10px;

+ 66
- 35
lib/components/ContentItem/index.js View File

@@ -8,6 +8,10 @@ var _icon = require("antd/es/icon");
8 8
 
9 9
 var _icon2 = _interopRequireDefault(_icon);
10 10
 
11
+var _tooltip = require("antd/es/tooltip");
12
+
13
+var _tooltip2 = _interopRequireDefault(_tooltip);
14
+
11 15
 var _avatar = require("antd/es/avatar");
12 16
 
13 17
 var _avatar2 = _interopRequireDefault(_avatar);
@@ -16,6 +20,8 @@ var _createClass = function () { function defineProperties(target, props) { for
16 20
 
17 21
 require("antd/es/icon/style/css");
18 22
 
23
+require("antd/es/tooltip/style/css");
24
+
19 25
 require("antd/es/avatar/style/css");
20 26
 
21 27
 var _react = require("react");
@@ -30,6 +36,12 @@ var _dayjs = require("dayjs");
30 36
 
31 37
 var _dayjs2 = _interopRequireDefault(_dayjs);
32 38
 
39
+require("dayjs/locale/zh-cn");
40
+
41
+var _relativeTime = require("dayjs/plugin/relativeTime");
42
+
43
+var _relativeTime2 = _interopRequireDefault(_relativeTime);
44
+
33 45
 var _Comment = require("../../Comment");
34 46
 
35 47
 var _Comment2 = _interopRequireDefault(_Comment);
@@ -38,6 +50,10 @@ var _CommentInput = require("../CommentInput");
38 50
 
39 51
 var _CommentInput2 = _interopRequireDefault(_CommentInput);
40 52
 
53
+var _avatar3 = require("../../avatar");
54
+
55
+var _avatar4 = _interopRequireDefault(_avatar3);
56
+
41 57
 var _helper = require("../../helper");
42 58
 
43 59
 require("./index.css");
@@ -50,6 +66,9 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
50 66
 
51 67
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
52 68
 
69
+_dayjs2.default.locale("zh-cn");
70
+_dayjs2.default.extend(_relativeTime2.default);
71
+
53 72
 var CommentItem = function (_Component) {
54 73
   _inherits(CommentItem, _Component);
55 74
 
@@ -59,7 +78,7 @@ var CommentItem = function (_Component) {
59 78
     var _this = _possibleConstructorReturn(this, (CommentItem.__proto__ || Object.getPrototypeOf(CommentItem)).call(this, props));
60 79
 
61 80
     _this.state = {
62
-      isShowInput: false
81
+      showInput: false
63 82
     };
64 83
     _this.handleToggleInput = _this.handleToggleInput.bind(_this);
65 84
     _this.renderTextWithReply = _this.renderTextWithReply.bind(_this);
@@ -69,7 +88,7 @@ var CommentItem = function (_Component) {
69 88
   _createClass(CommentItem, [{
70 89
     key: "handleToggleInput",
71 90
     value: function handleToggleInput() {
72
-      this.setState({ isShowInput: !this.state.isShowInput });
91
+      this.setState({ showInput: !this.state.showInput });
73 92
     }
74 93
   }, {
75 94
     key: "renderTextWithReply",
@@ -92,27 +111,27 @@ var CommentItem = function (_Component) {
92 111
           commentId = _props.commentId,
93 112
           replyId = _props.replyId,
94 113
           content = _props.content,
95
-          type = _props.type,
114
+          action = _props.action,
96 115
           showReply = _props.showReply,
97 116
           onShowReply = _props.onShowReply,
98 117
           app = _props.app;
99
-      var isShowInput = this.state.isShowInput;
118
+      var showInput = this.state.showInput;
119
+
100 120
 
101
-      var isComment = type === "comment";
102 121
       return _react2.default.createElement(
103 122
         "div",
104
-        { className: "box" },
123
+        { className: "comment-item-box" },
105 124
         _react2.default.createElement(
106 125
           "div",
107
-          { className: "left" },
108
-          _react2.default.createElement(_avatar2.default, { src: content.user_avatar, size: "large" })
126
+          { className: "comment-item-left" },
127
+          _react2.default.createElement(_avatar2.default, { src: content.user_avatar || _avatar4.default, size: "large" })
109 128
         ),
110 129
         _react2.default.createElement(
111 130
           "div",
112
-          { className: "right" },
131
+          { className: "comment-item-right" },
113 132
           _react2.default.createElement(
114 133
             "div",
115
-            { className: "name" },
134
+            null,
116 135
             _react2.default.createElement(
117 136
               "a",
118 137
               { href: "/" + content.user_id },
@@ -121,28 +140,31 @@ var CommentItem = function (_Component) {
121 140
             _react2.default.createElement(
122 141
               "span",
123 142
               { style: { marginLeft: 10 } },
124
-              (0, _dayjs2.default)(content.created * 1000).format("YYYY-MM-DD HH:mm:ss")
143
+              _react2.default.createElement(
144
+                _tooltip2.default,
145
+                {
146
+                  placement: "top",
147
+                  title: (0, _dayjs2.default)(content.created * 1000).format("YYYY-MM-DD HH:mm:ss")
148
+                },
149
+                (0, _dayjs2.default)(content.created * 1000).fromNow()
150
+              )
125 151
             )
126 152
           ),
127 153
           _react2.default.createElement("div", {
128
-            className: "content",
154
+            className: "comment-item-content",
129 155
             dangerouslySetInnerHTML: {
130 156
               __html: (0, _helper.renderContent)(this.renderTextWithReply(content.content, content))
131 157
             }
132 158
           }),
133 159
           _react2.default.createElement(
134 160
             "div",
135
-            { className: "bottom" },
136
-            isComment && content.reply_count ? _react2.default.createElement(
161
+            { className: "comment-item-bottom" },
162
+            content.reply_count ? _react2.default.createElement(
137 163
               "div",
138 164
               null,
139 165
               _react2.default.createElement(
140 166
                 "a",
141
-                {
142
-                  className: "itemLeft",
143
-                  onClick: onShowReply,
144
-                  style: { userSelect: "none" }
145
-                },
167
+                { className: "comment-item-bottom-left", onClick: onShowReply },
146 168
                 content.reply_count,
147 169
                 " \u6761\u56DE\u590D",
148 170
                 showReply ? _react2.default.createElement(_icon2.default, { type: "up" }) : _react2.default.createElement(_icon2.default, { type: "down" })
@@ -150,33 +172,41 @@ var CommentItem = function (_Component) {
150 172
             ) : null,
151 173
             _react2.default.createElement(
152 174
               "a",
153
-              { onClick: this.handleToggleInput, className: "itemRight" },
175
+              {
176
+                onClick: this.handleToggleInput,
177
+                className: "comment-item-bottom-right"
178
+              },
154 179
               "\xA0 \u56DE\u590D"
155 180
             ),
156 181
             _react2.default.createElement(
157 182
               "div",
158 183
               {
159
-                className: "itemRight",
160
-                style: { cursor: "pointer" },
184
+                className: "comment-item-bottom-right",
161 185
                 onClick: function onClick() {
162
-                  return app.sCommentFavor(content.id, content.favored);
186
+                  if (replyId) {
187
+                    // 如果有 replyId,则说明是评论的回复
188
+                    app.sReplyFavor(content.id, commentId, content.favored);
189
+                    return;
190
+                  }
191
+                  app.sCommentFavor(content.id, content.favored);
163 192
                 }
164 193
               },
165 194
               _react2.default.createElement(_icon2.default, {
166 195
                 type: "like-o",
167
-                className: content.favored ? "favored" : ""
196
+                className: content.favored ? "comment-favored" : ""
168 197
               }),
169 198
               "\xA0",
170 199
               content.favor_count
171 200
             )
172
-          ),
173
-          isShowInput ? _react2.default.createElement(_CommentInput2.default, {
174
-            type: type,
175
-            replyId: replyId,
176
-            commentId: commentId,
177
-            handleToggleInput: this.handleToggleInput
178
-          }) : null
179
-        )
201
+          )
202
+        ),
203
+        showInput && _react2.default.createElement(_CommentInput2.default, {
204
+          content: app.children,
205
+          action: action,
206
+          replyId: replyId,
207
+          commentId: commentId,
208
+          callback: this.handleToggleInput
209
+        })
180 210
       );
181 211
     }
182 212
   }]);
@@ -187,13 +217,14 @@ var CommentItem = function (_Component) {
187 217
 CommentItem.propTypes = {
188 218
   content: _propTypes2.default.object.isRequired,
189 219
   // comment 评论
190
-  // reply 回复
191
-  type: _propTypes2.default.oneOf(["comment", "reply"]),
220
+  // reply 评论的回复
221
+  // replyToReply 回复的回复
222
+  action: _propTypes2.default.oneOf(["comment", "reply", "replyToReply"]),
192 223
   onShowReply: _propTypes2.default.func
193 224
 };
194 225
 
195 226
 CommentItem.defaultProps = {
196
-  type: "comment",
227
+  action: "comment",
197 228
   onShowReply: function onShowReply() {}
198 229
 };
199 230
 

+ 1
- 1
lib/components/ContentItem/index.js.map
File diff suppressed because it is too large
View File


+ 0
- 2
lib/components/Editor/Upload.js View File

@@ -57,8 +57,6 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
57 57
 function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
58 58
 
59 59
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
60
-// import styles from "./Upload.less";
61
-
62 60
 
63 61
 var client = function client(oss) {
64 62
   return new window.OSS.Wrapper({

+ 1
- 1
lib/components/Editor/Upload.js.map
File diff suppressed because it is too large
View File


+ 23
- 29
lib/components/Editor/index.css View File

@@ -1,4 +1,4 @@
1
-.editor {
1
+.comment-editor {
2 2
   box-sizing: border-box;
3 3
   margin: 0;
4 4
   padding: 0;
@@ -16,67 +16,61 @@
16 16
   border-radius: 4px;
17 17
   transition: all 0.3s, height 0s;
18 18
 }
19
-.editor textarea.ant-input {
19
+.comment-editor textarea.ant-input {
20 20
   border: none;
21 21
   border-bottom: 1px solid #eee;
22 22
   border-bottom-right-radius: 0;
23 23
   border-bottom-left-radius: 0;
24 24
 }
25
-.editor textarea.ant-input:hover {
25
+.comment-editor textarea.ant-input:hover {
26 26
   border: none;
27 27
   border-bottom: 1px solid #eee;
28 28
 }
29
-.editor textarea.ant-input:focus {
29
+.comment-editor textarea.ant-input:focus {
30 30
   box-shadow: none;
31 31
   border-bottom: 1px solid #eee;
32 32
 }
33
-.editor [contentEditable="true"]:empty:not(:focus):before {
33
+.comment-editor [contentEditable="true"]:empty:not(:focus):before {
34 34
   content: attr(data-text);
35 35
   color: #bfbfbf;
36 36
 }
37
-.editor:focus,
38
-.editor:hover {
37
+.comment-editor:focus,
38
+.comment-editor:hover {
39 39
   border-color: #40a9ff;
40 40
   outline: 0;
41 41
   box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
42 42
 }
43
-.content {
44
-  display: block;
45
-  box-sizing: border-box;
46
-  padding: 4px 10px;
47
-  width: 100%;
48
-  max-width: 100%;
49
-  height: 100%;
50
-  max-height: 110px;
51
-  overflow-y: auto;
52
-  overflow-x: auto;
53
-}
54
-.content:focus,
55
-.content:hover {
56
-  outline: 0;
57
-}
58
-.toolbar {
43
+.comment-toolbar {
59 44
   display: inline-block;
60 45
   width: 100%;
61 46
   margin: 5px 0 0 0;
62 47
 }
63
-.toolbar .icon {
48
+.comment-toolbar .comment-toolbar-icon {
64 49
   font-size: 23px;
65 50
   cursor: pointer;
66 51
   transition: color 0.3s;
67 52
 }
68
-.toolbar .icon:hover {
53
+.comment-toolbar .comment-toolbar-icon:hover {
69 54
   color: #40a9ff;
70 55
 }
71
-.feed .ant-popover-inner-content {
56
+.comment-toolbar-left {
57
+  float: left;
58
+  margin: 8px 11px;
59
+}
60
+.comment-toolbar-right {
61
+  float: right;
62
+  margin: 5px 11px;
63
+}
64
+
65
+.comment-emoji-popover .ant-popover-inner-content {
72 66
   padding: 12px 16px 20px 16px;
73 67
 }
74
-.feed .ant-carousel .slick-dots {
68
+.comment-emoji-popover .ant-carousel .slick-dots {
75 69
   bottom: -10px;
76 70
 }
77
-.feed .ant-carousel .slick-dots li.slick-active button {
71
+.comment-emoji-popover .ant-carousel .slick-dots li.slick-active button {
78 72
   background-color: #7b868a;
79 73
 }
80
-.feed .ant-carousel .slick-dots li button {
74
+.comment-emoji-popover .ant-carousel .slick-dots li button {
81 75
   background-color: #a2aeb5;
82 76
 }

+ 158
- 52
lib/components/Editor/index.js View File

@@ -20,8 +20,6 @@ var _input = require("antd/es/input");
20 20
 
21 21
 var _input2 = _interopRequireDefault(_input);
22 22
 
23
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
24
-
25 23
 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
26 24
 
27 25
 require("antd/es/button/style/css");
@@ -61,19 +59,6 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
61 59
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
62 60
 
63 61
 var TextArea = _input2.default.TextArea;
64
-// 设置 Editor 组件的默认值
65
-// 不能在 Editor.defaultProps 中设置
66
-// 因为 Editor 在 ComponentInput 中调用
67
-// 在 ComponentInput 中,需要使用 Editor 的 props 覆盖 ComponentInput 传入的 props
68
-
69
-var EditorDefaultProps = {
70
-  rows: 5,
71
-  placeholder: "说点什么吧...",
72
-  showEmoji: true,
73
-  showUpload: true,
74
-  submitText: "发表",
75
-  onChange: function onChange() {}
76
-};
77 62
 
78 63
 var Editor = function (_React$Component) {
79 64
   _inherits(Editor, _React$Component);
@@ -84,21 +69,73 @@ var Editor = function (_React$Component) {
84 69
     var _this = _possibleConstructorReturn(this, (Editor.__proto__ || Object.getPrototypeOf(Editor)).call(this, props));
85 70
 
86 71
     _this.state = {
87
-      showUpload: false
72
+      showUpload: false,
73
+      value: "", // 编辑器里面的值
74
+
75
+      fileList: [], // 图片列表
76
+      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
88 77
     };
78
+    _this.handleChange = _this.handleChange.bind(_this);
89 79
     _this.handleClickEmoji = _this.handleClickEmoji.bind(_this);
80
+    _this.handleChangeFileList = _this.handleChangeFileList.bind(_this);
90 81
     _this.handleShowUpload = _this.handleShowUpload.bind(_this);
82
+    _this.handleUpload = _this.handleUpload.bind(_this);
83
+    _this.handleSubmit = _this.handleSubmit.bind(_this);
91 84
     return _this;
92 85
   }
93 86
 
94 87
   _createClass(Editor, [{
95 88
     key: "componentDidMount",
96 89
     value: function componentDidMount() {}
90
+
91
+    /**
92
+     * 编辑器的值改变事件
93
+     * 将最新的值存储到 state 中
94
+     * @param {string} value 输入的值
95
+     */
96
+
97
+  }, {
98
+    key: "handleChange",
99
+    value: function handleChange(value) {
100
+      this.setState({ value: value });
101
+      if (this.props.onChange) {
102
+        this.props.onChange(value);
103
+      }
104
+    }
105
+
106
+    /**
107
+     * 点击 emoji 的事件
108
+     * 点击后,需要将改 emoji 插入到编辑器中
109
+     * 插入的值为 [emoji chinese name]
110
+     * 参数 emoji 即为 emoji chinese name
111
+     * @param {string}} emoji emoji 的中文,如 微笑
112
+     */
113
+
97 114
   }, {
98 115
     key: "handleClickEmoji",
99
-    value: function handleClickEmoji(emojiId) {
100
-      this.props.onChangeEmoji(emojiId);
116
+    value: function handleClickEmoji(emoji) {
117
+      var value = this.state.value;
118
+
119
+      value += "[" + emoji + "]";
120
+      this.handleChange(value);
101 121
     }
122
+
123
+    /**
124
+     * 监听文件列表改变事件
125
+     * @param {Array} fileList 文件列表
126
+     */
127
+
128
+  }, {
129
+    key: "handleChangeFileList",
130
+    value: function handleChangeFileList(fileList) {
131
+      this.setState({ fileList: fileList });
132
+    }
133
+
134
+    /**
135
+     * 控制上传 Popover 的显示和隐藏
136
+     * @param {boolean} showUpload 是否显示上传的 Popover
137
+     */
138
+
102 139
   }, {
103 140
     key: "handleShowUpload",
104 141
     value: function handleShowUpload(showUpload) {
@@ -108,43 +145,82 @@ var Editor = function (_React$Component) {
108 145
         this.setState({ showUpload: !this.state.showUpload });
109 146
       }
110 147
     }
148
+
149
+    /**
150
+     * 上传文件 TODO:
151
+     * @param {object} param 文件对象
152
+     */
153
+
154
+  }, {
155
+    key: "handleUpload",
156
+    value: function handleUpload(_ref) {
157
+      var uid = _ref.uid,
158
+          path = _ref.path;
159
+      var fileMap = this.state.fileMap;
160
+
161
+      fileMap[uid] = path;
162
+      this.setState({ fileMap: fileMap });
163
+    }
164
+
165
+    /**
166
+     * 提交编辑器内容
167
+     * 提交功能,交给父组件来实现
168
+     * 需要父组件传入 onSubmit
169
+     */
170
+
171
+  }, {
172
+    key: "handleSubmit",
173
+    value: function handleSubmit() {
174
+      var _state = this.state,
175
+          value = _state.value,
176
+          fileMap = _state.fileMap,
177
+          fileList = _state.fileList;
178
+
179
+      if (fileList.length) {
180
+        value += "<br/>";
181
+        fileList.forEach(function (item) {
182
+          value += "[" + _constant.OSS_LINK + fileMap[item.uid] + "]";
183
+        });
184
+      }
185
+      this.props.onSubmit(value);
186
+    }
111 187
   }, {
112 188
     key: "render",
113 189
     value: function render() {
114 190
       var _this2 = this;
115 191
 
116
-      var props = _extends({}, EditorDefaultProps, this.props);
117
-      var value = props.value,
118
-          _onChange = props.onChange,
119
-          onSubmit = props.onSubmit,
120
-          loading = props.loading,
121
-          placeholder = props.placeholder,
122
-          fileList = props.fileList,
123
-          onChangeFileList = props.onChangeFileList,
124
-          rows = props.rows,
125
-          onUpload = props.onUpload,
126
-          showEmoji = props.showEmoji,
127
-          showUpload = props.showUpload,
128
-          submitText = props.submitText;
129
-
130
-
192
+      var _props = this.props,
193
+          value = _props.value,
194
+          placeholder = _props.placeholder,
195
+          rows = _props.rows,
196
+          showEmoji = _props.showEmoji,
197
+          showUpload = _props.showUpload,
198
+          btnSubmitText = _props.btnSubmitText,
199
+          btnLoading = _props.btnLoading,
200
+          btnDisabled = _props.btnDisabled,
201
+          button = _props.button,
202
+          emojiToolIcon = _props.emojiToolIcon,
203
+          imageToolIcon = _props.imageToolIcon;
204
+
205
+
206
+      var handleSubmit = this.handleSubmit;
131 207
       return _react2.default.createElement(
132 208
         "div",
133
-        { className: "editor" },
209
+        { className: "comment-editor" },
134 210
         _react2.default.createElement(TextArea, {
135
-          value: value,
211
+          value: value || this.state.value,
136 212
           onChange: function onChange(e) {
137
-            return _onChange(e.target.value);
213
+            return _this2.handleChange(e.target.value);
138 214
           },
139 215
           rows: rows,
140 216
           placeholder: placeholder
141 217
         }),
142 218
         _react2.default.createElement(
143 219
           "div",
144
-          { className: "toolbar" },
220
+          { className: "comment-toolbar" },
145 221
           _react2.default.createElement(
146 222
             "div",
147
-            { style: { float: "left", margin: "8px 11px" } },
223
+            { className: "comment-toolbar-left" },
148 224
             showEmoji && _react2.default.createElement(
149 225
               _popover2.default,
150 226
               {
@@ -156,9 +232,9 @@ var Editor = function (_React$Component) {
156 232
                   { style: { width: 200 } },
157 233
                   _react2.default.createElement(_Emoji2.default, { onClick: this.handleClickEmoji })
158 234
                 ),
159
-                overlayClassName: "feed"
235
+                overlayClassName: "comment-emoji-popover"
160 236
               },
161
-              _react2.default.createElement(_icon2.default, { type: "smile-o", className: "icon" })
237
+              emojiToolIcon || _react2.default.createElement(_icon2.default, { type: "smile-o", className: "comment-toolbar-icon" })
162 238
             ),
163 239
             showUpload && _react2.default.createElement(
164 240
               _popover2.default,
@@ -171,9 +247,9 @@ var Editor = function (_React$Component) {
171 247
                     style: { width: 112 * _constant.MAX_UPLOAD_NUMBER, minHeight: 100 }
172 248
                   },
173 249
                   _react2.default.createElement(_Upload2.default, {
174
-                    onChangeFileList: onChangeFileList,
175
-                    onUpload: onUpload,
176
-                    fileList: fileList
250
+                    onChangeFileList: this.handleChangeFileList,
251
+                    onUpload: this.handleUpload,
252
+                    fileList: this.state.fileList
177 253
                   })
178 254
                 ),
179 255
                 placement: "bottomLeft",
@@ -188,7 +264,7 @@ var Editor = function (_React$Component) {
188 264
                       "span",
189 265
                       { style: { color: "#666", fontWeight: 400 } },
190 266
                       "(\u60A8\u8FD8\u80FD\u4E0A\u4F20",
191
-                      _constant.MAX_UPLOAD_NUMBER - fileList.length,
267
+                      _constant.MAX_UPLOAD_NUMBER - this.state.fileList.length,
192 268
                       "\u5F20\u56FE\u7247)"
193 269
                     )
194 270
                   ),
@@ -205,9 +281,13 @@ var Editor = function (_React$Component) {
205 281
                   })
206 282
                 )
207 283
               },
208
-              _react2.default.createElement(_icon2.default, {
284
+              imageToolIcon ? _react2.default.cloneElement(imageToolIcon, {
285
+                onClick: function onClick() {
286
+                  return _this2.handleShowUpload(true);
287
+                }
288
+              }) : _react2.default.createElement(_icon2.default, {
209 289
                 type: "picture",
210
-                className: "icon",
290
+                className: "comment-toolbar-icon",
211 291
                 style: { marginLeft: 10 },
212 292
                 onClick: function onClick() {
213 293
                   return _this2.handleShowUpload(true);
@@ -217,11 +297,20 @@ var Editor = function (_React$Component) {
217 297
           ),
218 298
           _react2.default.createElement(
219 299
             "div",
220
-            { style: { float: "right", margin: "5px 11px" } },
221
-            _react2.default.createElement(
300
+            { className: "comment-toolbar-right" },
301
+            button ? _react2.default.cloneElement(button, {
302
+              onClick: button.props.onClick || handleSubmit
303
+            }) : _react2.default.createElement(
222 304
               _button2.default,
223
-              { onClick: onSubmit, type: "primary", loading: loading },
224
-              submitText
305
+              {
306
+                onClick: function onClick() {
307
+                  return _this2.handleSubmit();
308
+                },
309
+                type: "primary",
310
+                loading: btnLoading,
311
+                disabled: btnDisabled
312
+              },
313
+              btnSubmitText
225 314
             )
226 315
           )
227 316
         )
@@ -237,8 +326,25 @@ Editor.propTypes = {
237 326
   placeholder: _propTypes2.default.string,
238 327
   showEmoji: _propTypes2.default.bool,
239 328
   showUpload: _propTypes2.default.bool,
240
-  submitText: _propTypes2.default.string,
241
-  onChange: _propTypes2.default.func
329
+  value: _propTypes2.default.string,
330
+  onChange: _propTypes2.default.func,
331
+  onSubmit: _propTypes2.default.func,
332
+  btnSubmitText: _propTypes2.default.string,
333
+  btnLoading: _propTypes2.default.bool,
334
+  btnDisabled: _propTypes2.default.bool,
335
+  button: _propTypes2.default.node,
336
+  emojiToolIcon: _propTypes2.default.node,
337
+  imageToolIcon: _propTypes2.default.node
338
+};
339
+
340
+Editor.defaultProps = {
341
+  rows: 5,
342
+  placeholder: "说点什么吧...",
343
+  showEmoji: true,
344
+  showUpload: true,
345
+  btnSubmitText: "发表",
346
+  btnLoading: false,
347
+  btnDisabled: false
242 348
 };
243 349
 
244 350
 exports.default = Editor;

+ 1
- 1
lib/components/Editor/index.js.map
File diff suppressed because it is too large
View File


+ 2
- 0
lib/constant.js View File

@@ -16,4 +16,6 @@ var OSS_LINK = exports.OSS_LINK = "http://links-comment.oss-cn-beijing.aliyuncs.
16 16
 var MAX_UPLOAD_NUMBER = exports.MAX_UPLOAD_NUMBER = 4;
17 17
 
18 18
 var REGEXP = exports.REGEXP = /\[.+?\]/g;
19
+
20
+var AVATAR = exports.AVATAR = "";
19 21
 //# sourceMappingURL=constant.js.map

+ 1
- 1
lib/constant.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../src/constant.js"],"names":["ERROR_DEFAULT","LIMIT","OSS_ENDPOINT","OSS_BUCKET","DRIVER_LICENSE_PATH","OSS_LINK","MAX_UPLOAD_NUMBER","REGEXP"],"mappings":";;;;;AAAO,IAAMA,wCAAgB,MAAtB;;AAEA,IAAMC,wBAAQ,EAAd,C,CAAkB;;AAElB,IAAMC,sCAAe,6BAArB;AACA,IAAMC,kCAAa,eAAnB;AACA,IAAMC,oDAAsB,UAA5B;;AAEA,IAAMC,8BAAW,kDAAjB;;AAEA,IAAMC,gDAAoB,CAA1B;;AAEA,IAAMC,0BAAS,UAAf","file":"constant.js","sourcesContent":["export const ERROR_DEFAULT = \"出错了!\";\n\nexport const LIMIT = 10; // 默认 limit\n\nexport const OSS_ENDPOINT = \"oss-cn-beijing.aliyuncs.com\";\nexport const OSS_BUCKET = \"links-comment\";\nexport const DRIVER_LICENSE_PATH = \"/comment\";\n\nexport const OSS_LINK = \"http://links-comment.oss-cn-beijing.aliyuncs.com\";\n\nexport const MAX_UPLOAD_NUMBER = 4;\n\nexport const REGEXP = /\\[.+?\\]/g;\n"]}
1
+{"version":3,"sources":["../src/constant.js"],"names":["ERROR_DEFAULT","LIMIT","OSS_ENDPOINT","OSS_BUCKET","DRIVER_LICENSE_PATH","OSS_LINK","MAX_UPLOAD_NUMBER","REGEXP","AVATAR"],"mappings":";;;;;AAAO,IAAMA,wCAAgB,MAAtB;;AAEA,IAAMC,wBAAQ,EAAd,C,CAAkB;;AAElB,IAAMC,sCAAe,6BAArB;AACA,IAAMC,kCAAa,eAAnB;AACA,IAAMC,oDAAsB,UAA5B;;AAEA,IAAMC,8BAAW,kDAAjB;;AAEA,IAAMC,gDAAoB,CAA1B;;AAEA,IAAMC,0BAAS,UAAf;;AAEA,IAAMC,0BAAS,EAAf","file":"constant.js","sourcesContent":["export const ERROR_DEFAULT = \"出错了!\";\n\nexport const LIMIT = 10; // 默认 limit\n\nexport const OSS_ENDPOINT = \"oss-cn-beijing.aliyuncs.com\";\nexport const OSS_BUCKET = \"links-comment\";\nexport const DRIVER_LICENSE_PATH = \"/comment\";\n\nexport const OSS_LINK = \"http://links-comment.oss-cn-beijing.aliyuncs.com\";\n\nexport const MAX_UPLOAD_NUMBER = 4;\n\nexport const REGEXP = /\\[.+?\\]/g;\n\nexport const AVATAR = \"\";\n"]}

+ 1
- 1
lib/emoji.js View File

@@ -17,7 +17,7 @@ var emoji = [{
17 17
   title: "色"
18 18
 }, {
19 19
   value: "4",
20
-  ttile: "发呆"
20
+  title: "发呆"
21 21
 }, {
22 22
   value: "5",
23 23
   title: "酷"

+ 1
- 1
lib/emoji.js.map
File diff suppressed because it is too large
View File


+ 97
- 19
lib/index.js View File

@@ -1,5 +1,7 @@
1 1
 "use strict";
2 2
 
3
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4
+
3 5
 var _react = require("react");
4 6
 
5 7
 var _react2 = _interopRequireDefault(_react);
@@ -18,24 +20,100 @@ var _registerServiceWorker2 = _interopRequireDefault(_registerServiceWorker);
18 20
 
19 21
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20 22
 
21
-var props = {
22
-  type: 1,
23
-  businessId: "1",
24
-  API: "http://api.links123.net/comment/v1",
25
-  showList: false
26
-};
27
-
28
-var editorProps = {
29
-  showEmoji: true,
30
-  placeholder: "说点什么吧",
31
-  rows: 5
32
-  // onSubmit={v => console.log()} // TODO...
33
-};
34
-
35
-_reactDom2.default.render(_react2.default.createElement(
36
-  _App2.default,
37
-  props,
38
-  _react2.default.createElement(_App.Editor, editorProps)
39
-), document.getElementById("root-comment"));
23
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
24
+
25
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
26
+
27
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
28
+
29
+var Index = function (_Component) {
30
+  _inherits(Index, _Component);
31
+
32
+  function Index(props) {
33
+    _classCallCheck(this, Index);
34
+
35
+    var _this = _possibleConstructorReturn(this, (Index.__proto__ || Object.getPrototypeOf(Index)).call(this, props));
36
+
37
+    _this.state = {
38
+      value: ""
39
+    };
40
+    _this.handleChangeValue = _this.handleChangeValue.bind(_this);
41
+    _this.handleChangeSubmit = _this.handleChangeSubmit.bind(_this);
42
+    return _this;
43
+  }
44
+
45
+  _createClass(Index, [{
46
+    key: "handleChangeValue",
47
+    value: function handleChangeValue(value) {
48
+      this.setState({ value: value });
49
+      console.log("handleChangeValue value: ", value);
50
+    }
51
+  }, {
52
+    key: "handleChangeSubmit",
53
+    value: function handleChangeSubmit(value) {
54
+      var _this2 = this;
55
+
56
+      this.setState({ loading: true }, function () {
57
+        setTimeout(function () {
58
+          _this2.setState({ loading: false });
59
+        }, 2000);
60
+      });
61
+      console.log("submit value: ", value);
62
+    }
63
+  }, {
64
+    key: "render",
65
+    value: function render() {
66
+      // 最简单的用法
67
+      return _react2.default.createElement(
68
+        _App2.default,
69
+        { type: 1, businessId: "test" },
70
+        _react2.default.createElement(_App.Editor, null)
71
+      );
72
+
73
+      // 复杂的用户法
74
+      // const props = {
75
+      //   type: 1,
76
+      //   businessId: "1",
77
+      //   API: "http://api.links123.net/comment/v1",
78
+      //   showList: true
79
+      // };
80
+
81
+      // const editorProps = {
82
+      //   showEmoji: true,
83
+      //   placeholder: "说点什么吧",
84
+      //   rows: 5,
85
+      //   btnLoading: this.state.loading,
86
+      //   btnDisable: this.state.loading,
87
+      //   btnSubmitText: "提交",
88
+      //   value: this.state.value,
89
+      //   onChange: v => this.handleChangeValue(v),
90
+      //   onSubmit: v => this.handleChangeSubmit(v),
91
+      //   button: (
92
+      //     <Button
93
+      //       type="primary"
94
+      //       ghost
95
+      //       // onClick={() => console.log('click btn: ', this.state.value)}
96
+      //     >
97
+      //       自定义按钮
98
+      //     </Button>
99
+      //   ),
100
+      //   emojiToolIcon: <Icon type="smile" style={{ fontSize: 23 }} />,
101
+      //   imageToolIcon: (
102
+      //     <Icon type="cloud-upload-o" style={{ fontSize: 25, marginLeft: 10 }} />
103
+      //   )
104
+      // };
105
+
106
+      // return (
107
+      //   <App {...props}>
108
+      //     <Editor {...editorProps} />
109
+      //   </App>
110
+      // );
111
+    }
112
+  }]);
113
+
114
+  return Index;
115
+}(_react.Component);
116
+
117
+_reactDom2.default.render(_react2.default.createElement(Index, null), document.getElementById("root-comment"));
40 118
 (0, _registerServiceWorker2.default)();
41 119
 //# sourceMappingURL=index.js.map

+ 1
- 1
lib/index.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../src/index.js"],"names":["props","type","businessId","API","showList","editorProps","showEmoji","placeholder","rows","ReactDOM","render","document","getElementById"],"mappings":";;AAAA;;;;AACA;;;;AACA;;;;AACA;;;;;;AAEA,IAAMA,QAAQ;AACZC,QAAM,CADM;AAEZC,cAAY,GAFA;AAGZC,OAAK,oCAHO;AAIZC,YAAU;AAJE,CAAd;;AAOA,IAAMC,cAAc;AAClBC,aAAW,IADO;AAElBC,eAAa,OAFK;AAGlBC,QAAM;AACN;AAJkB,CAApB;;AAOAC,mBAASC,MAAT,CACE;AAAC,eAAD;AAASV,OAAT;AACE,gCAAC,WAAD,EAAYK,WAAZ;AADF,CADF,EAIEM,SAASC,cAAT,CAAwB,cAAxB,CAJF;AAMA","file":"index.js","sourcesContent":["import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport App, { Editor } from \"./App\";\nimport registerServiceWorker from \"./registerServiceWorker\";\n\nconst props = {\n  type: 1,\n  businessId: \"1\",\n  API: \"http://api.links123.net/comment/v1\",\n  showList: false\n};\n\nconst editorProps = {\n  showEmoji: true,\n  placeholder: \"说点什么吧\",\n  rows: 5\n  // onSubmit={v => console.log()} // TODO...\n};\n\nReactDOM.render(\n  <App {...props}>\n    <Editor {...editorProps} />\n  </App>,\n  document.getElementById(\"root-comment\")\n);\nregisterServiceWorker();\n"]}
1
+{"version":3,"sources":["../src/index.js"],"names":["Index","props","state","value","handleChangeValue","bind","handleChangeSubmit","setState","console","log","loading","setTimeout","Component","ReactDOM","render","document","getElementById"],"mappings":";;;;AAAA;;;;AACA;;;;AAEA;;;;AACA;;;;;;;;;;;;IAEMA,K;;;AACJ,iBAAYC,KAAZ,EAAmB;AAAA;;AAAA,8GACXA,KADW;;AAEjB,UAAKC,KAAL,GAAa;AACXC,aAAO;AADI,KAAb;AAGA,UAAKC,iBAAL,GAAyB,MAAKA,iBAAL,CAAuBC,IAAvB,OAAzB;AACA,UAAKC,kBAAL,GAA0B,MAAKA,kBAAL,CAAwBD,IAAxB,OAA1B;AANiB;AAOlB;;;;sCAEiBF,K,EAAO;AACvB,WAAKI,QAAL,CAAc,EAAEJ,YAAF,EAAd;AACAK,cAAQC,GAAR,CAAY,2BAAZ,EAAyCN,KAAzC;AACD;;;uCAEkBA,K,EAAO;AAAA;;AACxB,WAAKI,QAAL,CAAc,EAAEG,SAAS,IAAX,EAAd,EAAiC,YAAM;AACrCC,mBAAW,YAAM;AACf,iBAAKJ,QAAL,CAAc,EAAEG,SAAS,KAAX,EAAd;AACD,SAFD,EAEG,IAFH;AAGD,OAJD;AAKAF,cAAQC,GAAR,CAAY,gBAAZ,EAA8BN,KAA9B;AACD;;;6BAEQ;AACP;AACA,aACE;AAAC,qBAAD;AAAA,UAAK,MAAM,CAAX,EAAc,YAAW,MAAzB;AACE,sCAAC,WAAD;AADF,OADF;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACD;;;;EAtEiBS,gB;;AAyEpBC,mBAASC,MAAT,CAAgB,8BAAC,KAAD,OAAhB,EAA2BC,SAASC,cAAT,CAAwB,cAAxB,CAA3B;AACA","file":"index.js","sourcesContent":["import React, { Component } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { Button, Icon } from \"antd\";\nimport App, { Editor } from \"./App\";\nimport registerServiceWorker from \"./registerServiceWorker\";\n\nclass Index extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      value: \"\"\n    };\n    this.handleChangeValue = this.handleChangeValue.bind(this);\n    this.handleChangeSubmit = this.handleChangeSubmit.bind(this);\n  }\n\n  handleChangeValue(value) {\n    this.setState({ value });\n    console.log(\"handleChangeValue value: \", value);\n  }\n\n  handleChangeSubmit(value) {\n    this.setState({ loading: true }, () => {\n      setTimeout(() => {\n        this.setState({ loading: false });\n      }, 2000);\n    });\n    console.log(\"submit value: \", value);\n  }\n\n  render() {\n    // 最简单的用法\n    return (\n      <App type={1} businessId=\"test\">\n        <Editor />\n      </App>\n    );\n\n    // 复杂的用户法\n    // const props = {\n    //   type: 1,\n    //   businessId: \"1\",\n    //   API: \"http://api.links123.net/comment/v1\",\n    //   showList: true\n    // };\n\n    // const editorProps = {\n    //   showEmoji: true,\n    //   placeholder: \"说点什么吧\",\n    //   rows: 5,\n    //   btnLoading: this.state.loading,\n    //   btnDisable: this.state.loading,\n    //   btnSubmitText: \"提交\",\n    //   value: this.state.value,\n    //   onChange: v => this.handleChangeValue(v),\n    //   onSubmit: v => this.handleChangeSubmit(v),\n    //   button: (\n    //     <Button\n    //       type=\"primary\"\n    //       ghost\n    //       // onClick={() => console.log('click btn: ', this.state.value)}\n    //     >\n    //       自定义按钮\n    //     </Button>\n    //   ),\n    //   emojiToolIcon: <Icon type=\"smile\" style={{ fontSize: 23 }} />,\n    //   imageToolIcon: (\n    //     <Icon type=\"cloud-upload-o\" style={{ fontSize: 25, marginLeft: 10 }} />\n    //   )\n    // };\n\n    // return (\n    //   <App {...props}>\n    //     <Editor {...editorProps} />\n    //   </App>\n    // );\n  }\n}\n\nReactDOM.render(<Index />, document.getElementById(\"root-comment\"));\nregisterServiceWorker();\n"]}

+ 1
- 0
lib/lang/index.js View File

@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6 6
 // 语言包
7 7
 // 英文短语和中文提示的对应
8 8
 var data = {
9
+  "not found": "没有数据",
9 10
   "auth failed": "请先登录",
10 11
   "create comment failed": "创建评论失败",
11 12
   "comment favor failed": "评论点赞失败",

+ 1
- 1
lib/lang/index.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../../src/lang/index.js"],"names":["data"],"mappings":";;;;;AAAA;AACA;AACA,IAAMA,OAAO;AACX,iBAAe,MADJ;AAEX,2BAAyB,QAFd;AAGX,0BAAwB,QAHb;AAIX,iCAA+B,UAJpB;AAKX,yBAAuB,UALZ;AAMX,yBAAuB,QANZ;AAOX,wBAAsB,QAPX;AAQX,+BAA6B,UARlB;AASX,wBAAsB;AATX,CAAb;;kBAYeA,I","file":"index.js","sourcesContent":["// 语言包\n// 英文短语和中文提示的对应\nconst data = {\n  \"auth failed\": \"请先登录\",\n  \"create comment failed\": \"创建评论失败\",\n  \"comment favor failed\": \"评论点赞失败\",\n  \"delete comment favor failed\": \"评论取消点赞失败\",\n  \"get comments failed\": \"获取评论列表失败\",\n  \"create reply failed\": \"创建回复失败\",\n  \"reply favor failed\": \"回复点赞失败\",\n  \"delete reply favor failed\": \"删除回复点赞失败\",\n  \"get replies failed\": \"获取回复列表失败\"\n};\n\nexport default data;\n"]}
1
+{"version":3,"sources":["../../src/lang/index.js"],"names":["data"],"mappings":";;;;;AAAA;AACA;AACA,IAAMA,OAAO;AACX,eAAa,MADF;AAEX,iBAAe,MAFJ;AAGX,2BAAyB,QAHd;AAIX,0BAAwB,QAJb;AAKX,iCAA+B,UALpB;AAMX,yBAAuB,UANZ;AAOX,yBAAuB,QAPZ;AAQX,wBAAsB,QARX;AASX,+BAA6B,UATlB;AAUX,wBAAsB;AAVX,CAAb;;kBAaeA,I","file":"index.js","sourcesContent":["// 语言包\n// 英文短语和中文提示的对应\nconst data = {\n  \"not found\": \"没有数据\",\n  \"auth failed\": \"请先登录\",\n  \"create comment failed\": \"创建评论失败\",\n  \"comment favor failed\": \"评论点赞失败\",\n  \"delete comment favor failed\": \"评论取消点赞失败\",\n  \"get comments failed\": \"获取评论列表失败\",\n  \"create reply failed\": \"创建回复失败\",\n  \"reply favor failed\": \"回复点赞失败\",\n  \"delete reply favor failed\": \"删除回复点赞失败\",\n  \"get replies failed\": \"获取回复列表失败\"\n};\n\nexport default data;\n"]}

+ 25
- 25
lib/mock.js View File

@@ -1,62 +1,62 @@
1
-"use strict";
1
+'use strict';
2 2
 
3 3
 Object.defineProperty(exports, "__esModule", {
4 4
   value: true
5 5
 });
6 6
 var comments = exports.comments = {
7 7
   list: [{
8
-    id: "5b3a4f0973df3e6a32b0a9d2",
8
+    id: '5b3a4f0973df3e6a32b0a9d2',
9 9
     user_id: 0,
10
-    user_name: "",
11
-    user_avatar: "",
12
-    content: "test[微笑]",
10
+    user_name: '',
11
+    user_avatar: '',
12
+    content: 'test[微笑]',
13 13
     replies: [{
14
-      id: "5b3c58c473df3e64d8d53afb",
14
+      id: '5b3c58c473df3e64d8d53afb',
15 15
       user_id: 0,
16
-      user_name: "",
17
-      user_avatar: "",
16
+      user_name: '',
17
+      user_avatar: '',
18 18
       reply: null,
19
-      content: "test",
19
+      content: 'test',
20 20
       favor_count: 1,
21 21
       favored: false,
22 22
       created: 1530681540
23 23
     }, {
24
-      id: "5b3c58c473df3e64d8d53afa",
24
+      id: '5b3c58c473df3e64d8d53afa',
25 25
       user_id: 0,
26
-      user_name: "",
27
-      user_avatar: "",
26
+      user_name: '',
27
+      user_avatar: '',
28 28
       reply: {
29
-        id: "5b3c58c473df3e64d8d53afb",
29
+        id: '5b3c58c473df3e64d8d53afb',
30 30
         user_id: 0,
31
-        user_name: "",
32
-        user_avatar: "",
31
+        user_name: '',
32
+        user_avatar: '',
33 33
         reply: null,
34
-        content: "test",
34
+        content: 'test',
35 35
         favor_count: 1,
36 36
         favored: false,
37 37
         created: 1530681540
38 38
       },
39
-      content: "test",
39
+      content: 'test',
40 40
       favor_count: 2,
41 41
       favored: false,
42 42
       created: 1530681540
43 43
     }, {
44
-      id: "5b3c6e4873df3e53f870d912",
44
+      id: '5b3c6e4873df3e53f870d912',
45 45
       user_id: 0,
46
-      user_name: "",
47
-      user_avatar: "",
46
+      user_name: '',
47
+      user_avatar: '',
48 48
       reply: {
49
-        id: "5b3c58c473df3e64d8d53afb",
49
+        id: '5b3c58c473df3e64d8d53afb',
50 50
         user_id: 0,
51
-        user_name: "",
52
-        user_avatar: "",
51
+        user_name: '',
52
+        user_avatar: '',
53 53
         reply: null,
54
-        content: "test",
54
+        content: 'test',
55 55
         favor_count: 1,
56 56
         favored: false,
57 57
         created: 1530681540
58 58
       },
59
-      content: "test",
59
+      content: 'test',
60 60
       favor_count: 0,
61 61
       favored: false,
62 62
       created: 1530687048

+ 1
- 1
lib/mock.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../src/mock.js"],"names":["comments","list","id","user_id","user_name","user_avatar","content","replies","reply","favor_count","favored","created","reply_count","total","page"],"mappings":";;;;;AAAO,IAAMA,8BAAW;AACtBC,QAAM,CACJ;AACEC,QAAI,0BADN;AAEEC,aAAS,CAFX;AAGEC,eAAW,EAHb;AAIEC,iBAAa,EAJf;AAKEC,aAAS,UALX;AAMEC,aAAS,CACP;AACEL,UAAI,0BADN;AAEEC,eAAS,CAFX;AAGEC,iBAAW,EAHb;AAIEC,mBAAa,EAJf;AAKEG,aAAO,IALT;AAMEF,eAAS,MANX;AAOEG,mBAAa,CAPf;AAQEC,eAAS,KARX;AASEC,eAAS;AATX,KADO,EAYP;AACET,UAAI,0BADN;AAEEC,eAAS,CAFX;AAGEC,iBAAW,EAHb;AAIEC,mBAAa,EAJf;AAKEG,aAAO;AACLN,YAAI,0BADC;AAELC,iBAAS,CAFJ;AAGLC,mBAAW,EAHN;AAILC,qBAAa,EAJR;AAKLG,eAAO,IALF;AAMLF,iBAAS,MANJ;AAOLG,qBAAa,CAPR;AAQLC,iBAAS,KARJ;AASLC,iBAAS;AATJ,OALT;AAgBEL,eAAS,MAhBX;AAiBEG,mBAAa,CAjBf;AAkBEC,eAAS,KAlBX;AAmBEC,eAAS;AAnBX,KAZO,EAiCP;AACET,UAAI,0BADN;AAEEC,eAAS,CAFX;AAGEC,iBAAW,EAHb;AAIEC,mBAAa,EAJf;AAKEG,aAAO;AACLN,YAAI,0BADC;AAELC,iBAAS,CAFJ;AAGLC,mBAAW,EAHN;AAILC,qBAAa,EAJR;AAKLG,eAAO,IALF;AAMLF,iBAAS,MANJ;AAOLG,qBAAa,CAPR;AAQLC,iBAAS,KARJ;AASLC,iBAAS;AATJ,OALT;AAgBEL,eAAS,MAhBX;AAiBEG,mBAAa,CAjBf;AAkBEC,eAAS,KAlBX;AAmBEC,eAAS;AAnBX,KAjCO,CANX;AA6DEC,iBAAa,CA7Df;AA8DEH,iBAAa,CA9Df;AA+DEC,aAAS,KA/DX;AAgEEC,aAAS;AAhEX,GADI,CADgB;AAqEtBE,SAAO,CArEe;AAsEtBC,QAAM;AAtEgB,CAAjB","file":"mock.js","sourcesContent":["export const comments = {\n  list: [\n    {\n      id: \"5b3a4f0973df3e6a32b0a9d2\",\n      user_id: 0,\n      user_name: \"\",\n      user_avatar: \"\",\n      content: \"test[微笑]\",\n      replies: [\n        {\n          id: \"5b3c58c473df3e64d8d53afb\",\n          user_id: 0,\n          user_name: \"\",\n          user_avatar: \"\",\n          reply: null,\n          content: \"test\",\n          favor_count: 1,\n          favored: false,\n          created: 1530681540\n        },\n        {\n          id: \"5b3c58c473df3e64d8d53afa\",\n          user_id: 0,\n          user_name: \"\",\n          user_avatar: \"\",\n          reply: {\n            id: \"5b3c58c473df3e64d8d53afb\",\n            user_id: 0,\n            user_name: \"\",\n            user_avatar: \"\",\n            reply: null,\n            content: \"test\",\n            favor_count: 1,\n            favored: false,\n            created: 1530681540\n          },\n          content: \"test\",\n          favor_count: 2,\n          favored: false,\n          created: 1530681540\n        },\n        {\n          id: \"5b3c6e4873df3e53f870d912\",\n          user_id: 0,\n          user_name: \"\",\n          user_avatar: \"\",\n          reply: {\n            id: \"5b3c58c473df3e64d8d53afb\",\n            user_id: 0,\n            user_name: \"\",\n            user_avatar: \"\",\n            reply: null,\n            content: \"test\",\n            favor_count: 1,\n            favored: false,\n            created: 1530681540\n          },\n          content: \"test\",\n          favor_count: 0,\n          favored: false,\n          created: 1530687048\n        }\n      ],\n      reply_count: 4,\n      favor_count: 0,\n      favored: false,\n      created: 1530547977\n    }\n  ],\n  total: 1,\n  page: 1\n};\n"]}
1
+{"version":3,"sources":["../src/mock.js"],"names":["comments","list","id","user_id","user_name","user_avatar","content","replies","reply","favor_count","favored","created","reply_count","total","page"],"mappings":";;;;;AAAO,IAAMA,8BAAW;AACtBC,QAAM,CACJ;AACEC,QAAI,0BADN;AAEEC,aAAS,CAFX;AAGEC,eAAW,EAHb;AAIEC,iBAAa,EAJf;AAKEC,aAAS,UALX;AAMEC,aAAS,CACP;AACEL,UAAI,0BADN;AAEEC,eAAS,CAFX;AAGEC,iBAAW,EAHb;AAIEC,mBAAa,EAJf;AAKEG,aAAO,IALT;AAMEF,eAAS,MANX;AAOEG,mBAAa,CAPf;AAQEC,eAAS,KARX;AASEC,eAAS;AATX,KADO,EAYP;AACET,UAAI,0BADN;AAEEC,eAAS,CAFX;AAGEC,iBAAW,EAHb;AAIEC,mBAAa,EAJf;AAKEG,aAAO;AACLN,YAAI,0BADC;AAELC,iBAAS,CAFJ;AAGLC,mBAAW,EAHN;AAILC,qBAAa,EAJR;AAKLG,eAAO,IALF;AAMLF,iBAAS,MANJ;AAOLG,qBAAa,CAPR;AAQLC,iBAAS,KARJ;AASLC,iBAAS;AATJ,OALT;AAgBEL,eAAS,MAhBX;AAiBEG,mBAAa,CAjBf;AAkBEC,eAAS,KAlBX;AAmBEC,eAAS;AAnBX,KAZO,EAiCP;AACET,UAAI,0BADN;AAEEC,eAAS,CAFX;AAGEC,iBAAW,EAHb;AAIEC,mBAAa,EAJf;AAKEG,aAAO;AACLN,YAAI,0BADC;AAELC,iBAAS,CAFJ;AAGLC,mBAAW,EAHN;AAILC,qBAAa,EAJR;AAKLG,eAAO,IALF;AAMLF,iBAAS,MANJ;AAOLG,qBAAa,CAPR;AAQLC,iBAAS,KARJ;AASLC,iBAAS;AATJ,OALT;AAgBEL,eAAS,MAhBX;AAiBEG,mBAAa,CAjBf;AAkBEC,eAAS,KAlBX;AAmBEC,eAAS;AAnBX,KAjCO,CANX;AA6DEC,iBAAa,CA7Df;AA8DEH,iBAAa,CA9Df;AA+DEC,aAAS,KA/DX;AAgEEC,aAAS;AAhEX,GADI,CADgB;AAqEtBE,SAAO,CArEe;AAsEtBC,QAAM;AAtEgB,CAAjB","file":"mock.js","sourcesContent":["export const comments = {\n  list: [\n    {\n      id: '5b3a4f0973df3e6a32b0a9d2',\n      user_id: 0,\n      user_name: '',\n      user_avatar: '',\n      content: 'test[微笑]',\n      replies: [\n        {\n          id: '5b3c58c473df3e64d8d53afb',\n          user_id: 0,\n          user_name: '',\n          user_avatar: '',\n          reply: null,\n          content: 'test',\n          favor_count: 1,\n          favored: false,\n          created: 1530681540,\n        },\n        {\n          id: '5b3c58c473df3e64d8d53afa',\n          user_id: 0,\n          user_name: '',\n          user_avatar: '',\n          reply: {\n            id: '5b3c58c473df3e64d8d53afb',\n            user_id: 0,\n            user_name: '',\n            user_avatar: '',\n            reply: null,\n            content: 'test',\n            favor_count: 1,\n            favored: false,\n            created: 1530681540,\n          },\n          content: 'test',\n          favor_count: 2,\n          favored: false,\n          created: 1530681540,\n        },\n        {\n          id: '5b3c6e4873df3e53f870d912',\n          user_id: 0,\n          user_name: '',\n          user_avatar: '',\n          reply: {\n            id: '5b3c58c473df3e64d8d53afb',\n            user_id: 0,\n            user_name: '',\n            user_avatar: '',\n            reply: null,\n            content: 'test',\n            favor_count: 1,\n            favored: false,\n            created: 1530681540,\n          },\n          content: 'test',\n          favor_count: 0,\n          favored: false,\n          created: 1530687048,\n        },\n      ],\n      reply_count: 4,\n      favor_count: 0,\n      favored: false,\n      created: 1530547977,\n    },\n  ],\n  total: 1,\n  page: 1,\n};\n"]}

+ 2
- 2
package.json View File

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "comment",
3
-  "version": "0.1.0",
3
+  "version": "0.2.0",
4 4
   "main": "lib/App.js",
5 5
   "description": "通用评论",
6 6
   "keywords": [
@@ -31,7 +31,7 @@
31 31
   },
32 32
   "scripts": {
33 33
     "precommit": "lint-staged",
34
-    "prettier": "prettier --trailing-comma --write 'src/**/*.{js,jsx,json,css,less}'",
34
+    "prettier": "prettier --single-quote --trailing-comma --write 'src/**/*.{js,jsx,json,css,less}'",
35 35
     "start": "react-app-rewired start",
36 36
     "build": "react-app-rewired build",
37 37
     "test": "react-app-rewired test --env=jsdom",

+ 13
- 0
src/App.css View File

@@ -7,3 +7,16 @@
7 7
   position: fixed;
8 8
   top: 45%;
9 9
 }
10
+.comment-header-tag {
11
+  border: 1px solid #cecece;
12
+  border-radius: 0;
13
+  color: #666;
14
+}
15
+.comment-header-tip {
16
+  color: #5198eb;
17
+  margin-right: 15px;
18
+  margin-left: 5px;
19
+}
20
+.comment-header-text {
21
+  color: #666;
22
+}

+ 101
- 36
src/App.js View File

@@ -1,6 +1,6 @@
1 1
 import React, { Component } from "react";
2 2
 import PropTypes from "prop-types";
3
-import { message } from "antd";
3
+import { message, Tag } from "antd";
4 4
 import axios from "./axios";
5 5
 import { ERROR_DEFAULT, LIMIT } from "./constant";
6 6
 import { CommentContext } from "./Comment";
@@ -8,6 +8,7 @@ import { isFunction } from "./helper";
8 8
 import CommentInput from "./components/CommentInput";
9 9
 import CommentList from "./components/CommentList";
10 10
 import Editor from "./components/Editor";
11
+import lang from "./lang";
11 12
 import "./App.css";
12 13
 
13 14
 class App extends Component {
@@ -32,6 +33,7 @@ class App extends Component {
32 33
     this.sCreateComment = this.sCreateComment.bind(this);
33 34
     this.sCreateReply = this.sCreateReply.bind(this);
34 35
     this.sCommentFavor = this.sCommentFavor.bind(this);
36
+    this.sReplyFavor = this.sReplyFavor.bind(this);
35 37
     this.sOssSts = this.sOssSts.bind(this);
36 38
   }
37 39
 
@@ -82,10 +84,10 @@ class App extends Component {
82 84
       })
83 85
       .catch(error => {
84 86
         if (error.response && error.response.data && error.response.data.msg) {
85
-          message.error(error.response.data.msg || ERROR_DEFAULT);
87
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
86 88
           return;
87 89
         }
88
-        message.error(error.message || ERROR_DEFAULT);
90
+        message.error(lang[error.message] || ERROR_DEFAULT);
89 91
       })
90 92
       .finally(() => {
91 93
         this.handleChangeLoading("sGetComment", false);
@@ -101,22 +103,25 @@ class App extends Component {
101 103
     axios
102 104
       .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`)
103 105
       .then(response => {
104
-        const { list: replies } = response.data;
105
-        if (!replies) {
106
+        if (!response.data.list) {
106 107
           message.info("没有更多数据了!");
107 108
         }
108 109
         const list = this.state.list.map(item => {
109 110
           if (item.id === commentId) {
110 111
             if (!item.replies) item.replies = [];
111
-            if (replies) {
112
+            if (response.data.list) {
112 113
               if (page === 1) {
113 114
                 // 如果当前页数为第一页,则清空当前所有的 replies
114 115
                 // 并将获取到的 replies 存放在 state
115
-                item.replies = replies;
116
+                item.replies = response.data.list;
116 117
               } else {
117
-                item.replies = item.replies.concat(replies);
118
+                item.replies = item.replies
119
+                  .filter(o => !o.isTemporary)
120
+                  .concat(response.data.list);
118 121
                 // 如果当前页数非第一页,则合并 replies
119 122
               }
123
+              item.reply_count = response.data.total;
124
+              item.reply_page = response.data.page;
120 125
             } else {
121 126
               item.isNoMoreReply = true;
122 127
             }
@@ -127,10 +132,10 @@ class App extends Component {
127 132
       })
128 133
       .catch(error => {
129 134
         if (error.response && error.response.data && error.response.data.msg) {
130
-          message.error(error.response.data.msg || ERROR_DEFAULT);
135
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
131 136
           return;
132 137
         }
133
-        message.error(error.message || ERROR_DEFAULT);
138
+        message.error(lang[error.message] || ERROR_DEFAULT);
134 139
       })
135 140
       .finally(() => {
136 141
         this.handleChangeLoading("sGetReply", false);
@@ -139,9 +144,9 @@ class App extends Component {
139 144
 
140 145
   /**
141 146
    * 添加评论
142
-   * @param {string} content comment content
147
+   * @param {object} {content} comment content
143 148
    */
144
-  sCreateComment(content) {
149
+  sCreateComment({ content } = {}) {
145 150
     if (!content) return message.error("评论内容不能为空 ");
146 151
     this.handleChangeLoading("sCreateComment", true);
147 152
     const { API, type, businessId } = this.props;
@@ -159,19 +164,19 @@ class App extends Component {
159 164
         // 将数据写入到 list 中
160 165
         // 临时插入
161 166
         // 等到获取数据之后,删除临时数据
162
-        const { list } = this.state;
167
+        const { list, total } = this.state;
163 168
         list.unshift({
164 169
           ...response.data,
165 170
           isTemporary: true // 临时的数据
166 171
         });
167
-        this.setState({ list });
172
+        this.setState({ list, total: total + 1 });
168 173
       })
169 174
       .catch(error => {
170 175
         if (error.response && error.response.data && error.response.data.msg) {
171
-          message.error(error.response.data.msg || ERROR_DEFAULT);
176
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
172 177
           return;
173 178
         }
174
-        message.error(error.message || ERROR_DEFAULT);
179
+        message.error(lang[error.message] || ERROR_DEFAULT);
175 180
       })
176 181
       .finally(() => {
177 182
         this.handleChangeLoading("sCreateComment", false);
@@ -184,6 +189,8 @@ class App extends Component {
184 189
    * @param {object} data { comment_id, content, [reply_id] }
185 190
    */
186 191
   sCreateReply(data, cb) {
192
+    console.log("list: ", this.state.list);
193
+
187 194
     if (!data.content) return message.error("回复内容不能为空 ");
188 195
     this.handleChangeLoading("sCreateReply", true);
189 196
     const { API } = this.props;
@@ -193,26 +200,30 @@ class App extends Component {
193 200
       withCredentials: true
194 201
     })
195 202
       .then(response => {
196
-        // // 将该条数据插入到 list 中
197
-        // const list = this.state.list.map(item => {
198
-        //   if (item.id === data.comment_id) {
199
-        //     if (!item.replies) item.replies = [];
200
-        //     item.reply_count += 1
201
-        //     item.replies.unshift(response.data);
202
-        //   }
203
-        //   return item;
204
-        // });
205
-        // this.setState({ list });
206
-        this.sGetReply({ commentId: data.comment_id });
207 203
         message.success("回复成功!");
208 204
         if (isFunction(cb)) cb();
205
+        // 将数据写入到 list 中
206
+        // 临时插入
207
+        // 等到获取数据之后,删除临时数据
208
+        const list = this.state.list.map(item => {
209
+          if (item.id === data.comment_id) {
210
+            if (!item.replies) item.replies = [];
211
+            item.replies.push({
212
+              ...response.data,
213
+              isTemporary: true // 临时的数据
214
+            });
215
+            item.reply_count += 1;
216
+          }
217
+          return item;
218
+        });
219
+        this.setState({ list });
209 220
       })
210 221
       .catch(error => {
211 222
         if (error.response && error.response.data && error.response.data.msg) {
212
-          message.error(error.response.data.msg || ERROR_DEFAULT);
223
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
213 224
           return;
214 225
         }
215
-        message.error(error.message || ERROR_DEFAULT);
226
+        message.error(lang[error.message] || ERROR_DEFAULT);
216 227
       })
217 228
       .finally(() => {
218 229
         this.handleChangeLoading("sCreateReply", false);
@@ -220,7 +231,7 @@ class App extends Component {
220 231
   }
221 232
 
222 233
   /**
223
-   * 点赞/取消点赞
234
+   * 评论 点赞/取消点赞
224 235
    * @param {string} commentId { commentId }
225 236
    * @param {boolean} favored   是否已经点过赞
226 237
    */
@@ -245,16 +256,58 @@ class App extends Component {
245 256
       })
246 257
       .catch(error => {
247 258
         if (error.response && error.response.data && error.response.data.msg) {
248
-          message.error(error.response.data.msg || ERROR_DEFAULT);
259
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
249 260
           return;
250 261
         }
251
-        message.error(error.message || ERROR_DEFAULT);
262
+        message.error(lang[error.message] || ERROR_DEFAULT);
252 263
       })
253 264
       .finally(() => {
254 265
         this.handleChangeLoading("sCommentFavor", false);
255 266
       });
256 267
   }
257 268
 
269
+  /**
270
+   * 回复 点赞/取消点赞
271
+   * @param {string} replyId  replyId
272
+   * @param {string} commentId  commentId
273
+   * @param {boolean} favored   是否已经点过赞
274
+   */
275
+  sReplyFavor(replyId, commentId, favored) {
276
+    this.handleChangeLoading("sReplyFavor", true);
277
+    console.log("replyId, commentId ", replyId, commentId);
278
+
279
+    const { API } = this.props;
280
+    axios(`${API}/replies/${replyId}/favor`, {
281
+      method: favored ? "delete" : "put",
282
+      withCredentials: true
283
+    })
284
+      .then(response => {
285
+        console.log("response: ", response);
286
+
287
+        message.success(favored ? "取消点赞成功!" : "点赞成功!");
288
+        // TODO: (2018.07.20 node) 对评论的回复点赞,报错
289
+        // // 更新 list 中的该项数据的 favored
290
+        // const list = this.state.list.map(item => {
291
+        //   if (item.id === replyId) {
292
+        //     item.favored = !favored;
293
+        //     item.favor_count += favored ? -1 : 1;
294
+        //   }
295
+        //   return item;
296
+        // });
297
+        // this.setState({ list });
298
+      })
299
+      .catch(error => {
300
+        if (error.response && error.response.data && error.response.data.msg) {
301
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
302
+          return;
303
+        }
304
+        message.error(lang[error.message] || ERROR_DEFAULT);
305
+      })
306
+      .finally(() => {
307
+        this.handleChangeLoading("sReplyFavor", false);
308
+      });
309
+  }
310
+
258 311
   /**
259 312
    * 获取 OSS 上传的参数
260 313
    */
@@ -268,10 +321,10 @@ class App extends Component {
268 321
       })
269 322
       .catch(error => {
270 323
         if (error.response && error.response.data && error.response.data.msg) {
271
-          message.error(error.response.data.msg || ERROR_DEFAULT);
324
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
272 325
           return;
273 326
         }
274
-        message.error(error.message || ERROR_DEFAULT);
327
+        message.error(lang[error.message] || ERROR_DEFAULT);
275 328
       })
276 329
       .finally(() => {
277 330
         this.handleChangeLoading("sOssSts", false);
@@ -286,6 +339,7 @@ class App extends Component {
286 339
       sCreateComment: this.sCreateComment,
287 340
       sGetComment: this.sGetComment,
288 341
       sCommentFavor: this.sCommentFavor,
342
+      sReplyFavor: this.sReplyFavor,
289 343
       sCreateReply: this.sCreateReply,
290 344
       sGetReply: this.sGetReply,
291 345
       sOssSts: this.sOssSts
@@ -294,6 +348,15 @@ class App extends Component {
294 348
     return (
295 349
       <CommentContext.Provider value={value}>
296 350
         <div className="comment">
351
+          {this.props.showHeader && (
352
+            <div style={{ marginBottom: 15 }}>
353
+              <Tag className="comment-header-tag">留言</Tag>
354
+              <span className="comment-header-tip">口碑</span>
355
+              <span className="comment-header-text">
356
+                (全站挑出毛病或提出合理建议,奖励10到100元红包)
357
+              </span>
358
+            </div>
359
+          )}
297 360
           {this.props.showEditor && (
298 361
             <CommentInput content={this.props.children} />
299 362
           )}
@@ -313,13 +376,15 @@ App.propTypes = {
313 376
   businessId: PropTypes.string.isRequired, // 评论的 business_id
314 377
   API: PropTypes.string, // 评论的 API 前缀
315 378
   showList: PropTypes.bool, // 是否显示评论列表
316
-  showEditor: PropTypes.bool // 是否显示评论输入框
379
+  showEditor: PropTypes.bool, // 是否显示评论输入框
380
+  showHeader: PropTypes.bool // 是否显示评论顶部的提示
317 381
 };
318 382
 
319 383
 App.defaultProps = {
320 384
   API: "http://api.links123.net/comment/v1",
321 385
   showList: true,
322
-  showEditor: true
386
+  showEditor: true,
387
+  showHeader: true
323 388
 };
324 389
 
325 390
 export { Editor };

+ 0
- 12
src/CHANGELOG.md View File

@@ -1,12 +0,0 @@
1
-# CHANGELOG
2
-
3
-
4
-## 0.1.0
5
-
6
-- [x] 不兼容更新。
7
-- [x] 添加了很多 `Props`
8
-- [x] 组件导出为 `Comment` 和 `Editor`
9
-
10
-
11
-
12
-

+ 4
- 0
src/avatar.js
File diff suppressed because it is too large
View File


+ 11
- 10
src/components/CommentBox/index.css View File

@@ -1,4 +1,4 @@
1
-.showMore {
1
+.comment-show-more {
2 2
   color: #4a90e2;
3 3
   text-align: center;
4 4
   width: 100px;
@@ -8,40 +8,41 @@
8 8
   margin: 0 auto;
9 9
   transition: all 0.3s;
10 10
 }
11
-.showMore:hover {
11
+.comment-show-more:hover {
12 12
   background-color: #f5f5f5;
13 13
   color: #1890ff;
14 14
 }
15
-.moreBox {
15
+.comment-more-box {
16 16
   text-align: center;
17 17
   width: 90%;
18
-  margin-left: 40px;
18
+  margin-left: 50px;
19
+  margin-top: 10px;
19 20
   height: 40px;
20 21
   display: inline-block;
21 22
 }
22 23
 @media screen and (max-width: 616px) and (min-width: 449px) {
23
-  .moreBox {
24
+  .comment-more-box {
24 25
     text-align: center;
25 26
     width: 85%;
26
-    margin-left: 40px;
27
+    margin-left: 50px;
27 28
     height: 40px;
28 29
     display: inline-block;
29 30
   }
30 31
 }
31 32
 @media screen and (max-width: 449px) and (min-width: 365px) {
32
-  .moreBox {
33
+  .comment-more-box {
33 34
     text-align: center;
34 35
     width: 80%;
35
-    margin-left: 40px;
36
+    margin-left: 50px;
36 37
     height: 40px;
37 38
     display: inline-block;
38 39
   }
39 40
 }
40 41
 @media screen and (max-width: 365px) {
41
-  .moreBox {
42
+  .comment-more-box {
42 43
     text-align: center;
43 44
     width: 75%;
44
-    margin-left: 40px;
45
+    margin-left: 50px;
45 46
     height: 40px;
46 47
     display: inline-block;
47 48
   }

+ 20
- 14
src/components/CommentBox/index.js View File

@@ -39,9 +39,10 @@ class CommentBox extends Component {
39 39
   /**
40 40
    * 渲染回复 DOM
41 41
    * @param {array} replies 回复列表
42
+   * @param {number} replies 回复的数量
42 43
    * @param {boolean} isNoMoreReply 是否没有更多回复
43 44
    */
44
-  renderReplies(replies, isNoMoreReply) {
45
+  renderReplies(replies, replyCount, isNoMoreReply) {
45 46
     const { commentId } = this.props;
46 47
     const { showReply } = this.state;
47 48
     if (showReply && replies && replies.length) {
@@ -56,17 +57,18 @@ class CommentBox extends Component {
56 57
                   replyId={item.id}
57 58
                   key={item.id}
58 59
                   content={item}
59
-                  type="reply"
60
+                  action="replyToReply" // 回复的回复
60 61
                 />,
61
-                <div className="moreBox" key="show_more_button">
62
-                  {!isNoMoreReply && (
63
-                    <span
64
-                      className="showMore"
65
-                      onClick={() => this.handleGetMoreReply(commentId)}
66
-                    >
67
-                      查看更多回复
68
-                    </span>
69
-                  )}
62
+                <div className="comment-more-box" key="show_more_button">
63
+                  {!isNoMoreReply &&
64
+                    replyCount !== len && (
65
+                      <span
66
+                        className="comment-show-more"
67
+                        onClick={() => this.handleGetMoreReply(commentId)}
68
+                      >
69
+                        查看更多回复
70
+                      </span>
71
+                    )}
70 72
 
71 73
                   <a
72 74
                     style={{ float: "right" }}
@@ -83,7 +85,7 @@ class CommentBox extends Component {
83 85
                 replyId={item.id}
84 86
                 key={item.id}
85 87
                 content={item}
86
-                type="reply"
88
+                action="replyToReply" // 评论的回复
87 89
               />
88 90
             );
89 91
           })}
@@ -103,9 +105,13 @@ class CommentBox extends Component {
103 105
           onShowReply={this.handleToggleReply}
104 106
           showReply={showReply}
105 107
           commentId={content.id}
106
-          type="comment"
108
+          action="reply" // 评论的回复
107 109
         />
108
-        {this.renderReplies(content.replies, content.isNoMoreReply)}
110
+        {this.renderReplies(
111
+          content.replies,
112
+          content.reply_count,
113
+          content.isNoMoreReply
114
+        )}
109 115
       </div>
110 116
     );
111 117
   }

+ 24
- 97
src/components/CommentInput/index.js View File

@@ -1,143 +1,70 @@
1 1
 import React, { Component } from "react";
2 2
 import PropTypes from "prop-types";
3
-import { OSS_LINK } from "../../constant";
4 3
 import Comment from "../../Comment";
5 4
 
6 5
 class CommentInput extends Component {
7 6
   constructor(props) {
8 7
     super(props);
9
-    this.state = {
10
-      value: "",
11
-
12
-      fileList: [], // 图片列表
13
-      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
14
-    };
15
-    this.handleChange = this.handleChange.bind(this);
8
+    this.state = {};
16 9
     this.handleSubmit = this.handleSubmit.bind(this);
17
-    this.handleChangeFileList = this.handleChangeFileList.bind(this);
18
-    this.handleChangeEmoji = this.handleChangeEmoji.bind(this);
19
-    this.handleUpload = this.handleUpload.bind(this);
20
-  }
21
-
22
-  handleChange(value) {
23
-    this.setState({ value });
24
-  }
25
-
26
-  handleChangeFileList(fileList) {
27
-    this.setState({ fileList });
28
-  }
29
-
30
-  handleChangeEmoji(emojiId) {
31
-    let { value } = this.state;
32
-    value += `[${emojiId}]`;
33
-    this.setState({ value });
34
-    React.Children.forEach(this.props.content, child => {
35
-      // 如果 Editor 的父组件传入了 onChange 事件,则需要将改变之后的值传递给父组件
36
-      if (child.props.onChange) {
37
-        child.props.onChange(value);
38
-      }
39
-    });
40 10
   }
41 11
 
42
-  handleUpload({ uid, path }) {
43
-    const { fileMap } = this.state;
44
-    fileMap[uid] = path;
45
-    this.setState({ fileMap });
46
-  }
47
-
48
-  handleSubmit() {
49
-    let { value, fileMap, fileList } = this.state;
50
-    if (fileList.length) {
51
-      value += "<br/>";
52
-      fileList.forEach(item => {
53
-        value += `[${OSS_LINK}${fileMap[item.uid]}]`;
54
-      });
55
-    }
56
-
57
-    const { type, commentId, replyId, handleToggleInput } = this.props;
58
-    if (type === "normal") {
12
+  handleSubmit(value) {
13
+    const { action, commentId, replyId, callback } = this.props;
14
+    if (action === "comment") {
59 15
       this.props.app.sCreateComment({
60 16
         content: value
61 17
       });
62
-    } else if (type === "comment") {
18
+    } else if (action === "reply") {
63 19
       this.props.app.sCreateReply(
64 20
         {
65 21
           comment_id: commentId,
66 22
           content: value
67 23
         },
68
-        () => handleToggleInput()
24
+        () => callback && callback()
69 25
       );
70
-    } else if (type === "reply") {
26
+    } else if (action === "replyToReply") {
71 27
       this.props.app.sCreateReply(
72 28
         {
73 29
           comment_id: commentId,
74 30
           content: value,
75 31
           reply_id: replyId
76 32
         },
77
-        () => handleToggleInput()
33
+        () => callback && callback()
78 34
       );
79 35
     }
80
-
81
-    React.Children.forEach(this.props.content, child => {
82
-      // 如果 Editor 的父组件传入了 onSubmit 事件,则需要将改变之后的值传递给父组件
83
-      if (child.props.onSubmit) {
84
-        child.props.onSubmit(value);
85
-      }
86
-    });
87 36
   }
88 37
 
89 38
   render() {
90
-    const { type } = this.props;
91
-    const { value, fileList } = this.state;
92
-
93 39
     const childrenWithProps = React.Children.map(this.props.content, child => {
94 40
       return React.cloneElement(child, {
95
-        value: value,
96
-        fileList: fileList,
97
-        onChange: this.handleChange,
41
+        // 编辑器本身不提交值,但 CommentInput 会提交
42
+        // CommentInput 主要是负责评论的业务逻辑,提交评论和回复
43
+        // 默认使用 CommentInput 的 onSubmit 来提交评论
44
+        // 但也可以使用 Editor 的 props 来覆盖 onSubmit
98 45
         onSubmit: this.handleSubmit,
99
-        onChangeFileList: this.handleChangeFileList,
100
-        onChangeEmoji: this.handleChangeEmoji,
101
-        onUpload: this.handleUpload,
102
-        loading: this.props.app.loading.sCreateComment,
103
-        ...child.props
46
+        ...child.props,
47
+        // 如果当前的编辑器不是“评论”,则编辑器高度减小一些
48
+        rows:
49
+          this.props.action === "comment"
50
+            ? child.props.rows
51
+            : child.props.rows - 1
104 52
       });
105 53
     });
106 54
 
107
-    return (
108
-      <div>
109
-        {type === "normal" ? (
110
-          <div>
111
-            <span
112
-              style={{
113
-                border: "1px solid #CECECE",
114
-                color: "#666",
115
-                padding: "2px 3px"
116
-              }}
117
-            >
118
-              回复
119
-            </span>
120
-            <span style={{ marginLeft: "20px", color: "#5198EB" }}>
121
-              口碑
122
-              <span style={{ marginLeft: "20px", color: "#666666" }}>
123
-                (全站挑出毛病或提出合理建议,奖励10到100元红包)
124
-              </span>
125
-            </span>
126
-          </div>
127
-        ) : null}
128
-        <div style={{ marginTop: 40 }}>{childrenWithProps}</div>
129
-      </div>
130
-    );
55
+    return <div>{childrenWithProps}</div>;
131 56
   }
132 57
 }
133 58
 
134 59
 CommentInput.propTypes = {
135
-  // normal 有切换回复/口碑的 header ; comment 评论输入框 / reply 回复输入框
136
-  type: PropTypes.oneOf(["normal", "comment", "reply"])
60
+  // comment 评论
61
+  // reply 评论的回复
62
+  // replyToReply 回复的回复
63
+  action: PropTypes.oneOf(["comment", "reply", "replyToReply"])
137 64
 };
138 65
 
139 66
 CommentInput.defaultProps = {
140
-  type: "normal"
67
+  action: "comment"
141 68
 };
142 69
 
143 70
 export default Comment(CommentInput);

+ 2
- 2
src/components/CommentList/index.css View File

@@ -1,4 +1,4 @@
1
-.showMore {
1
+.comment-list-show-more {
2 2
   color: #4a90e2;
3 3
   text-align: center;
4 4
   width: 100%;
@@ -8,7 +8,7 @@
8 8
   margin: 40px auto;
9 9
   transition: all 0.3s;
10 10
 }
11
-.showMore:hover {
11
+.comment-list-show-more:hover {
12 12
   background-color: #f5f5f5;
13 13
   color: #1890ff;
14 14
 }

+ 14
- 9
src/components/CommentList/index.js View File

@@ -17,28 +17,33 @@ class CommentList extends Component {
17 17
   render() {
18 18
     const {
19 19
       list,
20
+      total,
20 21
       page,
21 22
       loading,
22 23
       isNoMoreComment,
23 24
       sGetComment
24 25
     } = this.props.app;
25 26
 
26
-    const spinning = Boolean(loading.sGetComment || loading.sCommentFavor);
27
+    const spinning = Boolean(
28
+      loading.sGetComment || loading.sCommentFavor || loading.sReplyFavor
29
+    );
27 30
     return (
28 31
       <div>
29 32
         <Spin spinning={spinning}>
33
+          <div>共 {total} 条评论</div>
30 34
           {list.map(item => (
31 35
             <CommentBox content={item} key={item.id} commentId={item.id} />
32 36
           ))}
33 37
 
34
-          {!isNoMoreComment && (
35
-            <div
36
-              className="showMore"
37
-              onClick={() => sGetComment({ page: page + 1 })}
38
-            >
39
-              <span>查看更多评论</span>
40
-            </div>
41
-          )}
38
+          {!isNoMoreComment &&
39
+            list.length !== total && (
40
+              <div
41
+                className="comment-list-show-more"
42
+                onClick={() => sGetComment({ page: page + 1 })}
43
+              >
44
+                <span>查看更多评论</span>
45
+              </div>
46
+            )}
42 47
         </Spin>
43 48
       </div>
44 49
     );

+ 20
- 17
src/components/ContentItem/index.css View File

@@ -1,50 +1,53 @@
1
-.left {
1
+.comment-item-box {
2
+  margin: 10px 0 0 0;
3
+  padding: 15px 5px 0 5px;
4
+  border-top: 1px solid #eee;
5
+}
6
+.comment-item-left {
2 7
   display: inline-block;
3 8
   vertical-align: top;
4 9
   width: 40px;
5 10
 }
6
-.right {
11
+.comment-item-right {
7 12
   display: inline-block;
8 13
   width: 90%;
9 14
   margin-left: 10px;
15
+  margin-bottom: 20px;
10 16
 }
11
-.box {
12
-  margin: 10px;
13
-  padding: 15px 5px;
14
-  border-top: 1px solid #eee;
15
-}
16
-.content {
17
+.comment-item-content {
17 18
   margin: 10px 0;
18 19
 }
19
-.itemLeft {
20
+.comment-item-bottom {
21
+  margin: 20px auto;
22
+}
23
+.comment-item-bottom-left {
20 24
   float: left;
25
+  user-select: none;
21 26
 }
22
-.itemRight {
27
+.comment-item-bottom-right {
23 28
   float: right;
24 29
   margin-left: 5px;
30
+  cursor: pointer;
25 31
 }
26
-.bottom {
27
-  margin: 20px auto;
28
-}
29
-.favored {
32
+.comment-favored {
30 33
   color: #4a90e2;
31 34
 }
32 35
 @media screen and (max-width: 616px) and (min-width: 449px) {
33
-  .right {
36
+  .comment-item-right {
34 37
     display: inline-block;
35 38
     width: 85%;
36 39
     margin-left: 10px;
37 40
   }
38 41
 }
39 42
 @media screen and (max-width: 449px) and (min-width: 365px) {
40
-  .right {
43
+  .comment-item-right {
41 44
     display: inline-block;
42 45
     width: 80%;
43 46
     margin-left: 10px;
44 47
   }
45 48
 }
46 49
 @media screen and (max-width: 365px) {
47
-  .right {
50
+  .comment-item-right {
48 51
     display: inline-block;
49 52
     width: 75%;
50 53
     margin-left: 10px;

+ 58
- 37
src/components/ContentItem/index.js View File

@@ -1,24 +1,30 @@
1 1
 import React, { Component } from "react";
2 2
 import PropTypes from "prop-types";
3
-import { Avatar, Icon } from "antd";
3
+import { Avatar, Icon, Tooltip } from "antd";
4 4
 import dayjs from "dayjs";
5
+import "dayjs/locale/zh-cn";
6
+import relativeTime from "dayjs/plugin/relativeTime";
5 7
 import Comment from "../../Comment";
6 8
 import CommentInput from "../CommentInput";
9
+import avatar from "../../avatar";
7 10
 import { renderContent } from "../../helper";
8 11
 import "./index.css";
9 12
 
13
+dayjs.locale("zh-cn");
14
+dayjs.extend(relativeTime);
15
+
10 16
 class CommentItem extends Component {
11 17
   constructor(props) {
12 18
     super(props);
13 19
     this.state = {
14
-      isShowInput: false
20
+      showInput: false
15 21
     };
16 22
     this.handleToggleInput = this.handleToggleInput.bind(this);
17 23
     this.renderTextWithReply = this.renderTextWithReply.bind(this);
18 24
   }
19 25
 
20 26
   handleToggleInput() {
21
-    this.setState({ isShowInput: !this.state.isShowInput });
27
+    this.setState({ showInput: !this.state.showInput });
22 28
   }
23 29
 
24 30
   renderTextWithReply(text, content) {
@@ -40,75 +46,89 @@ class CommentItem extends Component {
40 46
       commentId,
41 47
       replyId,
42 48
       content,
43
-      type,
49
+      action,
44 50
       showReply,
45 51
       onShowReply,
46 52
       app
47 53
     } = this.props;
48
-    const { isShowInput } = this.state;
49
-    const isComment = type === "comment";
54
+
55
+    const { showInput } = this.state;
56
+
50 57
     return (
51
-      <div className="box">
52
-        <div className="left">
53
-          <Avatar src={content.user_avatar} size="large" />
58
+      <div className="comment-item-box">
59
+        <div className="comment-item-left">
60
+          <Avatar src={content.user_avatar || avatar} size="large" />
54 61
         </div>
55
-        <div className="right">
56
-          <div className="name">
62
+        <div className="comment-item-right">
63
+          <div>
57 64
             <a href={`/${content.user_id}`}>
58 65
               {content.user_name || "暂无昵称"}
59 66
             </a>
60 67
 
61 68
             <span style={{ marginLeft: 10 }}>
62
-              {dayjs(content.created * 1000).format("YYYY-MM-DD HH:mm:ss")}
69
+              <Tooltip
70
+                placement="top"
71
+                title={dayjs(content.created * 1000).format(
72
+                  "YYYY-MM-DD HH:mm:ss"
73
+                )}
74
+              >
75
+                {dayjs(content.created * 1000).fromNow()}
76
+              </Tooltip>
63 77
             </span>
64 78
           </div>
65 79
           <div
66
-            className="content"
80
+            className="comment-item-content"
67 81
             dangerouslySetInnerHTML={{
68 82
               __html: renderContent(
69 83
                 this.renderTextWithReply(content.content, content)
70 84
               )
71 85
             }}
72 86
           />
73
-          <div className="bottom">
74
-            {isComment && content.reply_count ? (
87
+          <div className="comment-item-bottom">
88
+            {content.reply_count ? (
75 89
               <div>
76
-                <a
77
-                  className="itemLeft"
78
-                  onClick={onShowReply}
79
-                  style={{ userSelect: "none" }}
80
-                >
90
+                <a className="comment-item-bottom-left" onClick={onShowReply}>
81 91
                   {content.reply_count} 条回复
82 92
                   {showReply ? <Icon type="up" /> : <Icon type="down" />}
83 93
                 </a>
84 94
               </div>
85 95
             ) : null}
86 96
 
87
-            <a onClick={this.handleToggleInput} className="itemRight">
97
+            <a
98
+              onClick={this.handleToggleInput}
99
+              className="comment-item-bottom-right"
100
+            >
88 101
               &nbsp; 回复
89 102
             </a>
90 103
             <div
91
-              className="itemRight"
92
-              style={{ cursor: "pointer" }}
93
-              onClick={() => app.sCommentFavor(content.id, content.favored)}
104
+              className="comment-item-bottom-right"
105
+              onClick={() => {
106
+                if (replyId) {
107
+                  // 如果有 replyId,则说明是评论的回复
108
+                  app.sReplyFavor(content.id, commentId, content.favored);
109
+                  return;
110
+                }
111
+                app.sCommentFavor(content.id, content.favored);
112
+              }}
94 113
             >
95 114
               <Icon
96 115
                 type="like-o"
97
-                className={content.favored ? "favored" : ""}
116
+                className={content.favored ? "comment-favored" : ""}
98 117
               />
99 118
               &nbsp;{content.favor_count}
100 119
             </div>
101 120
           </div>
102
-
103
-          {isShowInput ? (
104
-            <CommentInput
105
-              type={type}
106
-              replyId={replyId}
107
-              commentId={commentId}
108
-              handleToggleInput={this.handleToggleInput}
109
-            />
110
-          ) : null}
111 121
         </div>
122
+
123
+        {showInput && (
124
+          <CommentInput
125
+            content={app.children}
126
+            action={action}
127
+            replyId={replyId}
128
+            commentId={commentId}
129
+            callback={this.handleToggleInput}
130
+          />
131
+        )}
112 132
       </div>
113 133
     );
114 134
   }
@@ -117,13 +137,14 @@ class CommentItem extends Component {
117 137
 CommentItem.propTypes = {
118 138
   content: PropTypes.object.isRequired,
119 139
   // comment 评论
120
-  // reply 回复
121
-  type: PropTypes.oneOf(["comment", "reply"]),
140
+  // reply 评论的回复
141
+  // replyToReply 回复的回复
142
+  action: PropTypes.oneOf(["comment", "reply", "replyToReply"]),
122 143
   onShowReply: PropTypes.func
123 144
 };
124 145
 
125 146
 CommentItem.defaultProps = {
126
-  type: "comment",
147
+  action: "comment",
127 148
   onShowReply: () => {}
128 149
 };
129 150
 

+ 0
- 1
src/components/Editor/Upload.js View File

@@ -10,7 +10,6 @@ import {
10 10
   MAX_UPLOAD_NUMBER
11 11
 } from "../../constant";
12 12
 import Comment from "../../Comment";
13
-// import styles from "./Upload.less";
14 13
 import "./Upload.css";
15 14
 
16 15
 const client = oss => {

+ 23
- 29
src/components/Editor/index.css View File

@@ -1,4 +1,4 @@
1
-.editor {
1
+.comment-editor {
2 2
   box-sizing: border-box;
3 3
   margin: 0;
4 4
   padding: 0;
@@ -16,67 +16,61 @@
16 16
   border-radius: 4px;
17 17
   transition: all 0.3s, height 0s;
18 18
 }
19
-.editor textarea.ant-input {
19
+.comment-editor textarea.ant-input {
20 20
   border: none;
21 21
   border-bottom: 1px solid #eee;
22 22
   border-bottom-right-radius: 0;
23 23
   border-bottom-left-radius: 0;
24 24
 }
25
-.editor textarea.ant-input:hover {
25
+.comment-editor textarea.ant-input:hover {
26 26
   border: none;
27 27
   border-bottom: 1px solid #eee;
28 28
 }
29
-.editor textarea.ant-input:focus {
29
+.comment-editor textarea.ant-input:focus {
30 30
   box-shadow: none;
31 31
   border-bottom: 1px solid #eee;
32 32
 }
33
-.editor [contentEditable="true"]:empty:not(:focus):before {
33
+.comment-editor [contentEditable="true"]:empty:not(:focus):before {
34 34
   content: attr(data-text);
35 35
   color: #bfbfbf;
36 36
 }
37
-.editor:focus,
38
-.editor:hover {
37
+.comment-editor:focus,
38
+.comment-editor:hover {
39 39
   border-color: #40a9ff;
40 40
   outline: 0;
41 41
   box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
42 42
 }
43
-.content {
44
-  display: block;
45
-  box-sizing: border-box;
46
-  padding: 4px 10px;
47
-  width: 100%;
48
-  max-width: 100%;
49
-  height: 100%;
50
-  max-height: 110px;
51
-  overflow-y: auto;
52
-  overflow-x: auto;
53
-}
54
-.content:focus,
55
-.content:hover {
56
-  outline: 0;
57
-}
58
-.toolbar {
43
+.comment-toolbar {
59 44
   display: inline-block;
60 45
   width: 100%;
61 46
   margin: 5px 0 0 0;
62 47
 }
63
-.toolbar .icon {
48
+.comment-toolbar .comment-toolbar-icon {
64 49
   font-size: 23px;
65 50
   cursor: pointer;
66 51
   transition: color 0.3s;
67 52
 }
68
-.toolbar .icon:hover {
53
+.comment-toolbar .comment-toolbar-icon:hover {
69 54
   color: #40a9ff;
70 55
 }
71
-.feed .ant-popover-inner-content {
56
+.comment-toolbar-left {
57
+  float: left;
58
+  margin: 8px 11px;
59
+}
60
+.comment-toolbar-right {
61
+  float: right;
62
+  margin: 5px 11px;
63
+}
64
+
65
+.comment-emoji-popover .ant-popover-inner-content {
72 66
   padding: 12px 16px 20px 16px;
73 67
 }
74
-.feed .ant-carousel .slick-dots {
68
+.comment-emoji-popover .ant-carousel .slick-dots {
75 69
   bottom: -10px;
76 70
 }
77
-.feed .ant-carousel .slick-dots li.slick-active button {
71
+.comment-emoji-popover .ant-carousel .slick-dots li.slick-active button {
78 72
   background-color: #7b868a;
79 73
 }
80
-.feed .ant-carousel .slick-dots li button {
74
+.comment-emoji-popover .ant-carousel .slick-dots li button {
81 75
   background-color: #a2aeb5;
82 76
 }

+ 139
- 47
src/components/Editor/index.js View File

@@ -1,41 +1,71 @@
1 1
 import React from "react";
2 2
 import PropTypes from "prop-types";
3 3
 import { Icon, Button, Popover, Input } from "antd";
4
+import { OSS_LINK } from "../../constant";
4 5
 import { MAX_UPLOAD_NUMBER } from "../../constant";
5 6
 import Upload from "./Upload";
6 7
 import Emoji from "./Emoji";
7 8
 import "./index.css";
8 9
 
9 10
 const { TextArea } = Input;
10
-// 设置 Editor 组件的默认值
11
-// 不能在 Editor.defaultProps 中设置
12
-// 因为 Editor 在 ComponentInput 中调用
13
-// 在 ComponentInput 中,需要使用 Editor 的 props 覆盖 ComponentInput 传入的 props
14
-const EditorDefaultProps = {
15
-  rows: 5,
16
-  placeholder: "说点什么吧...",
17
-  showEmoji: true,
18
-  showUpload: true,
19
-  submitText: "发表",
20
-  onChange: () => {}
21
-};
22 11
 
23 12
 class Editor extends React.Component {
24 13
   constructor(props) {
25 14
     super(props);
26 15
     this.state = {
27
-      showUpload: false
16
+      showUpload: false,
17
+      value: "", // 编辑器里面的值
18
+
19
+      fileList: [], // 图片列表
20
+      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
28 21
     };
22
+    this.handleChange = this.handleChange.bind(this);
29 23
     this.handleClickEmoji = this.handleClickEmoji.bind(this);
24
+    this.handleChangeFileList = this.handleChangeFileList.bind(this);
30 25
     this.handleShowUpload = this.handleShowUpload.bind(this);
26
+    this.handleUpload = this.handleUpload.bind(this);
27
+    this.handleSubmit = this.handleSubmit.bind(this);
31 28
   }
32 29
 
33 30
   componentDidMount() {}
34 31
 
35
-  handleClickEmoji(emojiId) {
36
-    this.props.onChangeEmoji(emojiId);
32
+  /**
33
+   * 编辑器的值改变事件
34
+   * 将最新的值存储到 state 中
35
+   * @param {string} value 输入的值
36
+   */
37
+  handleChange(value) {
38
+    this.setState({ value });
39
+    if (this.props.onChange) {
40
+      this.props.onChange(value);
41
+    }
42
+  }
43
+
44
+  /**
45
+   * 点击 emoji 的事件
46
+   * 点击后,需要将改 emoji 插入到编辑器中
47
+   * 插入的值为 [emoji chinese name]
48
+   * 参数 emoji 即为 emoji chinese name
49
+   * @param {string}} emoji emoji 的中文,如 微笑
50
+   */
51
+  handleClickEmoji(emoji) {
52
+    let { value } = this.state;
53
+    value += `[${emoji}]`;
54
+    this.handleChange(value);
37 55
   }
38 56
 
57
+  /**
58
+   * 监听文件列表改变事件
59
+   * @param {Array} fileList 文件列表
60
+   */
61
+  handleChangeFileList(fileList) {
62
+    this.setState({ fileList });
63
+  }
64
+
65
+  /**
66
+   * 控制上传 Popover 的显示和隐藏
67
+   * @param {boolean} showUpload 是否显示上传的 Popover
68
+   */
39 69
   handleShowUpload(showUpload) {
40 70
     if (typeof showUpload === "boolean") {
41 71
       this.setState({ showUpload: showUpload });
@@ -44,34 +74,59 @@ class Editor extends React.Component {
44 74
     }
45 75
   }
46 76
 
77
+  /**
78
+   * 上传文件 TODO:
79
+   * @param {object} param 文件对象
80
+   */
81
+  handleUpload({ uid, path }) {
82
+    const { fileMap } = this.state;
83
+    fileMap[uid] = path;
84
+    this.setState({ fileMap });
85
+  }
86
+
87
+  /**
88
+   * 提交编辑器内容
89
+   * 提交功能,交给父组件来实现
90
+   * 需要父组件传入 onSubmit
91
+   */
92
+  handleSubmit() {
93
+    let { value, fileMap, fileList } = this.state;
94
+    if (fileList.length) {
95
+      value += "<br/>";
96
+      fileList.forEach(item => {
97
+        value += `[${OSS_LINK}${fileMap[item.uid]}]`;
98
+      });
99
+    }
100
+    this.props.onSubmit(value);
101
+  }
102
+
47 103
   render() {
48
-    const props = { ...EditorDefaultProps, ...this.props };
49 104
     const {
50 105
       value,
51
-      onChange,
52
-      onSubmit,
53
-      loading,
54 106
       placeholder,
55
-      fileList,
56
-      onChangeFileList,
57 107
       rows,
58
-      onUpload,
59 108
       showEmoji,
60 109
       showUpload,
61
-      submitText
62
-    } = props;
110
+      btnSubmitText,
111
+      btnLoading,
112
+      btnDisabled,
113
+      button,
114
+      emojiToolIcon,
115
+      imageToolIcon
116
+    } = this.props;
63 117
 
118
+    const handleSubmit = this.handleSubmit;
64 119
     return (
65
-      <div className="editor">
120
+      <div className="comment-editor">
66 121
         <TextArea
67
-          value={value}
68
-          onChange={e => onChange(e.target.value)}
122
+          value={value || this.state.value}
123
+          onChange={e => this.handleChange(e.target.value)}
69 124
           rows={rows}
70 125
           placeholder={placeholder}
71 126
         />
72 127
 
73
-        <div className="toolbar">
74
-          <div style={{ float: "left", margin: "8px 11px" }}>
128
+        <div className="comment-toolbar">
129
+          <div className="comment-toolbar-left">
75 130
             {showEmoji && (
76 131
               <Popover
77 132
                 trigger="click"
@@ -82,9 +137,11 @@ class Editor extends React.Component {
82 137
                     <Emoji onClick={this.handleClickEmoji} />
83 138
                   </div>
84 139
                 }
85
-                overlayClassName="feed"
140
+                overlayClassName="comment-emoji-popover"
86 141
               >
87
-                <Icon type="smile-o" className="icon" />
142
+                {emojiToolIcon || (
143
+                  <Icon type="smile-o" className="comment-toolbar-icon" />
144
+                )}
88 145
               </Popover>
89 146
             )}
90 147
 
@@ -97,9 +154,9 @@ class Editor extends React.Component {
97 154
                     style={{ width: 112 * MAX_UPLOAD_NUMBER, minHeight: 100 }}
98 155
                   >
99 156
                     <Upload
100
-                      onChangeFileList={onChangeFileList}
101
-                      onUpload={onUpload}
102
-                      fileList={fileList}
157
+                      onChangeFileList={this.handleChangeFileList}
158
+                      onUpload={this.handleUpload}
159
+                      fileList={this.state.fileList}
103 160
                     />
104 161
                   </div>
105 162
                 }
@@ -109,7 +166,8 @@ class Editor extends React.Component {
109 166
                     <span>
110 167
                       上传图片
111 168
                       <span style={{ color: "#666", fontWeight: 400 }}>
112
-                        (您还能上传{MAX_UPLOAD_NUMBER - fileList.length}张图片)
169
+                        (您还能上传{MAX_UPLOAD_NUMBER -
170
+                          this.state.fileList.length}张图片)
113 171
                       </span>
114 172
                     </span>
115 173
                     <Icon
@@ -124,20 +182,37 @@ class Editor extends React.Component {
124 182
                   </div>
125 183
                 }
126 184
               >
127
-                <Icon
128
-                  type="picture"
129
-                  className="icon"
130
-                  style={{ marginLeft: 10 }}
131
-                  onClick={() => this.handleShowUpload(true)}
132
-                />
185
+                {imageToolIcon ? (
186
+                  React.cloneElement(imageToolIcon, {
187
+                    onClick: () => this.handleShowUpload(true)
188
+                  })
189
+                ) : (
190
+                  <Icon
191
+                    type="picture"
192
+                    className="comment-toolbar-icon"
193
+                    style={{ marginLeft: 10 }}
194
+                    onClick={() => this.handleShowUpload(true)}
195
+                  />
196
+                )}
133 197
               </Popover>
134 198
             )}
135 199
           </div>
136 200
 
137
-          <div style={{ float: "right", margin: "5px 11px" }}>
138
-            <Button onClick={onSubmit} type="primary" loading={loading}>
139
-              {submitText}
140
-            </Button>
201
+          <div className="comment-toolbar-right">
202
+            {button ? (
203
+              React.cloneElement(button, {
204
+                onClick: button.props.onClick || handleSubmit
205
+              })
206
+            ) : (
207
+              <Button
208
+                onClick={() => this.handleSubmit()}
209
+                type="primary"
210
+                loading={btnLoading}
211
+                disabled={btnDisabled}
212
+              >
213
+                {btnSubmitText}
214
+              </Button>
215
+            )}
141 216
           </div>
142 217
         </div>
143 218
       </div>
@@ -150,8 +225,25 @@ Editor.propTypes = {
150 225
   placeholder: PropTypes.string,
151 226
   showEmoji: PropTypes.bool,
152 227
   showUpload: PropTypes.bool,
153
-  submitText: PropTypes.string,
154
-  onChange: PropTypes.func
228
+  value: PropTypes.string,
229
+  onChange: PropTypes.func,
230
+  onSubmit: PropTypes.func,
231
+  btnSubmitText: PropTypes.string,
232
+  btnLoading: PropTypes.bool,
233
+  btnDisabled: PropTypes.bool,
234
+  button: PropTypes.node,
235
+  emojiToolIcon: PropTypes.node,
236
+  imageToolIcon: PropTypes.node
237
+};
238
+
239
+Editor.defaultProps = {
240
+  rows: 5,
241
+  placeholder: "说点什么吧...",
242
+  showEmoji: true,
243
+  showUpload: true,
244
+  btnSubmitText: "发表",
245
+  btnLoading: false,
246
+  btnDisabled: false
155 247
 };
156 248
 
157 249
 export default Editor;

+ 2
- 0
src/constant.js View File

@@ -11,3 +11,5 @@ export const OSS_LINK = "http://links-comment.oss-cn-beijing.aliyuncs.com";
11 11
 export const MAX_UPLOAD_NUMBER = 4;
12 12
 
13 13
 export const REGEXP = /\[.+?\]/g;
14
+
15
+export const AVATAR = "";

+ 1
- 1
src/emoji.js View File

@@ -18,7 +18,7 @@ const emoji = [
18 18
   },
19 19
   {
20 20
     value: "4",
21
-    ttile: "发呆"
21
+    title: "发呆"
22 22
   },
23 23
   {
24 24
     value: "5",

+ 76
- 21
src/index.js View File

@@ -1,26 +1,81 @@
1
-import React from "react";
1
+import React, { Component } from "react";
2 2
 import ReactDOM from "react-dom";
3
+import { Button, Icon } from "antd";
3 4
 import App, { Editor } from "./App";
4 5
 import registerServiceWorker from "./registerServiceWorker";
5 6
 
6
-const props = {
7
-  type: 1,
8
-  businessId: "1",
9
-  API: "http://api.links123.net/comment/v1",
10
-  showList: false
11
-};
12
-
13
-const editorProps = {
14
-  showEmoji: true,
15
-  placeholder: "说点什么吧",
16
-  rows: 5
17
-  // onSubmit={v => console.log()} // TODO...
18
-};
19
-
20
-ReactDOM.render(
21
-  <App {...props}>
22
-    <Editor {...editorProps} />
23
-  </App>,
24
-  document.getElementById("root-comment")
25
-);
7
+class Index extends Component {
8
+  constructor(props) {
9
+    super(props);
10
+    this.state = {
11
+      value: ""
12
+    };
13
+    this.handleChangeValue = this.handleChangeValue.bind(this);
14
+    this.handleChangeSubmit = this.handleChangeSubmit.bind(this);
15
+  }
16
+
17
+  handleChangeValue(value) {
18
+    this.setState({ value });
19
+    console.log("handleChangeValue value: ", value);
20
+  }
21
+
22
+  handleChangeSubmit(value) {
23
+    this.setState({ loading: true }, () => {
24
+      setTimeout(() => {
25
+        this.setState({ loading: false });
26
+      }, 2000);
27
+    });
28
+    console.log("submit value: ", value);
29
+  }
30
+
31
+  render() {
32
+    // 最简单的用法
33
+    return (
34
+      <App type={1} businessId="test">
35
+        <Editor />
36
+      </App>
37
+    );
38
+
39
+    // 复杂的用户法
40
+    // const props = {
41
+    //   type: 1,
42
+    //   businessId: "1",
43
+    //   API: "http://api.links123.net/comment/v1",
44
+    //   showList: true
45
+    // };
46
+
47
+    // const editorProps = {
48
+    //   showEmoji: true,
49
+    //   placeholder: "说点什么吧",
50
+    //   rows: 5,
51
+    //   btnLoading: this.state.loading,
52
+    //   btnDisable: this.state.loading,
53
+    //   btnSubmitText: "提交",
54
+    //   value: this.state.value,
55
+    //   onChange: v => this.handleChangeValue(v),
56
+    //   onSubmit: v => this.handleChangeSubmit(v),
57
+    //   button: (
58
+    //     <Button
59
+    //       type="primary"
60
+    //       ghost
61
+    //       // onClick={() => console.log('click btn: ', this.state.value)}
62
+    //     >
63
+    //       自定义按钮
64
+    //     </Button>
65
+    //   ),
66
+    //   emojiToolIcon: <Icon type="smile" style={{ fontSize: 23 }} />,
67
+    //   imageToolIcon: (
68
+    //     <Icon type="cloud-upload-o" style={{ fontSize: 25, marginLeft: 10 }} />
69
+    //   )
70
+    // };
71
+
72
+    // return (
73
+    //   <App {...props}>
74
+    //     <Editor {...editorProps} />
75
+    //   </App>
76
+    // );
77
+  }
78
+}
79
+
80
+ReactDOM.render(<Index />, document.getElementById("root-comment"));
26 81
 registerServiceWorker();

+ 1
- 0
src/lang/index.js View File

@@ -1,6 +1,7 @@
1 1
 // 语言包
2 2
 // 英文短语和中文提示的对应
3 3
 const data = {
4
+  "not found": "没有数据",
4 5
   "auth failed": "请先登录",
5 6
   "create comment failed": "创建评论失败",
6 7
   "comment favor failed": "评论点赞失败",

+ 0
- 72
src/mock.js View File

@@ -1,72 +0,0 @@
1
-export const comments = {
2
-  list: [
3
-    {
4
-      id: "5b3a4f0973df3e6a32b0a9d2",
5
-      user_id: 0,
6
-      user_name: "",
7
-      user_avatar: "",
8
-      content: "test[微笑]",
9
-      replies: [
10
-        {
11
-          id: "5b3c58c473df3e64d8d53afb",
12
-          user_id: 0,
13
-          user_name: "",
14
-          user_avatar: "",
15
-          reply: null,
16
-          content: "test",
17
-          favor_count: 1,
18
-          favored: false,
19
-          created: 1530681540
20
-        },
21
-        {
22
-          id: "5b3c58c473df3e64d8d53afa",
23
-          user_id: 0,
24
-          user_name: "",
25
-          user_avatar: "",
26
-          reply: {
27
-            id: "5b3c58c473df3e64d8d53afb",
28
-            user_id: 0,
29
-            user_name: "",
30
-            user_avatar: "",
31
-            reply: null,
32
-            content: "test",
33
-            favor_count: 1,
34
-            favored: false,
35
-            created: 1530681540
36
-          },
37
-          content: "test",
38
-          favor_count: 2,
39
-          favored: false,
40
-          created: 1530681540
41
-        },
42
-        {
43
-          id: "5b3c6e4873df3e53f870d912",
44
-          user_id: 0,
45
-          user_name: "",
46
-          user_avatar: "",
47
-          reply: {
48
-            id: "5b3c58c473df3e64d8d53afb",
49
-            user_id: 0,
50
-            user_name: "",
51
-            user_avatar: "",
52
-            reply: null,
53
-            content: "test",
54
-            favor_count: 1,
55
-            favored: false,
56
-            created: 1530681540
57
-          },
58
-          content: "test",
59
-          favor_count: 0,
60
-          favored: false,
61
-          created: 1530687048
62
-        }
63
-      ],
64
-      reply_count: 4,
65
-      favor_count: 0,
66
-      favored: false,
67
-      created: 1530547977
68
-    }
69
-  ],
70
-  total: 1,
71
-  page: 1
72
-};