Browse Source

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

node 6 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

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
 # Comment
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
 
134
 
24
 render() {
135
 render() {
25
   return (
136
   return (
26
-    <Comment>
137
+    <Comment type={1} businessId="test">
27
       <Editor />
138
       <Editor />
28
     </Comment>
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
 ```js
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
 </App>
155
 </App>
60
 ```
156
 ```
61
 
157
 
65
 $ yarn build
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
 最后创建一个 id 为 `root-comment` 的标签,作为渲染通用评论的跟元素。
170
 最后创建一个 id 为 `root-comment` 的标签,作为渲染通用评论的跟元素。
93
 </html>
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
 ```
199
 ```
131
 
200
 
132
 - `yarn build` 将项目打包成一个单页应用
201
 - `yarn build` 将项目打包成一个单页应用
133
-- `yarn lib` 将项目打包成一个组件
202
+- `yarn lib` 将项目打包成一个 es5 组件
134
 - `yarn prettier` 格式化代码
203
 - `yarn prettier` 格式化代码
135
 
204
 
136
 ## TODO
205
 ## TODO
137
 
206
 
138
-- [ ] 前后端统一错误码
207
+- [x] 前后端统一错误码
139
 - [x] type 和 businessID 的定义
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
   position: fixed;
7
   position: fixed;
8
   top: 45%;
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
 });
5
 });
6
 exports.Editor = undefined;
6
 exports.Editor = undefined;
7
 
7
 
8
+var _tag = require("antd/es/tag");
9
+
10
+var _tag2 = _interopRequireDefault(_tag);
11
+
8
 var _message2 = require("antd/es/message");
12
 var _message2 = require("antd/es/message");
9
 
13
 
10
 var _message3 = _interopRequireDefault(_message2);
14
 var _message3 = _interopRequireDefault(_message2);
13
 
17
 
14
 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; }; }();
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
 require("antd/es/message/style/css");
22
 require("antd/es/message/style/css");
17
 
23
 
18
 var _react = require("react");
24
 var _react = require("react");
45
 
51
 
46
 var _Editor2 = _interopRequireDefault(_Editor);
52
 var _Editor2 = _interopRequireDefault(_Editor);
47
 
53
 
54
+var _lang = require("./lang");
55
+
56
+var _lang2 = _interopRequireDefault(_lang);
57
+
48
 require("./App.css");
58
 require("./App.css");
49
 
59
 
50
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
60
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
82
     _this.sCreateComment = _this.sCreateComment.bind(_this);
92
     _this.sCreateComment = _this.sCreateComment.bind(_this);
83
     _this.sCreateReply = _this.sCreateReply.bind(_this);
93
     _this.sCreateReply = _this.sCreateReply.bind(_this);
84
     _this.sCommentFavor = _this.sCommentFavor.bind(_this);
94
     _this.sCommentFavor = _this.sCommentFavor.bind(_this);
95
+    _this.sReplyFavor = _this.sReplyFavor.bind(_this);
85
     _this.sOssSts = _this.sOssSts.bind(_this);
96
     _this.sOssSts = _this.sOssSts.bind(_this);
86
     return _this;
97
     return _this;
87
   }
98
   }
154
         }
165
         }
155
       }).catch(function (error) {
166
       }).catch(function (error) {
156
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
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
       }).finally(function () {
172
       }).finally(function () {
162
         _this2.handleChangeLoading("sGetComment", false);
173
         _this2.handleChangeLoading("sGetComment", false);
163
       });
174
       });
181
       var API = this.props.API;
192
       var API = this.props.API;
182
 
193
 
183
       _axios2.default.get(API + "/replies?comment_id=" + commentId + "&page=" + page + "&limit=" + _constant.LIMIT).then(function (response) {
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
           _message3.default.info("没有更多数据了!");
196
           _message3.default.info("没有更多数据了!");
188
         }
197
         }
189
         var list = _this3.state.list.map(function (item) {
198
         var list = _this3.state.list.map(function (item) {
190
           if (item.id === commentId) {
199
           if (item.id === commentId) {
191
             if (!item.replies) item.replies = [];
200
             if (!item.replies) item.replies = [];
192
-            if (replies) {
201
+            if (response.data.list) {
193
               if (page === 1) {
202
               if (page === 1) {
194
                 // 如果当前页数为第一页,则清空当前所有的 replies
203
                 // 如果当前页数为第一页,则清空当前所有的 replies
195
                 // 并将获取到的 replies 存放在 state
204
                 // 并将获取到的 replies 存放在 state
196
-                item.replies = replies;
205
+                item.replies = response.data.list;
197
               } else {
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
                 // 如果当前页数非第一页,则合并 replies
210
                 // 如果当前页数非第一页,则合并 replies
200
               }
211
               }
212
+              item.reply_count = response.data.total;
213
+              item.reply_page = response.data.page;
201
             } else {
214
             } else {
202
               item.isNoMoreReply = true;
215
               item.isNoMoreReply = true;
203
             }
216
             }
207
         _this3.setState({ list: list });
220
         _this3.setState({ list: list });
208
       }).catch(function (error) {
221
       }).catch(function (error) {
209
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
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
       }).finally(function () {
227
       }).finally(function () {
215
         _this3.handleChangeLoading("sGetReply", false);
228
         _this3.handleChangeLoading("sGetReply", false);
216
       });
229
       });
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
     key: "sCreateComment",
238
     key: "sCreateComment",
226
-    value: function sCreateComment(content) {
239
+    value: function sCreateComment() {
227
       var _this4 = this;
240
       var _this4 = this;
228
 
241
 
242
+      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
243
+          content = _ref3.content;
244
+
229
       if (!content) return _message3.default.error("评论内容不能为空 ");
245
       if (!content) return _message3.default.error("评论内容不能为空 ");
230
       this.handleChangeLoading("sCreateComment", true);
246
       this.handleChangeLoading("sCreateComment", true);
231
       var _props2 = this.props,
247
       var _props2 = this.props,
246
         // 将数据写入到 list 中
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
         list.unshift(_extends({}, response.data, {
269
         list.unshift(_extends({}, response.data, {
252
           isTemporary: true // 临时的数据
270
           isTemporary: true // 临时的数据
253
         }));
271
         }));
254
-        _this4.setState({ list: list });
272
+        _this4.setState({ list: list, total: total + 1 });
255
       }).catch(function (error) {
273
       }).catch(function (error) {
256
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
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
       }).finally(function () {
279
       }).finally(function () {
262
         _this4.handleChangeLoading("sCreateComment", false);
280
         _this4.handleChangeLoading("sCreateComment", false);
263
       });
281
       });
274
     value: function sCreateReply(data, cb) {
292
     value: function sCreateReply(data, cb) {
275
       var _this5 = this;
293
       var _this5 = this;
276
 
294
 
295
+      console.log("list: ", this.state.list);
296
+
277
       if (!data.content) return _message3.default.error("回复内容不能为空 ");
297
       if (!data.content) return _message3.default.error("回复内容不能为空 ");
278
       this.handleChangeLoading("sCreateReply", true);
298
       this.handleChangeLoading("sCreateReply", true);
279
       var API = this.props.API;
299
       var API = this.props.API;
283
         data: data,
303
         data: data,
284
         withCredentials: true
304
         withCredentials: true
285
       }).then(function (response) {
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
         _message3.default.success("回复成功!");
306
         _message3.default.success("回复成功!");
298
         if ((0, _helper.isFunction)(cb)) cb();
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
       }).catch(function (error) {
322
       }).catch(function (error) {
300
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
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
       }).finally(function () {
328
       }).finally(function () {
306
         _this5.handleChangeLoading("sCreateReply", false);
329
         _this5.handleChangeLoading("sCreateReply", false);
307
       });
330
       });
308
     }
331
     }
309
 
332
 
310
     /**
333
     /**
311
-     * 点赞/取消点赞
334
+     * 评论 点赞/取消点赞
312
      * @param {string} commentId { commentId }
335
      * @param {string} commentId { commentId }
313
      * @param {boolean} favored   是否已经点过赞
336
      * @param {boolean} favored   是否已经点过赞
314
      */
337
      */
337
         _this6.setState({ list: list });
360
         _this6.setState({ list: list });
338
       }).catch(function (error) {
361
       }).catch(function (error) {
339
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
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
       }).finally(function () {
367
       }).finally(function () {
345
         _this6.handleChangeLoading("sCommentFavor", false);
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
      * 获取 OSS 上传的参数
418
      * 获取 OSS 上传的参数
351
      */
419
      */
353
   }, {
421
   }, {
354
     key: "sOssSts",
422
     key: "sOssSts",
355
     value: function sOssSts() {
423
     value: function sOssSts() {
356
-      var _this7 = this;
424
+      var _this8 = this;
357
 
425
 
358
       this.handleChangeLoading("sOssSts", true);
426
       this.handleChangeLoading("sOssSts", true);
359
       var API = this.props.API;
427
       var API = this.props.API;
360
 
428
 
361
       _axios2.default.get(API + "/oss/sts").then(function (response) {
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
       }).catch(function (error) {
431
       }).catch(function (error) {
364
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
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
       }).finally(function () {
437
       }).finally(function () {
370
-        _this7.handleChangeLoading("sOssSts", false);
438
+        _this8.handleChangeLoading("sOssSts", false);
371
       });
439
       });
372
     }
440
     }
373
   }, {
441
   }, {
378
         sCreateComment: this.sCreateComment,
446
         sCreateComment: this.sCreateComment,
379
         sGetComment: this.sGetComment,
447
         sGetComment: this.sGetComment,
380
         sCommentFavor: this.sCommentFavor,
448
         sCommentFavor: this.sCommentFavor,
449
+        sReplyFavor: this.sReplyFavor,
381
         sCreateReply: this.sCreateReply,
450
         sCreateReply: this.sCreateReply,
382
         sGetReply: this.sGetReply,
451
         sGetReply: this.sGetReply,
383
         sOssSts: this.sOssSts
452
         sOssSts: this.sOssSts
389
         _react2.default.createElement(
458
         _react2.default.createElement(
390
           "div",
459
           "div",
391
           { className: "comment" },
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
           this.props.showEditor && _react2.default.createElement(_CommentInput2.default, { content: this.props.children }),
480
           this.props.showEditor && _react2.default.createElement(_CommentInput2.default, { content: this.props.children }),
393
           this.props.showList && _react2.default.createElement(
481
           this.props.showList && _react2.default.createElement(
394
             "div",
482
             "div",
408
   businessId: _propTypes2.default.string.isRequired, // 评论的 business_id
496
   businessId: _propTypes2.default.string.isRequired, // 评论的 business_id
409
   API: _propTypes2.default.string, // 评论的 API 前缀
497
   API: _propTypes2.default.string, // 评论的 API 前缀
410
   showList: _propTypes2.default.bool, // 是否显示评论列表
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
 App.defaultProps = {
503
 App.defaultProps = {
415
   API: "http://api.links123.net/comment/v1",
504
   API: "http://api.links123.net/comment/v1",
416
   showList: true,
505
   showList: true,
417
-  showEditor: true
506
+  showEditor: true,
507
+  showHeader: true
418
 };
508
 };
419
 
509
 
420
 exports.Editor = _Editor2.default;
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
-.showMore {
1
+.comment-show-more {
2
   color: #4a90e2;
2
   color: #4a90e2;
3
   text-align: center;
3
   text-align: center;
4
   width: 100px;
4
   width: 100px;
8
   margin: 0 auto;
8
   margin: 0 auto;
9
   transition: all 0.3s;
9
   transition: all 0.3s;
10
 }
10
 }
11
-.showMore:hover {
11
+.comment-show-more:hover {
12
   background-color: #f5f5f5;
12
   background-color: #f5f5f5;
13
   color: #1890ff;
13
   color: #1890ff;
14
 }
14
 }
15
-.moreBox {
15
+.comment-more-box {
16
   text-align: center;
16
   text-align: center;
17
   width: 90%;
17
   width: 90%;
18
-  margin-left: 40px;
18
+  margin-left: 50px;
19
+  margin-top: 10px;
19
   height: 40px;
20
   height: 40px;
20
   display: inline-block;
21
   display: inline-block;
21
 }
22
 }
22
 @media screen and (max-width: 616px) and (min-width: 449px) {
23
 @media screen and (max-width: 616px) and (min-width: 449px) {
23
-  .moreBox {
24
+  .comment-more-box {
24
     text-align: center;
25
     text-align: center;
25
     width: 85%;
26
     width: 85%;
26
-    margin-left: 40px;
27
+    margin-left: 50px;
27
     height: 40px;
28
     height: 40px;
28
     display: inline-block;
29
     display: inline-block;
29
   }
30
   }
30
 }
31
 }
31
 @media screen and (max-width: 449px) and (min-width: 365px) {
32
 @media screen and (max-width: 449px) and (min-width: 365px) {
32
-  .moreBox {
33
+  .comment-more-box {
33
     text-align: center;
34
     text-align: center;
34
     width: 80%;
35
     width: 80%;
35
-    margin-left: 40px;
36
+    margin-left: 50px;
36
     height: 40px;
37
     height: 40px;
37
     display: inline-block;
38
     display: inline-block;
38
   }
39
   }
39
 }
40
 }
40
 @media screen and (max-width: 365px) {
41
 @media screen and (max-width: 365px) {
41
-  .moreBox {
42
+  .comment-more-box {
42
     text-align: center;
43
     text-align: center;
43
     width: 75%;
44
     width: 75%;
44
-    margin-left: 40px;
45
+    margin-left: 50px;
45
     height: 40px;
46
     height: 40px;
46
     display: inline-block;
47
     display: inline-block;
47
   }
48
   }

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

86
     /**
86
     /**
87
      * 渲染回复 DOM
87
      * 渲染回复 DOM
88
      * @param {array} replies 回复列表
88
      * @param {array} replies 回复列表
89
+     * @param {number} replies 回复的数量
89
      * @param {boolean} isNoMoreReply 是否没有更多回复
90
      * @param {boolean} isNoMoreReply 是否没有更多回复
90
      */
91
      */
91
 
92
 
92
   }, {
93
   }, {
93
     key: "renderReplies",
94
     key: "renderReplies",
94
-    value: function renderReplies(replies, isNoMoreReply) {
95
+    value: function renderReplies(replies, replyCount, isNoMoreReply) {
95
       var _this2 = this;
96
       var _this2 = this;
96
 
97
 
97
       var commentId = this.props.commentId;
98
       var commentId = this.props.commentId;
109
                 replyId: item.id,
110
                 replyId: item.id,
110
                 key: item.id,
111
                 key: item.id,
111
                 content: item,
112
                 content: item,
112
-                type: "reply"
113
+                action: "replyToReply" // 回复的回复
113
               }), _react2.default.createElement(
114
               }), _react2.default.createElement(
114
                 "div",
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
                   "span",
118
                   "span",
118
                   {
119
                   {
119
-                    className: "showMore",
120
+                    className: "comment-show-more",
120
                     onClick: function onClick() {
121
                     onClick: function onClick() {
121
                       return _this2.handleGetMoreReply(commentId);
122
                       return _this2.handleGetMoreReply(commentId);
122
                     }
123
                     }
139
               replyId: item.id,
140
               replyId: item.id,
140
               key: item.id,
141
               key: item.id,
141
               content: item,
142
               content: item,
142
-              type: "reply"
143
+              action: "replyToReply" // 评论的回复
143
             });
144
             });
144
           })
145
           })
145
         );
146
         );
160
           onShowReply: this.handleToggleReply,
161
           onShowReply: this.handleToggleReply,
161
           showReply: showReply,
162
           showReply: showReply,
162
           commentId: content.id,
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
 
16
 
17
 var _propTypes2 = _interopRequireDefault(_propTypes);
17
 var _propTypes2 = _interopRequireDefault(_propTypes);
18
 
18
 
19
-var _constant = require("../../constant");
20
-
21
 var _Comment = require("../../Comment");
19
 var _Comment = require("../../Comment");
22
 
20
 
23
 var _Comment2 = _interopRequireDefault(_Comment);
21
 var _Comment2 = _interopRequireDefault(_Comment);
38
 
36
 
39
     var _this = _possibleConstructorReturn(this, (CommentInput.__proto__ || Object.getPrototypeOf(CommentInput)).call(this, props));
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
     _this.handleSubmit = _this.handleSubmit.bind(_this);
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
     return _this;
41
     return _this;
53
   }
42
   }
54
 
43
 
55
   _createClass(CommentInput, [{
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
     key: "handleSubmit",
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
       var _props = this.props,
47
       var _props = this.props,
105
-          type = _props.type,
48
+          action = _props.action,
106
           commentId = _props.commentId,
49
           commentId = _props.commentId,
107
           replyId = _props.replyId,
50
           replyId = _props.replyId,
108
-          handleToggleInput = _props.handleToggleInput;
51
+          callback = _props.callback;
109
 
52
 
110
-      if (type === "normal") {
53
+      if (action === "comment") {
111
         this.props.app.sCreateComment({
54
         this.props.app.sCreateComment({
112
           content: value
55
           content: value
113
         });
56
         });
114
-      } else if (type === "comment") {
57
+      } else if (action === "reply") {
115
         this.props.app.sCreateReply({
58
         this.props.app.sCreateReply({
116
           comment_id: commentId,
59
           comment_id: commentId,
117
           content: value
60
           content: value
118
         }, function () {
61
         }, function () {
119
-          return handleToggleInput();
62
+          return callback && callback();
120
         });
63
         });
121
-      } else if (type === "reply") {
64
+      } else if (action === "replyToReply") {
122
         this.props.app.sCreateReply({
65
         this.props.app.sCreateReply({
123
           comment_id: commentId,
66
           comment_id: commentId,
124
           content: value,
67
           content: value,
125
           reply_id: replyId
68
           reply_id: replyId
126
         }, function () {
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
     key: "render",
75
     key: "render",
140
     value: function render() {
76
     value: function render() {
141
       var _this2 = this;
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
       var childrenWithProps = _react2.default.Children.map(this.props.content, function (child) {
79
       var childrenWithProps = _react2.default.Children.map(this.props.content, function (child) {
150
         return _react2.default.cloneElement(child, _extends({
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
       return _react2.default.createElement(
92
       return _react2.default.createElement(
163
         "div",
93
         "div",
164
         null,
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
 }(_react.Component);
101
 }(_react.Component);
201
 
102
 
202
 CommentInput.propTypes = {
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
 CommentInput.defaultProps = {
110
 CommentInput.defaultProps = {
208
-  type: "normal"
111
+  action: "comment"
209
 };
112
 };
210
 
113
 
211
 exports.default = (0, _Comment2.default)(CommentInput);
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
-.showMore {
1
+.comment-list-show-more {
2
   color: #4a90e2;
2
   color: #4a90e2;
3
   text-align: center;
3
   text-align: center;
4
   width: 100%;
4
   width: 100%;
8
   margin: 40px auto;
8
   margin: 40px auto;
9
   transition: all 0.3s;
9
   transition: all 0.3s;
10
 }
10
 }
11
-.showMore:hover {
11
+.comment-list-show-more:hover {
12
   background-color: #f5f5f5;
12
   background-color: #f5f5f5;
13
   color: #1890ff;
13
   color: #1890ff;
14
 }
14
 }

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

56
     value: function render() {
56
     value: function render() {
57
       var _props$app = this.props.app,
57
       var _props$app = this.props.app,
58
           list = _props$app.list,
58
           list = _props$app.list,
59
+          total = _props$app.total,
59
           page = _props$app.page,
60
           page = _props$app.page,
60
           loading = _props$app.loading,
61
           loading = _props$app.loading,
61
           isNoMoreComment = _props$app.isNoMoreComment,
62
           isNoMoreComment = _props$app.isNoMoreComment,
62
           sGetComment = _props$app.sGetComment;
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
       return _react2.default.createElement(
67
       return _react2.default.createElement(
67
         "div",
68
         "div",
68
         null,
69
         null,
69
         _react2.default.createElement(
70
         _react2.default.createElement(
70
           _spin2.default,
71
           _spin2.default,
71
           { spinning: spinning },
72
           { spinning: spinning },
73
+          _react2.default.createElement(
74
+            "div",
75
+            null,
76
+            "\u5171 ",
77
+            total,
78
+            " \u6761\u8BC4\u8BBA"
79
+          ),
72
           list.map(function (item) {
80
           list.map(function (item) {
73
             return _react2.default.createElement(_CommentBox2.default, { content: item, key: item.id, commentId: item.id });
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
             "div",
84
             "div",
77
             {
85
             {
78
-              className: "showMore",
86
+              className: "comment-list-show-more",
79
               onClick: function onClick() {
87
               onClick: function onClick() {
80
                 return sGetComment({ page: page + 1 });
88
                 return sGetComment({ page: page + 1 });
81
               }
89
               }

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

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

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

8
 
8
 
9
 var _icon2 = _interopRequireDefault(_icon);
9
 var _icon2 = _interopRequireDefault(_icon);
10
 
10
 
11
+var _tooltip = require("antd/es/tooltip");
12
+
13
+var _tooltip2 = _interopRequireDefault(_tooltip);
14
+
11
 var _avatar = require("antd/es/avatar");
15
 var _avatar = require("antd/es/avatar");
12
 
16
 
13
 var _avatar2 = _interopRequireDefault(_avatar);
17
 var _avatar2 = _interopRequireDefault(_avatar);
16
 
20
 
17
 require("antd/es/icon/style/css");
21
 require("antd/es/icon/style/css");
18
 
22
 
23
+require("antd/es/tooltip/style/css");
24
+
19
 require("antd/es/avatar/style/css");
25
 require("antd/es/avatar/style/css");
20
 
26
 
21
 var _react = require("react");
27
 var _react = require("react");
30
 
36
 
31
 var _dayjs2 = _interopRequireDefault(_dayjs);
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
 var _Comment = require("../../Comment");
45
 var _Comment = require("../../Comment");
34
 
46
 
35
 var _Comment2 = _interopRequireDefault(_Comment);
47
 var _Comment2 = _interopRequireDefault(_Comment);
38
 
50
 
39
 var _CommentInput2 = _interopRequireDefault(_CommentInput);
51
 var _CommentInput2 = _interopRequireDefault(_CommentInput);
40
 
52
 
53
+var _avatar3 = require("../../avatar");
54
+
55
+var _avatar4 = _interopRequireDefault(_avatar3);
56
+
41
 var _helper = require("../../helper");
57
 var _helper = require("../../helper");
42
 
58
 
43
 require("./index.css");
59
 require("./index.css");
50
 
66
 
51
 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; }
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
 var CommentItem = function (_Component) {
72
 var CommentItem = function (_Component) {
54
   _inherits(CommentItem, _Component);
73
   _inherits(CommentItem, _Component);
55
 
74
 
59
     var _this = _possibleConstructorReturn(this, (CommentItem.__proto__ || Object.getPrototypeOf(CommentItem)).call(this, props));
78
     var _this = _possibleConstructorReturn(this, (CommentItem.__proto__ || Object.getPrototypeOf(CommentItem)).call(this, props));
60
 
79
 
61
     _this.state = {
80
     _this.state = {
62
-      isShowInput: false
81
+      showInput: false
63
     };
82
     };
64
     _this.handleToggleInput = _this.handleToggleInput.bind(_this);
83
     _this.handleToggleInput = _this.handleToggleInput.bind(_this);
65
     _this.renderTextWithReply = _this.renderTextWithReply.bind(_this);
84
     _this.renderTextWithReply = _this.renderTextWithReply.bind(_this);
69
   _createClass(CommentItem, [{
88
   _createClass(CommentItem, [{
70
     key: "handleToggleInput",
89
     key: "handleToggleInput",
71
     value: function handleToggleInput() {
90
     value: function handleToggleInput() {
72
-      this.setState({ isShowInput: !this.state.isShowInput });
91
+      this.setState({ showInput: !this.state.showInput });
73
     }
92
     }
74
   }, {
93
   }, {
75
     key: "renderTextWithReply",
94
     key: "renderTextWithReply",
92
           commentId = _props.commentId,
111
           commentId = _props.commentId,
93
           replyId = _props.replyId,
112
           replyId = _props.replyId,
94
           content = _props.content,
113
           content = _props.content,
95
-          type = _props.type,
114
+          action = _props.action,
96
           showReply = _props.showReply,
115
           showReply = _props.showReply,
97
           onShowReply = _props.onShowReply,
116
           onShowReply = _props.onShowReply,
98
           app = _props.app;
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
       return _react2.default.createElement(
121
       return _react2.default.createElement(
103
         "div",
122
         "div",
104
-        { className: "box" },
123
+        { className: "comment-item-box" },
105
         _react2.default.createElement(
124
         _react2.default.createElement(
106
           "div",
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
         _react2.default.createElement(
129
         _react2.default.createElement(
111
           "div",
130
           "div",
112
-          { className: "right" },
131
+          { className: "comment-item-right" },
113
           _react2.default.createElement(
132
           _react2.default.createElement(
114
             "div",
133
             "div",
115
-            { className: "name" },
134
+            null,
116
             _react2.default.createElement(
135
             _react2.default.createElement(
117
               "a",
136
               "a",
118
               { href: "/" + content.user_id },
137
               { href: "/" + content.user_id },
121
             _react2.default.createElement(
140
             _react2.default.createElement(
122
               "span",
141
               "span",
123
               { style: { marginLeft: 10 } },
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
           _react2.default.createElement("div", {
153
           _react2.default.createElement("div", {
128
-            className: "content",
154
+            className: "comment-item-content",
129
             dangerouslySetInnerHTML: {
155
             dangerouslySetInnerHTML: {
130
               __html: (0, _helper.renderContent)(this.renderTextWithReply(content.content, content))
156
               __html: (0, _helper.renderContent)(this.renderTextWithReply(content.content, content))
131
             }
157
             }
132
           }),
158
           }),
133
           _react2.default.createElement(
159
           _react2.default.createElement(
134
             "div",
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
               "div",
163
               "div",
138
               null,
164
               null,
139
               _react2.default.createElement(
165
               _react2.default.createElement(
140
                 "a",
166
                 "a",
141
-                {
142
-                  className: "itemLeft",
143
-                  onClick: onShowReply,
144
-                  style: { userSelect: "none" }
145
-                },
167
+                { className: "comment-item-bottom-left", onClick: onShowReply },
146
                 content.reply_count,
168
                 content.reply_count,
147
                 " \u6761\u56DE\u590D",
169
                 " \u6761\u56DE\u590D",
148
                 showReply ? _react2.default.createElement(_icon2.default, { type: "up" }) : _react2.default.createElement(_icon2.default, { type: "down" })
170
                 showReply ? _react2.default.createElement(_icon2.default, { type: "up" }) : _react2.default.createElement(_icon2.default, { type: "down" })
150
             ) : null,
172
             ) : null,
151
             _react2.default.createElement(
173
             _react2.default.createElement(
152
               "a",
174
               "a",
153
-              { onClick: this.handleToggleInput, className: "itemRight" },
175
+              {
176
+                onClick: this.handleToggleInput,
177
+                className: "comment-item-bottom-right"
178
+              },
154
               "\xA0 \u56DE\u590D"
179
               "\xA0 \u56DE\u590D"
155
             ),
180
             ),
156
             _react2.default.createElement(
181
             _react2.default.createElement(
157
               "div",
182
               "div",
158
               {
183
               {
159
-                className: "itemRight",
160
-                style: { cursor: "pointer" },
184
+                className: "comment-item-bottom-right",
161
                 onClick: function onClick() {
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
               _react2.default.createElement(_icon2.default, {
194
               _react2.default.createElement(_icon2.default, {
166
                 type: "like-o",
195
                 type: "like-o",
167
-                className: content.favored ? "favored" : ""
196
+                className: content.favored ? "comment-favored" : ""
168
               }),
197
               }),
169
               "\xA0",
198
               "\xA0",
170
               content.favor_count
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
 CommentItem.propTypes = {
217
 CommentItem.propTypes = {
188
   content: _propTypes2.default.object.isRequired,
218
   content: _propTypes2.default.object.isRequired,
189
   // comment 评论
219
   // comment 评论
190
-  // reply 回复
191
-  type: _propTypes2.default.oneOf(["comment", "reply"]),
220
+  // reply 评论的回复
221
+  // replyToReply 回复的回复
222
+  action: _propTypes2.default.oneOf(["comment", "reply", "replyToReply"]),
192
   onShowReply: _propTypes2.default.func
223
   onShowReply: _propTypes2.default.func
193
 };
224
 };
194
 
225
 
195
 CommentItem.defaultProps = {
226
 CommentItem.defaultProps = {
196
-  type: "comment",
227
+  action: "comment",
197
   onShowReply: function onShowReply() {}
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
 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; }
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
 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; }
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
 var client = function client(oss) {
61
 var client = function client(oss) {
64
   return new window.OSS.Wrapper({
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
-.editor {
1
+.comment-editor {
2
   box-sizing: border-box;
2
   box-sizing: border-box;
3
   margin: 0;
3
   margin: 0;
4
   padding: 0;
4
   padding: 0;
16
   border-radius: 4px;
16
   border-radius: 4px;
17
   transition: all 0.3s, height 0s;
17
   transition: all 0.3s, height 0s;
18
 }
18
 }
19
-.editor textarea.ant-input {
19
+.comment-editor textarea.ant-input {
20
   border: none;
20
   border: none;
21
   border-bottom: 1px solid #eee;
21
   border-bottom: 1px solid #eee;
22
   border-bottom-right-radius: 0;
22
   border-bottom-right-radius: 0;
23
   border-bottom-left-radius: 0;
23
   border-bottom-left-radius: 0;
24
 }
24
 }
25
-.editor textarea.ant-input:hover {
25
+.comment-editor textarea.ant-input:hover {
26
   border: none;
26
   border: none;
27
   border-bottom: 1px solid #eee;
27
   border-bottom: 1px solid #eee;
28
 }
28
 }
29
-.editor textarea.ant-input:focus {
29
+.comment-editor textarea.ant-input:focus {
30
   box-shadow: none;
30
   box-shadow: none;
31
   border-bottom: 1px solid #eee;
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
   content: attr(data-text);
34
   content: attr(data-text);
35
   color: #bfbfbf;
35
   color: #bfbfbf;
36
 }
36
 }
37
-.editor:focus,
38
-.editor:hover {
37
+.comment-editor:focus,
38
+.comment-editor:hover {
39
   border-color: #40a9ff;
39
   border-color: #40a9ff;
40
   outline: 0;
40
   outline: 0;
41
   box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
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
   display: inline-block;
44
   display: inline-block;
60
   width: 100%;
45
   width: 100%;
61
   margin: 5px 0 0 0;
46
   margin: 5px 0 0 0;
62
 }
47
 }
63
-.toolbar .icon {
48
+.comment-toolbar .comment-toolbar-icon {
64
   font-size: 23px;
49
   font-size: 23px;
65
   cursor: pointer;
50
   cursor: pointer;
66
   transition: color 0.3s;
51
   transition: color 0.3s;
67
 }
52
 }
68
-.toolbar .icon:hover {
53
+.comment-toolbar .comment-toolbar-icon:hover {
69
   color: #40a9ff;
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
   padding: 12px 16px 20px 16px;
66
   padding: 12px 16px 20px 16px;
73
 }
67
 }
74
-.feed .ant-carousel .slick-dots {
68
+.comment-emoji-popover .ant-carousel .slick-dots {
75
   bottom: -10px;
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
   background-color: #7b868a;
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
   background-color: #a2aeb5;
75
   background-color: #a2aeb5;
82
 }
76
 }

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

20
 
20
 
21
 var _input2 = _interopRequireDefault(_input);
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
 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; }; }();
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
 require("antd/es/button/style/css");
25
 require("antd/es/button/style/css");
61
 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; }
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
 var TextArea = _input2.default.TextArea;
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
 var Editor = function (_React$Component) {
63
 var Editor = function (_React$Component) {
79
   _inherits(Editor, _React$Component);
64
   _inherits(Editor, _React$Component);
84
     var _this = _possibleConstructorReturn(this, (Editor.__proto__ || Object.getPrototypeOf(Editor)).call(this, props));
69
     var _this = _possibleConstructorReturn(this, (Editor.__proto__ || Object.getPrototypeOf(Editor)).call(this, props));
85
 
70
 
86
     _this.state = {
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
     _this.handleClickEmoji = _this.handleClickEmoji.bind(_this);
79
     _this.handleClickEmoji = _this.handleClickEmoji.bind(_this);
80
+    _this.handleChangeFileList = _this.handleChangeFileList.bind(_this);
90
     _this.handleShowUpload = _this.handleShowUpload.bind(_this);
81
     _this.handleShowUpload = _this.handleShowUpload.bind(_this);
82
+    _this.handleUpload = _this.handleUpload.bind(_this);
83
+    _this.handleSubmit = _this.handleSubmit.bind(_this);
91
     return _this;
84
     return _this;
92
   }
85
   }
93
 
86
 
94
   _createClass(Editor, [{
87
   _createClass(Editor, [{
95
     key: "componentDidMount",
88
     key: "componentDidMount",
96
     value: function componentDidMount() {}
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
     key: "handleClickEmoji",
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
     key: "handleShowUpload",
140
     key: "handleShowUpload",
104
     value: function handleShowUpload(showUpload) {
141
     value: function handleShowUpload(showUpload) {
108
         this.setState({ showUpload: !this.state.showUpload });
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
     key: "render",
188
     key: "render",
113
     value: function render() {
189
     value: function render() {
114
       var _this2 = this;
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
       return _react2.default.createElement(
207
       return _react2.default.createElement(
132
         "div",
208
         "div",
133
-        { className: "editor" },
209
+        { className: "comment-editor" },
134
         _react2.default.createElement(TextArea, {
210
         _react2.default.createElement(TextArea, {
135
-          value: value,
211
+          value: value || this.state.value,
136
           onChange: function onChange(e) {
212
           onChange: function onChange(e) {
137
-            return _onChange(e.target.value);
213
+            return _this2.handleChange(e.target.value);
138
           },
214
           },
139
           rows: rows,
215
           rows: rows,
140
           placeholder: placeholder
216
           placeholder: placeholder
141
         }),
217
         }),
142
         _react2.default.createElement(
218
         _react2.default.createElement(
143
           "div",
219
           "div",
144
-          { className: "toolbar" },
220
+          { className: "comment-toolbar" },
145
           _react2.default.createElement(
221
           _react2.default.createElement(
146
             "div",
222
             "div",
147
-            { style: { float: "left", margin: "8px 11px" } },
223
+            { className: "comment-toolbar-left" },
148
             showEmoji && _react2.default.createElement(
224
             showEmoji && _react2.default.createElement(
149
               _popover2.default,
225
               _popover2.default,
150
               {
226
               {
156
                   { style: { width: 200 } },
232
                   { style: { width: 200 } },
157
                   _react2.default.createElement(_Emoji2.default, { onClick: this.handleClickEmoji })
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
             showUpload && _react2.default.createElement(
239
             showUpload && _react2.default.createElement(
164
               _popover2.default,
240
               _popover2.default,
171
                     style: { width: 112 * _constant.MAX_UPLOAD_NUMBER, minHeight: 100 }
247
                     style: { width: 112 * _constant.MAX_UPLOAD_NUMBER, minHeight: 100 }
172
                   },
248
                   },
173
                   _react2.default.createElement(_Upload2.default, {
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
                 placement: "bottomLeft",
255
                 placement: "bottomLeft",
188
                       "span",
264
                       "span",
189
                       { style: { color: "#666", fontWeight: 400 } },
265
                       { style: { color: "#666", fontWeight: 400 } },
190
                       "(\u60A8\u8FD8\u80FD\u4E0A\u4F20",
266
                       "(\u60A8\u8FD8\u80FD\u4E0A\u4F20",
191
-                      _constant.MAX_UPLOAD_NUMBER - fileList.length,
267
+                      _constant.MAX_UPLOAD_NUMBER - this.state.fileList.length,
192
                       "\u5F20\u56FE\u7247)"
268
                       "\u5F20\u56FE\u7247)"
193
                     )
269
                     )
194
                   ),
270
                   ),
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
                 type: "picture",
289
                 type: "picture",
210
-                className: "icon",
290
+                className: "comment-toolbar-icon",
211
                 style: { marginLeft: 10 },
291
                 style: { marginLeft: 10 },
212
                 onClick: function onClick() {
292
                 onClick: function onClick() {
213
                   return _this2.handleShowUpload(true);
293
                   return _this2.handleShowUpload(true);
217
           ),
297
           ),
218
           _react2.default.createElement(
298
           _react2.default.createElement(
219
             "div",
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
               _button2.default,
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
   placeholder: _propTypes2.default.string,
326
   placeholder: _propTypes2.default.string,
238
   showEmoji: _propTypes2.default.bool,
327
   showEmoji: _propTypes2.default.bool,
239
   showUpload: _propTypes2.default.bool,
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
 exports.default = Editor;
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
 var MAX_UPLOAD_NUMBER = exports.MAX_UPLOAD_NUMBER = 4;
16
 var MAX_UPLOAD_NUMBER = exports.MAX_UPLOAD_NUMBER = 4;
17
 
17
 
18
 var REGEXP = exports.REGEXP = /\[.+?\]/g;
18
 var REGEXP = exports.REGEXP = /\[.+?\]/g;
19
+
20
+var AVATAR = exports.AVATAR = "";
19
 //# sourceMappingURL=constant.js.map
21
 //# sourceMappingURL=constant.js.map

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

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
   title: "色"
17
   title: "色"
18
 }, {
18
 }, {
19
   value: "4",
19
   value: "4",
20
-  ttile: "发呆"
20
+  title: "发呆"
21
 }, {
21
 }, {
22
   value: "5",
22
   value: "5",
23
   title: "酷"
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
 "use strict";
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
 var _react = require("react");
5
 var _react = require("react");
4
 
6
 
5
 var _react2 = _interopRequireDefault(_react);
7
 var _react2 = _interopRequireDefault(_react);
18
 
20
 
19
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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
 (0, _registerServiceWorker2.default)();
118
 (0, _registerServiceWorker2.default)();
41
 //# sourceMappingURL=index.js.map
119
 //# sourceMappingURL=index.js.map

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

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
 // 语言包
7
 // 英文短语和中文提示的对应
7
 // 英文短语和中文提示的对应
8
 var data = {
8
 var data = {
9
+  "not found": "没有数据",
9
   "auth failed": "请先登录",
10
   "auth failed": "请先登录",
10
   "create comment failed": "创建评论失败",
11
   "create comment failed": "创建评论失败",
11
   "comment favor failed": "评论点赞失败",
12
   "comment favor failed": "评论点赞失败",

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

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

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

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
 {
1
 {
2
   "name": "comment",
2
   "name": "comment",
3
-  "version": "0.1.0",
3
+  "version": "0.2.0",
4
   "main": "lib/App.js",
4
   "main": "lib/App.js",
5
   "description": "通用评论",
5
   "description": "通用评论",
6
   "keywords": [
6
   "keywords": [
31
   },
31
   },
32
   "scripts": {
32
   "scripts": {
33
     "precommit": "lint-staged",
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
     "start": "react-app-rewired start",
35
     "start": "react-app-rewired start",
36
     "build": "react-app-rewired build",
36
     "build": "react-app-rewired build",
37
     "test": "react-app-rewired test --env=jsdom",
37
     "test": "react-app-rewired test --env=jsdom",

+ 13
- 0
src/App.css View File

7
   position: fixed;
7
   position: fixed;
8
   top: 45%;
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
 import React, { Component } from "react";
1
 import React, { Component } from "react";
2
 import PropTypes from "prop-types";
2
 import PropTypes from "prop-types";
3
-import { message } from "antd";
3
+import { message, Tag } from "antd";
4
 import axios from "./axios";
4
 import axios from "./axios";
5
 import { ERROR_DEFAULT, LIMIT } from "./constant";
5
 import { ERROR_DEFAULT, LIMIT } from "./constant";
6
 import { CommentContext } from "./Comment";
6
 import { CommentContext } from "./Comment";
8
 import CommentInput from "./components/CommentInput";
8
 import CommentInput from "./components/CommentInput";
9
 import CommentList from "./components/CommentList";
9
 import CommentList from "./components/CommentList";
10
 import Editor from "./components/Editor";
10
 import Editor from "./components/Editor";
11
+import lang from "./lang";
11
 import "./App.css";
12
 import "./App.css";
12
 
13
 
13
 class App extends Component {
14
 class App extends Component {
32
     this.sCreateComment = this.sCreateComment.bind(this);
33
     this.sCreateComment = this.sCreateComment.bind(this);
33
     this.sCreateReply = this.sCreateReply.bind(this);
34
     this.sCreateReply = this.sCreateReply.bind(this);
34
     this.sCommentFavor = this.sCommentFavor.bind(this);
35
     this.sCommentFavor = this.sCommentFavor.bind(this);
36
+    this.sReplyFavor = this.sReplyFavor.bind(this);
35
     this.sOssSts = this.sOssSts.bind(this);
37
     this.sOssSts = this.sOssSts.bind(this);
36
   }
38
   }
37
 
39
 
82
       })
84
       })
83
       .catch(error => {
85
       .catch(error => {
84
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
88
           return;
87
         }
89
         }
88
-        message.error(error.message || ERROR_DEFAULT);
90
+        message.error(lang[error.message] || ERROR_DEFAULT);
89
       })
91
       })
90
       .finally(() => {
92
       .finally(() => {
91
         this.handleChangeLoading("sGetComment", false);
93
         this.handleChangeLoading("sGetComment", false);
101
     axios
103
     axios
102
       .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`)
104
       .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`)
103
       .then(response => {
105
       .then(response => {
104
-        const { list: replies } = response.data;
105
-        if (!replies) {
106
+        if (!response.data.list) {
106
           message.info("没有更多数据了!");
107
           message.info("没有更多数据了!");
107
         }
108
         }
108
         const list = this.state.list.map(item => {
109
         const list = this.state.list.map(item => {
109
           if (item.id === commentId) {
110
           if (item.id === commentId) {
110
             if (!item.replies) item.replies = [];
111
             if (!item.replies) item.replies = [];
111
-            if (replies) {
112
+            if (response.data.list) {
112
               if (page === 1) {
113
               if (page === 1) {
113
                 // 如果当前页数为第一页,则清空当前所有的 replies
114
                 // 如果当前页数为第一页,则清空当前所有的 replies
114
                 // 并将获取到的 replies 存放在 state
115
                 // 并将获取到的 replies 存放在 state
115
-                item.replies = replies;
116
+                item.replies = response.data.list;
116
               } else {
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
                 // 如果当前页数非第一页,则合并 replies
121
                 // 如果当前页数非第一页,则合并 replies
119
               }
122
               }
123
+              item.reply_count = response.data.total;
124
+              item.reply_page = response.data.page;
120
             } else {
125
             } else {
121
               item.isNoMoreReply = true;
126
               item.isNoMoreReply = true;
122
             }
127
             }
127
       })
132
       })
128
       .catch(error => {
133
       .catch(error => {
129
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
136
           return;
132
         }
137
         }
133
-        message.error(error.message || ERROR_DEFAULT);
138
+        message.error(lang[error.message] || ERROR_DEFAULT);
134
       })
139
       })
135
       .finally(() => {
140
       .finally(() => {
136
         this.handleChangeLoading("sGetReply", false);
141
         this.handleChangeLoading("sGetReply", false);
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
     if (!content) return message.error("评论内容不能为空 ");
150
     if (!content) return message.error("评论内容不能为空 ");
146
     this.handleChangeLoading("sCreateComment", true);
151
     this.handleChangeLoading("sCreateComment", true);
147
     const { API, type, businessId } = this.props;
152
     const { API, type, businessId } = this.props;
159
         // 将数据写入到 list 中
164
         // 将数据写入到 list 中
160
         // 临时插入
165
         // 临时插入
161
         // 等到获取数据之后,删除临时数据
166
         // 等到获取数据之后,删除临时数据
162
-        const { list } = this.state;
167
+        const { list, total } = this.state;
163
         list.unshift({
168
         list.unshift({
164
           ...response.data,
169
           ...response.data,
165
           isTemporary: true // 临时的数据
170
           isTemporary: true // 临时的数据
166
         });
171
         });
167
-        this.setState({ list });
172
+        this.setState({ list, total: total + 1 });
168
       })
173
       })
169
       .catch(error => {
174
       .catch(error => {
170
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
177
           return;
173
         }
178
         }
174
-        message.error(error.message || ERROR_DEFAULT);
179
+        message.error(lang[error.message] || ERROR_DEFAULT);
175
       })
180
       })
176
       .finally(() => {
181
       .finally(() => {
177
         this.handleChangeLoading("sCreateComment", false);
182
         this.handleChangeLoading("sCreateComment", false);
184
    * @param {object} data { comment_id, content, [reply_id] }
189
    * @param {object} data { comment_id, content, [reply_id] }
185
    */
190
    */
186
   sCreateReply(data, cb) {
191
   sCreateReply(data, cb) {
192
+    console.log("list: ", this.state.list);
193
+
187
     if (!data.content) return message.error("回复内容不能为空 ");
194
     if (!data.content) return message.error("回复内容不能为空 ");
188
     this.handleChangeLoading("sCreateReply", true);
195
     this.handleChangeLoading("sCreateReply", true);
189
     const { API } = this.props;
196
     const { API } = this.props;
193
       withCredentials: true
200
       withCredentials: true
194
     })
201
     })
195
       .then(response => {
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
         message.success("回复成功!");
203
         message.success("回复成功!");
208
         if (isFunction(cb)) cb();
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
       .catch(error => {
221
       .catch(error => {
211
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
224
           return;
214
         }
225
         }
215
-        message.error(error.message || ERROR_DEFAULT);
226
+        message.error(lang[error.message] || ERROR_DEFAULT);
216
       })
227
       })
217
       .finally(() => {
228
       .finally(() => {
218
         this.handleChangeLoading("sCreateReply", false);
229
         this.handleChangeLoading("sCreateReply", false);
220
   }
231
   }
221
 
232
 
222
   /**
233
   /**
223
-   * 点赞/取消点赞
234
+   * 评论 点赞/取消点赞
224
    * @param {string} commentId { commentId }
235
    * @param {string} commentId { commentId }
225
    * @param {boolean} favored   是否已经点过赞
236
    * @param {boolean} favored   是否已经点过赞
226
    */
237
    */
245
       })
256
       })
246
       .catch(error => {
257
       .catch(error => {
247
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
260
           return;
250
         }
261
         }
251
-        message.error(error.message || ERROR_DEFAULT);
262
+        message.error(lang[error.message] || ERROR_DEFAULT);
252
       })
263
       })
253
       .finally(() => {
264
       .finally(() => {
254
         this.handleChangeLoading("sCommentFavor", false);
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
    * 获取 OSS 上传的参数
312
    * 获取 OSS 上传的参数
260
    */
313
    */
268
       })
321
       })
269
       .catch(error => {
322
       .catch(error => {
270
         if (error.response && error.response.data && error.response.data.msg) {
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
           return;
325
           return;
273
         }
326
         }
274
-        message.error(error.message || ERROR_DEFAULT);
327
+        message.error(lang[error.message] || ERROR_DEFAULT);
275
       })
328
       })
276
       .finally(() => {
329
       .finally(() => {
277
         this.handleChangeLoading("sOssSts", false);
330
         this.handleChangeLoading("sOssSts", false);
286
       sCreateComment: this.sCreateComment,
339
       sCreateComment: this.sCreateComment,
287
       sGetComment: this.sGetComment,
340
       sGetComment: this.sGetComment,
288
       sCommentFavor: this.sCommentFavor,
341
       sCommentFavor: this.sCommentFavor,
342
+      sReplyFavor: this.sReplyFavor,
289
       sCreateReply: this.sCreateReply,
343
       sCreateReply: this.sCreateReply,
290
       sGetReply: this.sGetReply,
344
       sGetReply: this.sGetReply,
291
       sOssSts: this.sOssSts
345
       sOssSts: this.sOssSts
294
     return (
348
     return (
295
       <CommentContext.Provider value={value}>
349
       <CommentContext.Provider value={value}>
296
         <div className="comment">
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
           {this.props.showEditor && (
360
           {this.props.showEditor && (
298
             <CommentInput content={this.props.children} />
361
             <CommentInput content={this.props.children} />
299
           )}
362
           )}
313
   businessId: PropTypes.string.isRequired, // 评论的 business_id
376
   businessId: PropTypes.string.isRequired, // 评论的 business_id
314
   API: PropTypes.string, // 评论的 API 前缀
377
   API: PropTypes.string, // 评论的 API 前缀
315
   showList: PropTypes.bool, // 是否显示评论列表
378
   showList: PropTypes.bool, // 是否显示评论列表
316
-  showEditor: PropTypes.bool // 是否显示评论输入框
379
+  showEditor: PropTypes.bool, // 是否显示评论输入框
380
+  showHeader: PropTypes.bool // 是否显示评论顶部的提示
317
 };
381
 };
318
 
382
 
319
 App.defaultProps = {
383
 App.defaultProps = {
320
   API: "http://api.links123.net/comment/v1",
384
   API: "http://api.links123.net/comment/v1",
321
   showList: true,
385
   showList: true,
322
-  showEditor: true
386
+  showEditor: true,
387
+  showHeader: true
323
 };
388
 };
324
 
389
 
325
 export { Editor };
390
 export { Editor };

+ 0
- 12
src/CHANGELOG.md View File

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

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

39
   /**
39
   /**
40
    * 渲染回复 DOM
40
    * 渲染回复 DOM
41
    * @param {array} replies 回复列表
41
    * @param {array} replies 回复列表
42
+   * @param {number} replies 回复的数量
42
    * @param {boolean} isNoMoreReply 是否没有更多回复
43
    * @param {boolean} isNoMoreReply 是否没有更多回复
43
    */
44
    */
44
-  renderReplies(replies, isNoMoreReply) {
45
+  renderReplies(replies, replyCount, isNoMoreReply) {
45
     const { commentId } = this.props;
46
     const { commentId } = this.props;
46
     const { showReply } = this.state;
47
     const { showReply } = this.state;
47
     if (showReply && replies && replies.length) {
48
     if (showReply && replies && replies.length) {
56
                   replyId={item.id}
57
                   replyId={item.id}
57
                   key={item.id}
58
                   key={item.id}
58
                   content={item}
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
                   <a
73
                   <a
72
                     style={{ float: "right" }}
74
                     style={{ float: "right" }}
83
                 replyId={item.id}
85
                 replyId={item.id}
84
                 key={item.id}
86
                 key={item.id}
85
                 content={item}
87
                 content={item}
86
-                type="reply"
88
+                action="replyToReply" // 评论的回复
87
               />
89
               />
88
             );
90
             );
89
           })}
91
           })}
103
           onShowReply={this.handleToggleReply}
105
           onShowReply={this.handleToggleReply}
104
           showReply={showReply}
106
           showReply={showReply}
105
           commentId={content.id}
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
       </div>
115
       </div>
110
     );
116
     );
111
   }
117
   }

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

1
 import React, { Component } from "react";
1
 import React, { Component } from "react";
2
 import PropTypes from "prop-types";
2
 import PropTypes from "prop-types";
3
-import { OSS_LINK } from "../../constant";
4
 import Comment from "../../Comment";
3
 import Comment from "../../Comment";
5
 
4
 
6
 class CommentInput extends Component {
5
 class CommentInput extends Component {
7
   constructor(props) {
6
   constructor(props) {
8
     super(props);
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
     this.handleSubmit = this.handleSubmit.bind(this);
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
       this.props.app.sCreateComment({
15
       this.props.app.sCreateComment({
60
         content: value
16
         content: value
61
       });
17
       });
62
-    } else if (type === "comment") {
18
+    } else if (action === "reply") {
63
       this.props.app.sCreateReply(
19
       this.props.app.sCreateReply(
64
         {
20
         {
65
           comment_id: commentId,
21
           comment_id: commentId,
66
           content: value
22
           content: value
67
         },
23
         },
68
-        () => handleToggleInput()
24
+        () => callback && callback()
69
       );
25
       );
70
-    } else if (type === "reply") {
26
+    } else if (action === "replyToReply") {
71
       this.props.app.sCreateReply(
27
       this.props.app.sCreateReply(
72
         {
28
         {
73
           comment_id: commentId,
29
           comment_id: commentId,
74
           content: value,
30
           content: value,
75
           reply_id: replyId
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
   render() {
38
   render() {
90
-    const { type } = this.props;
91
-    const { value, fileList } = this.state;
92
-
93
     const childrenWithProps = React.Children.map(this.props.content, child => {
39
     const childrenWithProps = React.Children.map(this.props.content, child => {
94
       return React.cloneElement(child, {
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
         onSubmit: this.handleSubmit,
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
 CommentInput.propTypes = {
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
 CommentInput.defaultProps = {
66
 CommentInput.defaultProps = {
140
-  type: "normal"
67
+  action: "comment"
141
 };
68
 };
142
 
69
 
143
 export default Comment(CommentInput);
70
 export default Comment(CommentInput);

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

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

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

17
   render() {
17
   render() {
18
     const {
18
     const {
19
       list,
19
       list,
20
+      total,
20
       page,
21
       page,
21
       loading,
22
       loading,
22
       isNoMoreComment,
23
       isNoMoreComment,
23
       sGetComment
24
       sGetComment
24
     } = this.props.app;
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
     return (
30
     return (
28
       <div>
31
       <div>
29
         <Spin spinning={spinning}>
32
         <Spin spinning={spinning}>
33
+          <div>共 {total} 条评论</div>
30
           {list.map(item => (
34
           {list.map(item => (
31
             <CommentBox content={item} key={item.id} commentId={item.id} />
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
         </Spin>
47
         </Spin>
43
       </div>
48
       </div>
44
     );
49
     );

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

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

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

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

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

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

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

1
-.editor {
1
+.comment-editor {
2
   box-sizing: border-box;
2
   box-sizing: border-box;
3
   margin: 0;
3
   margin: 0;
4
   padding: 0;
4
   padding: 0;
16
   border-radius: 4px;
16
   border-radius: 4px;
17
   transition: all 0.3s, height 0s;
17
   transition: all 0.3s, height 0s;
18
 }
18
 }
19
-.editor textarea.ant-input {
19
+.comment-editor textarea.ant-input {
20
   border: none;
20
   border: none;
21
   border-bottom: 1px solid #eee;
21
   border-bottom: 1px solid #eee;
22
   border-bottom-right-radius: 0;
22
   border-bottom-right-radius: 0;
23
   border-bottom-left-radius: 0;
23
   border-bottom-left-radius: 0;
24
 }
24
 }
25
-.editor textarea.ant-input:hover {
25
+.comment-editor textarea.ant-input:hover {
26
   border: none;
26
   border: none;
27
   border-bottom: 1px solid #eee;
27
   border-bottom: 1px solid #eee;
28
 }
28
 }
29
-.editor textarea.ant-input:focus {
29
+.comment-editor textarea.ant-input:focus {
30
   box-shadow: none;
30
   box-shadow: none;
31
   border-bottom: 1px solid #eee;
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
   content: attr(data-text);
34
   content: attr(data-text);
35
   color: #bfbfbf;
35
   color: #bfbfbf;
36
 }
36
 }
37
-.editor:focus,
38
-.editor:hover {
37
+.comment-editor:focus,
38
+.comment-editor:hover {
39
   border-color: #40a9ff;
39
   border-color: #40a9ff;
40
   outline: 0;
40
   outline: 0;
41
   box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
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
   display: inline-block;
44
   display: inline-block;
60
   width: 100%;
45
   width: 100%;
61
   margin: 5px 0 0 0;
46
   margin: 5px 0 0 0;
62
 }
47
 }
63
-.toolbar .icon {
48
+.comment-toolbar .comment-toolbar-icon {
64
   font-size: 23px;
49
   font-size: 23px;
65
   cursor: pointer;
50
   cursor: pointer;
66
   transition: color 0.3s;
51
   transition: color 0.3s;
67
 }
52
 }
68
-.toolbar .icon:hover {
53
+.comment-toolbar .comment-toolbar-icon:hover {
69
   color: #40a9ff;
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
   padding: 12px 16px 20px 16px;
66
   padding: 12px 16px 20px 16px;
73
 }
67
 }
74
-.feed .ant-carousel .slick-dots {
68
+.comment-emoji-popover .ant-carousel .slick-dots {
75
   bottom: -10px;
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
   background-color: #7b868a;
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
   background-color: #a2aeb5;
75
   background-color: #a2aeb5;
82
 }
76
 }

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

1
 import React from "react";
1
 import React from "react";
2
 import PropTypes from "prop-types";
2
 import PropTypes from "prop-types";
3
 import { Icon, Button, Popover, Input } from "antd";
3
 import { Icon, Button, Popover, Input } from "antd";
4
+import { OSS_LINK } from "../../constant";
4
 import { MAX_UPLOAD_NUMBER } from "../../constant";
5
 import { MAX_UPLOAD_NUMBER } from "../../constant";
5
 import Upload from "./Upload";
6
 import Upload from "./Upload";
6
 import Emoji from "./Emoji";
7
 import Emoji from "./Emoji";
7
 import "./index.css";
8
 import "./index.css";
8
 
9
 
9
 const { TextArea } = Input;
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
 class Editor extends React.Component {
12
 class Editor extends React.Component {
24
   constructor(props) {
13
   constructor(props) {
25
     super(props);
14
     super(props);
26
     this.state = {
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
     this.handleClickEmoji = this.handleClickEmoji.bind(this);
23
     this.handleClickEmoji = this.handleClickEmoji.bind(this);
24
+    this.handleChangeFileList = this.handleChangeFileList.bind(this);
30
     this.handleShowUpload = this.handleShowUpload.bind(this);
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
   componentDidMount() {}
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
   handleShowUpload(showUpload) {
69
   handleShowUpload(showUpload) {
40
     if (typeof showUpload === "boolean") {
70
     if (typeof showUpload === "boolean") {
41
       this.setState({ showUpload: showUpload });
71
       this.setState({ showUpload: showUpload });
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
   render() {
103
   render() {
48
-    const props = { ...EditorDefaultProps, ...this.props };
49
     const {
104
     const {
50
       value,
105
       value,
51
-      onChange,
52
-      onSubmit,
53
-      loading,
54
       placeholder,
106
       placeholder,
55
-      fileList,
56
-      onChangeFileList,
57
       rows,
107
       rows,
58
-      onUpload,
59
       showEmoji,
108
       showEmoji,
60
       showUpload,
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
     return (
119
     return (
65
-      <div className="editor">
120
+      <div className="comment-editor">
66
         <TextArea
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
           rows={rows}
124
           rows={rows}
70
           placeholder={placeholder}
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
             {showEmoji && (
130
             {showEmoji && (
76
               <Popover
131
               <Popover
77
                 trigger="click"
132
                 trigger="click"
82
                     <Emoji onClick={this.handleClickEmoji} />
137
                     <Emoji onClick={this.handleClickEmoji} />
83
                   </div>
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
               </Popover>
145
               </Popover>
89
             )}
146
             )}
90
 
147
 
97
                     style={{ width: 112 * MAX_UPLOAD_NUMBER, minHeight: 100 }}
154
                     style={{ width: 112 * MAX_UPLOAD_NUMBER, minHeight: 100 }}
98
                   >
155
                   >
99
                     <Upload
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
                   </div>
161
                   </div>
105
                 }
162
                 }
109
                     <span>
166
                     <span>
110
                       上传图片
167
                       上传图片
111
                       <span style={{ color: "#666", fontWeight: 400 }}>
168
                       <span style={{ color: "#666", fontWeight: 400 }}>
112
-                        (您还能上传{MAX_UPLOAD_NUMBER - fileList.length}张图片)
169
+                        (您还能上传{MAX_UPLOAD_NUMBER -
170
+                          this.state.fileList.length}张图片)
113
                       </span>
171
                       </span>
114
                     </span>
172
                     </span>
115
                     <Icon
173
                     <Icon
124
                   </div>
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
               </Popover>
197
               </Popover>
134
             )}
198
             )}
135
           </div>
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
           </div>
216
           </div>
142
         </div>
217
         </div>
143
       </div>
218
       </div>
150
   placeholder: PropTypes.string,
225
   placeholder: PropTypes.string,
151
   showEmoji: PropTypes.bool,
226
   showEmoji: PropTypes.bool,
152
   showUpload: PropTypes.bool,
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
 export default Editor;
249
 export default Editor;

+ 2
- 0
src/constant.js View File

11
 export const MAX_UPLOAD_NUMBER = 4;
11
 export const MAX_UPLOAD_NUMBER = 4;
12
 
12
 
13
 export const REGEXP = /\[.+?\]/g;
13
 export const REGEXP = /\[.+?\]/g;
14
+
15
+export const AVATAR = "";

+ 1
- 1
src/emoji.js View File

18
   },
18
   },
19
   {
19
   {
20
     value: "4",
20
     value: "4",
21
-    ttile: "发呆"
21
+    title: "发呆"
22
   },
22
   },
23
   {
23
   {
24
     value: "5",
24
     value: "5",

+ 76
- 21
src/index.js View File

1
-import React from "react";
1
+import React, { Component } from "react";
2
 import ReactDOM from "react-dom";
2
 import ReactDOM from "react-dom";
3
+import { Button, Icon } from "antd";
3
 import App, { Editor } from "./App";
4
 import App, { Editor } from "./App";
4
 import registerServiceWorker from "./registerServiceWorker";
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
 registerServiceWorker();
81
 registerServiceWorker();

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

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

+ 0
- 72
src/mock.js View File

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
-};