20 Commits

Author SHA1 Message Date
  Roxas 3f2f41970b add default show upload props 3 years ago
  AdamFu a5c4a7f2dd Merge branch 'master' of allen6/comment into master 4 years ago
  Allen 6365ddacee fix: 修复跟读 loading 空白问题 4 years ago
  Allen 48149d4ee5 feat: AudioPlayer 添加 loading 过程 4 years ago
  AdamFu f79e8f9daf Merge branch 'develop' of Tzhx/comment into master 4 years ago
  adam dec1b6fd70 fix:修正跟读的双语 4 years ago
  adam 7a67067d6a fix:跟读播放的时候,暂停其他的播放中的音频 4 years ago
  Tzhx 7da2eb9b93 feat: 支持跟读 4 years ago
  AdamFu daff054613 Merge branch 'feature/img' of Evo/comment into master 4 years ago
  Evo eb03b3e300 yarn lib 4 years ago
  Evo a888ebe920 image upload兼容性修改:添加limitOne适配私信 4 years ago
  Evo 016964ff38 移动端只能上传一张图片;执行yarn lib 4 years ago
  Evo e963be4624 图片上传适配移动端: 调整popover size;调整title 4 years ago
  AdamFu 9a188d5039 Merge branch 'feature/enter' of Evo/comment into master 4 years ago
  Evo d125718223 Update: 执行yarn lib 4 years ago
  Evo 556bf1c6ca Update: Enter触发submit事件,不是订阅Enter事件 4 years ago
  AdamFu 78fe198b54 Merge branch 'feature/enter' of Evo/comment into master 4 years ago
  Evo 25f5e26125 Add: comments on handlePressEnter 4 years ago
  Evo 855671a6fc Add: Enter event; 4 years ago
  Roxas 38edbfc2c7 feature: 编辑器内部值支持预处理函数preRenderValue 4 years ago
47 changed files with 1075 additions and 201 deletions
  1. 1
    0
      README.md
  2. 13
    5
      lib/App.js
  3. 1
    1
      lib/App.js.map
  4. BIN
      lib/assert/btn_audio_play.png
  5. BIN
      lib/assert/loading.gif
  6. 226
    0
      lib/components/AudioPlayer/index.js
  7. 1
    0
      lib/components/AudioPlayer/index.js.map
  8. 65
    0
      lib/components/AudioPlayer/index.less
  9. 12
    0
      lib/components/CommentList/index.css
  10. 36
    5
      lib/components/CommentList/index.js
  11. 1
    1
      lib/components/CommentList/index.js.map
  12. 27
    0
      lib/components/ContentItem/index.css
  13. 23
    1
      lib/components/ContentItem/index.js
  14. 1
    1
      lib/components/ContentItem/index.js.map
  15. 1
    1
      lib/components/EditComment/EditComment.js
  16. 1
    1
      lib/components/EditComment/EditComment.js.map
  17. 2
    2
      lib/components/Editor/Upload.js
  18. 1
    1
      lib/components/Editor/Upload.js.map
  19. 25
    0
      lib/components/Editor/index.css
  20. 106
    59
      lib/components/Editor/index.js
  21. 1
    1
      lib/components/Editor/index.js.map
  22. 10
    8
      lib/index.js
  23. 1
    1
      lib/index.js.map
  24. 2
    0
      lib/lang/en-US.js
  25. 1
    1
      lib/lang/en-US.js.map
  26. 2
    0
      lib/lang/zh-CN.js
  27. 1
    1
      lib/lang/zh-CN.js.map
  28. 10
    0
      lib/utils.js
  29. 1
    0
      lib/utils.js.map
  30. 4
    4
      lib/version.json
  31. 1
    1
      package.json
  32. 8
    4
      src/App.js
  33. BIN
      src/assert/btn_audio_play.png
  34. BIN
      src/assert/loading.gif
  35. 144
    0
      src/components/AudioPlayer/index.js
  36. 65
    0
      src/components/AudioPlayer/index.less
  37. 12
    0
      src/components/CommentList/index.css
  38. 35
    6
      src/components/CommentList/index.js
  39. 27
    0
      src/components/ContentItem/index.css
  40. 78
    59
      src/components/ContentItem/index.js
  41. 4
    1
      src/components/EditComment/EditComment.js
  42. 25
    0
      src/components/Editor/index.css
  43. 82
    29
      src/components/Editor/index.js
  44. 9
    7
      src/index.js
  45. 2
    0
      src/lang/en-US.js
  46. 2
    0
      src/lang/zh-CN.js
  47. 5
    0
      src/utils.js

+ 1
- 0
README.md View File

@@ -372,3 +372,4 @@ $ yarn start
372 372
 - [ ] 头像 404 `https://links123-images.oss-cn-hangzhou.aliyuncs.com/avatar/`
373 373
 - [ ] 上传图片的时候偶尔会出现InvalidPart
374 374
 - [ ] 上传图片失败时, 提示并且不显示缩略图
375
+- [x] 支持Ente事件(PC端和手机键盘Enter)

+ 13
- 5
lib/App.js View File

@@ -278,7 +278,9 @@ var App = function (_Component) {
278 278
 
279 279
       var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
280 280
           _ref2$page = _ref2.page,
281
-          page = _ref2$page === undefined ? 1 : _ref2$page;
281
+          page = _ref2$page === undefined ? 1 : _ref2$page,
282
+          _ref2$filterSpeak = _ref2.filterSpeak,
283
+          filterSpeak = _ref2$filterSpeak === undefined ? 0 : _ref2$filterSpeak;
282 284
 
283 285
       var pageType = this.props.pageType;
284 286
 
@@ -289,7 +291,7 @@ var App = function (_Component) {
289 291
           businessId = _props.businessId,
290 292
           limit = _props.limit;
291 293
 
292
-      this.axios.get(API + "/comments?type=" + type + "&business_id=" + businessId + "&page=" + page + "&limit=" + limit).then(function (response) {
294
+      this.axios.get(API + "/comments?type=" + type + "&business_id=" + businessId + "&is_speak=" + filterSpeak + "&page=" + page + "&limit=" + limit).then(function (response) {
293 295
         var _response$data = response.data,
294 296
             list = _response$data.list,
295 297
             page = _response$data.page,
@@ -745,6 +747,7 @@ var App = function (_Component) {
745 747
         sUpdateComment: this.sUpdateComment,
746 748
         handleEdit: this.handleEdit
747 749
       });
750
+
748 751
       return this.state.initDone && _react2.default.createElement(
749 752
         _Comment.CommentContext.Provider,
750 753
         { value: value },
@@ -766,7 +769,8 @@ var App = function (_Component) {
766 769
           commentId: this.state.commentId,
767 770
           userId: this.state.content.user_id,
768 771
           content: this.state.content,
769
-          handleClose: this.handleClose
772
+          handleClose: this.handleClose,
773
+          preRenderValue: this.props.preRenderValue
770 774
         })
771 775
       );
772 776
     }
@@ -797,7 +801,8 @@ App.propTypes = {
797 801
   onUpdateComment: _propTypes2.default.func,
798 802
   locales: _propTypes2.default.string, //  传入的语言环境, en-US/zh-CN
799 803
   onCountChange: _propTypes2.default.func, // 评论数量变更时的回调
800
-  onCommentFail: _propTypes2.default.func // 评论失败时的回调
804
+  onCommentFail: _propTypes2.default.func, // 评论失败时的回调
805
+  preRenderValue: _propTypes2.default.func // 编辑器渲染前对值需要做的工作
801 806
 };
802 807
 
803 808
 App.defaultProps = {
@@ -820,7 +825,10 @@ App.defaultProps = {
820 825
   onUpdateComment: function onUpdateComment() {},
821 826
   onBeforeUpdateComment: function onBeforeUpdateComment() {},
822 827
   onCountChange: function onCountChange() {},
823
-  onCommentFail: function onCommentFail() {}
828
+  onCommentFail: function onCommentFail() {},
829
+  preRenderValue: function preRenderValue(v) {
830
+    return v;
831
+  }
824 832
 };
825 833
 
826 834
 exports.Editor = _Editor2.default;

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


BIN
lib/assert/btn_audio_play.png View File


BIN
lib/assert/loading.gif View File


+ 226
- 0
lib/components/AudioPlayer/index.js View File

@@ -0,0 +1,226 @@
1
+"use strict";
2
+
3
+Object.defineProperty(exports, "__esModule", {
4
+  value: true
5
+});
6
+
7
+var _slider = require("antd/es/slider");
8
+
9
+var _slider2 = _interopRequireDefault(_slider);
10
+
11
+var _icon = require("antd/es/icon");
12
+
13
+var _icon2 = _interopRequireDefault(_icon);
14
+
15
+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; }; }();
16
+
17
+require("antd/es/slider/style");
18
+
19
+require("antd/es/icon/style");
20
+
21
+var _react = require("react");
22
+
23
+var _react2 = _interopRequireDefault(_react);
24
+
25
+var _dayjs = require("dayjs");
26
+
27
+var _dayjs2 = _interopRequireDefault(_dayjs);
28
+
29
+var _duration = require("dayjs/plugin/duration");
30
+
31
+var _duration2 = _interopRequireDefault(_duration);
32
+
33
+var _utc = require("dayjs/plugin/utc");
34
+
35
+var _utc2 = _interopRequireDefault(_utc);
36
+
37
+require("./index.less");
38
+
39
+var _loading = require("../../assert/loading.gif");
40
+
41
+var _loading2 = _interopRequireDefault(_loading);
42
+
43
+var _btn_audio_play = require("../../assert/btn_audio_play.png");
44
+
45
+var _btn_audio_play2 = _interopRequireDefault(_btn_audio_play);
46
+
47
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
48
+
49
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
50
+
51
+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; }
52
+
53
+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; }
54
+
55
+_dayjs2.default.extend(_duration2.default);
56
+_dayjs2.default.extend(_utc2.default);
57
+
58
+var AudioPlayer = function (_React$Component) {
59
+  _inherits(AudioPlayer, _React$Component);
60
+
61
+  function AudioPlayer(props) {
62
+    _classCallCheck(this, AudioPlayer);
63
+
64
+    var _this = _possibleConstructorReturn(this, (AudioPlayer.__proto__ || Object.getPrototypeOf(AudioPlayer)).call(this, props));
65
+
66
+    _this.state = {
67
+      duration: 0,
68
+      currentDuration: 0,
69
+      isPlaying: false,
70
+      isLoading: false
71
+    };
72
+    return _this;
73
+  }
74
+
75
+  _createClass(AudioPlayer, [{
76
+    key: "componentDidMount",
77
+    value: function componentDidMount() {
78
+      var _this2 = this;
79
+
80
+      if (this.player) {
81
+        var src = this.props.src;
82
+
83
+        this.player.oncanplay = function (event) {
84
+          return _this2.setState({ duration: event.target.duration });
85
+        };
86
+        this.player.onended = function () {
87
+          return _this2.setState({ isPlaying: false, currentDuration: 0 });
88
+        };
89
+        this.player.onpause = function () {
90
+          return _this2.setState({ isPlaying: false, isLoading: false });
91
+        };
92
+        this.player.onplay = function () {
93
+          return _this2.setState({ isPlaying: false, isLoading: true });
94
+        };
95
+        this.player.onplaying = function () {
96
+          return _this2.setState({ isPlaying: true, isLoading: false });
97
+        };
98
+        this.player.ontimeupdate = function (event) {
99
+          return _this2.setState({ currentDuration: event.target.currentTime });
100
+        };
101
+        this.player.src = src;
102
+      }
103
+    }
104
+  }, {
105
+    key: "componentDidUpdate",
106
+    value: function componentDidUpdate(prevProps) {
107
+      var _this3 = this;
108
+
109
+      if (this.props.src !== prevProps.src) {
110
+        this.player.oncanplay = function (event) {
111
+          return _this3.setState({ duration: event.target.duration });
112
+        };
113
+        this.player.onended = function () {
114
+          return _this3.setState({ isPlaying: false, currentDuration: 0 });
115
+        };
116
+        this.player.onpause = function () {
117
+          return _this3.setState({ isPlaying: false, isLoading: false });
118
+        };
119
+        this.player.onplay = function () {
120
+          return _this3.setState({ isPlaying: false, isLoading: true });
121
+        };
122
+        this.player.onplaying = function () {
123
+          return _this3.setState({ isPlaying: true, isLoading: false });
124
+        };
125
+        this.player.ontimeupdate = function (event) {
126
+          return _this3.setState({ currentDuration: event.target.currentTime });
127
+        };
128
+        this.player.src = this.props.src;
129
+      }
130
+    }
131
+  }, {
132
+    key: "clickPlayOrPause",
133
+    value: function clickPlayOrPause(isPlaying) {
134
+      if (!this.player) {
135
+        return;
136
+      }
137
+      if (isPlaying) {
138
+        this.player.pause();
139
+      } else {
140
+        // 如果是播放,先暂停其他的播放
141
+        var playerList = document.getElementsByTagName("audio");
142
+        if (playerList) {
143
+          Array.from(playerList).forEach(function (player) {
144
+            if (!player.paused) {
145
+              player.pause();
146
+            }
147
+          });
148
+        }
149
+        this.player.play();
150
+      }
151
+    }
152
+  }, {
153
+    key: "getPlayIcon",
154
+    value: function getPlayIcon() {
155
+      var _state = this.state,
156
+          isPlaying = _state.isPlaying,
157
+          isLoading = _state.isLoading;
158
+
159
+      var playIconElem = void 0;
160
+
161
+      if (isLoading) {
162
+        playIconElem = _react2.default.createElement("img", { className: "icon-loading", src: _loading2.default, alt: "iconLoading" });
163
+      } else if (isPlaying) {
164
+        playIconElem = _react2.default.createElement(_icon2.default, { className: "pause", type: "pause" });
165
+      } else {
166
+        playIconElem = _react2.default.createElement("img", { className: "icon-loading", src: _btn_audio_play2.default, alt: "iconPlaying" });
167
+      }
168
+      return playIconElem;
169
+    }
170
+  }, {
171
+    key: "render",
172
+    value: function render() {
173
+      var _this4 = this;
174
+
175
+      var _state2 = this.state,
176
+          currentDuration = _state2.currentDuration,
177
+          isPlaying = _state2.isPlaying,
178
+          duration = _state2.duration;
179
+
180
+      return _react2.default.createElement(
181
+        "div",
182
+        { className: "comment-item-speak-audio-container" },
183
+        _react2.default.createElement("audio", {
184
+          ref: function ref(_ref) {
185
+            _this4.player = _ref;
186
+          },
187
+          style: { display: "none" }
188
+        }),
189
+        _react2.default.createElement(
190
+          "span",
191
+          {
192
+            className: "icon",
193
+            onClick: function onClick() {
194
+              _this4.clickPlayOrPause(isPlaying);
195
+            }
196
+          },
197
+          this.getPlayIcon()
198
+        ),
199
+        _react2.default.createElement(_slider2.default, {
200
+          step: 0.001,
201
+          className: "slider",
202
+          tooltipVisible: false,
203
+          value: currentDuration,
204
+          max: duration,
205
+          onChange: function onChange(value) {
206
+            if (_this4.player) {
207
+              _this4.player.currentTime = value;
208
+            }
209
+          }
210
+        }),
211
+        _react2.default.createElement(
212
+          "span",
213
+          { className: "time" },
214
+          _dayjs2.default.utc(_dayjs2.default.duration(currentDuration, "seconds").asMilliseconds()).format("mm:ss"),
215
+          "/",
216
+          _dayjs2.default.utc(_dayjs2.default.duration(duration, "seconds").asMilliseconds()).format("mm:ss")
217
+        )
218
+      );
219
+    }
220
+  }]);
221
+
222
+  return AudioPlayer;
223
+}(_react2.default.Component);
224
+
225
+exports.default = AudioPlayer;
226
+//# sourceMappingURL=index.js.map

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


+ 65
- 0
lib/components/AudioPlayer/index.less View File

@@ -0,0 +1,65 @@
1
+.comment-item-speak-audio-container {
2
+  background-color: #f5f5f5;
3
+  border: 1px solid rgba(210, 210, 210, 1);
4
+  border-radius: 4px;
5
+  display: flex;
6
+  align-items: center;
7
+  height: 100%;
8
+
9
+  .icon {
10
+    cursor: pointer;
11
+    width: 28px;
12
+    height: 28px;
13
+    line-height: 28px;
14
+    text-align: center;
15
+    border-radius: 14px;
16
+    background-color: #fff;
17
+    color: #71c135;
18
+    margin-left: 12px;
19
+    font-size: 14px;
20
+  }
21
+
22
+  .icon-loading {
23
+    width: 100%;
24
+    height: 100%;
25
+  }
26
+
27
+  .slider {
28
+    margin: 0 0 0 16px;
29
+    width: 195px;
30
+
31
+    :global {
32
+      .ant-slider-rail {
33
+        background-color: #dfdfdf;
34
+      }
35
+    }
36
+  }
37
+
38
+  .time {
39
+    margin-left: 14px;
40
+    color: #848484;
41
+    font-size: 12px;
42
+  }
43
+}
44
+
45
+@media screen and (max-width: 616px) and (min-width: 449px) {
46
+  .comment-item-speak-audio-container {
47
+    .slider {
48
+      width: 195px;
49
+    }
50
+  }
51
+}
52
+@media screen and (max-width: 449px) and (min-width: 365px) {
53
+  .comment-item-speak-audio-container {
54
+    .slider {
55
+      width: 114px;
56
+    }
57
+  }
58
+}
59
+@media screen and (max-width: 365px) {
60
+  .comment-item-speak-audio-container {
61
+    .slider {
62
+      width: 60px;
63
+    }
64
+  }
65
+}

+ 12
- 0
lib/components/CommentList/index.css View File

@@ -17,3 +17,15 @@
17 17
 .comment-list-pagination {
18 18
   text-align: center;
19 19
 }
20
+
21
+.comment-list-filter-speak {
22
+  margin-left: 20px;
23
+  font-size: 14px;
24
+  color: rgba(93, 93, 93, 0.65);
25
+}
26
+
27
+@media screen and (max-width: 365px) {
28
+  .comment-list-filter-speak {
29
+    float: right;
30
+  }
31
+}

+ 36
- 5
lib/components/CommentList/index.js View File

@@ -8,6 +8,10 @@ var _spin = require("antd/es/spin");
8 8
 
9 9
 var _spin2 = _interopRequireDefault(_spin);
10 10
 
11
+var _checkbox = require("antd/es/checkbox");
12
+
13
+var _checkbox2 = _interopRequireDefault(_checkbox);
14
+
11 15
 var _pagination = require("antd/es/pagination");
12 16
 
13 17
 var _pagination2 = _interopRequireDefault(_pagination);
@@ -16,6 +20,8 @@ var _createClass = function () { function defineProperties(target, props) { for
16 20
 
17 21
 require("antd/es/spin/style");
18 22
 
23
+require("antd/es/checkbox/style");
24
+
19 25
 require("antd/es/pagination/style");
20 26
 
21 27
 var _react = require("react");
@@ -52,7 +58,9 @@ var CommentList = function (_Component) {
52 58
 
53 59
     var _this = _possibleConstructorReturn(this, (CommentList.__proto__ || Object.getPrototypeOf(CommentList)).call(this, props));
54 60
 
55
-    _this.state = {};
61
+    _this.state = {
62
+      filterSpeak: 0
63
+    };
56 64
     return _this;
57 65
   }
58 66
 
@@ -74,6 +82,7 @@ var CommentList = function (_Component) {
74 82
           sGetComment = _props$app.sGetComment,
75 83
           onPageChange = _props$app.onPageChange,
76 84
           onGetMoreBtnClick = _props$app.onGetMoreBtnClick;
85
+      var filterSpeak = this.state.filterSpeak;
77 86
 
78 87
       if (pageType === "slice") {
79 88
         // 截断多余评论,通过点击查看更多跳转
@@ -93,7 +102,7 @@ var CommentList = function (_Component) {
93 102
             {
94 103
               className: "comment-list-show-more",
95 104
               onClick: function onClick() {
96
-                sGetComment({ page: page + 1 });
105
+                sGetComment({ page: page + 1, filterSpeak: filterSpeak });
97 106
                 onPageChange(page + 1);
98 107
               }
99 108
             },
@@ -115,7 +124,7 @@ var CommentList = function (_Component) {
115 124
             current: page,
116 125
             total: total,
117 126
             onChange: function onChange(p) {
118
-              sGetComment({ page: p });
127
+              sGetComment({ page: p, filterSpeak: filterSpeak });
119 128
               onPageChange(p);
120 129
             }
121 130
           })
@@ -125,10 +134,15 @@ var CommentList = function (_Component) {
125 134
   }, {
126 135
     key: "render",
127 136
     value: function render() {
137
+      var _this2 = this;
138
+
128 139
       var _props$app2 = this.props.app,
129 140
           list = _props$app2.list,
130 141
           total = _props$app2.total,
131
-          loading = _props$app2.loading;
142
+          loading = _props$app2.loading,
143
+          isSpeak = _props$app2.isSpeak,
144
+          sGetComment = _props$app2.sGetComment,
145
+          onPageChange = _props$app2.onPageChange;
132 146
 
133 147
 
134 148
       var spinning = Boolean(loading.sGetComment || loading.sCommentFavor || loading.sReplyFavor);
@@ -141,7 +155,24 @@ var CommentList = function (_Component) {
141 155
           _react2.default.createElement(
142 156
             "div",
143 157
             null,
144
-            _reactIntlUniversal2.default.get("comment.totalComment", { total: total })
158
+            _reactIntlUniversal2.default.get("comment.totalComment", { total: total }),
159
+            isSpeak && _react2.default.createElement(
160
+              _checkbox2.default,
161
+              {
162
+                className: "comment-list-filter-speak",
163
+                onChange: function onChange(e) {
164
+                  _this2.setState({
165
+                    filterSpeak: e.target.checked ? 1 : 0
166
+                  });
167
+                  sGetComment({
168
+                    page: 1,
169
+                    filterSpeak: e.target.checked ? 1 : 0
170
+                  });
171
+                  onPageChange(1);
172
+                }
173
+              },
174
+              _reactIntlUniversal2.default.get("comment.filterSpeak")
175
+            )
145 176
           ),
146 177
           list.map(function (item) {
147 178
             return _react2.default.createElement(_CommentBox2.default, {

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


+ 27
- 0
lib/components/ContentItem/index.css View File

@@ -33,6 +33,21 @@
33 33
   word-break: break-all;
34 34
 }
35 35
 
36
+.comment-item-speak {
37
+  margin-top: 4px;
38
+}
39
+
40
+.comment-item-speak-message {
41
+  font-size: 14px;
42
+  color: #71c135;
43
+}
44
+
45
+.comment-item-speak-audio-wrapper {
46
+  margin-top: 8px;
47
+  width: 350px;
48
+  height: 44px;
49
+}
50
+
36 51
 .comment-item-bottom {
37 52
   display: flex;
38 53
   justify-content: flex-end;
@@ -156,6 +171,10 @@
156 171
     width: 85%;
157 172
     margin-left: 10px;
158 173
   }
174
+
175
+  .comment-item-speak-audio-wrapper {
176
+    width: 350px;
177
+  }
159 178
 }
160 179
 
161 180
 @media screen and (max-width: 449px) and (min-width: 365px) {
@@ -164,6 +183,10 @@
164 183
     width: 80%;
165 184
     margin-left: 10px;
166 185
   }
186
+
187
+  .comment-item-speak-audio-wrapper {
188
+    width: 266px;
189
+  }
167 190
 }
168 191
 
169 192
 @media screen and (max-width: 365px) {
@@ -172,6 +195,10 @@
172 195
     width: 75%;
173 196
     margin-left: 10px;
174 197
   }
198
+
199
+  .comment-item-speak-audio-wrapper {
200
+    width: 218px;
201
+  }
175 202
 }
176 203
 
177 204
 @media (max-width: 575px) {

+ 23
- 1
lib/components/ContentItem/index.js View File

@@ -80,6 +80,10 @@ var _ImagePreviewer = require("../ImagePreviewer/ImagePreviewer");
80 80
 
81 81
 var _ImagePreviewer2 = _interopRequireDefault(_ImagePreviewer);
82 82
 
83
+var _AudioPlayer = require("../AudioPlayer");
84
+
85
+var _AudioPlayer2 = _interopRequireDefault(_AudioPlayer);
86
+
83 87
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
84 88
 
85 89
 function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
@@ -197,6 +201,8 @@ var CommentItem = function (_Component) {
197 201
           app = _props.app,
198 202
           user_id = _props.user_id,
199 203
           page = _props.page;
204
+      var medias = content.medias,
205
+          isSpeak = content.is_speak;
200 206
       var _props$app = this.props.app,
201 207
           locale = _props$app.locale,
202 208
           showHoverCard = _props$app.showHoverCard,
@@ -295,6 +301,22 @@ var CommentItem = function (_Component) {
295 301
               __html: (0, _helper.renderContent)(this.renderTextWithReply(newContent, content))
296 302
             }
297 303
           }),
304
+          isSpeak && _react2.default.createElement(
305
+            "div",
306
+            { className: "comment-item-speak" },
307
+            _react2.default.createElement(
308
+              "span",
309
+              { className: "comment-item-speak-message" },
310
+              "[",
311
+              _reactIntlUniversal2.default.get("comment.speakComment"),
312
+              "]"
313
+            ),
314
+            _react2.default.createElement(
315
+              "div",
316
+              { className: "comment-item-speak-audio-wrapper" },
317
+              _react2.default.createElement(_AudioPlayer2.default, { src: medias && medias[0] && medias[0].url })
318
+            )
319
+          ),
298 320
           // image为空时不渲染comment-item-image
299 321
           imageList.length > 0 && imageList[0] !== "" && _react2.default.createElement(
300 322
             "div",
@@ -351,7 +373,7 @@ var CommentItem = function (_Component) {
351 373
                 showReply ? _react2.default.createElement(_icon2.default, { type: "up" }) : _react2.default.createElement(_icon2.default, { type: "down" })
352 374
               )
353 375
             ) : null,
354
-            showEdit && app.userId === content.user_id && _react2.default.createElement("i", {
376
+            showEdit && !isSpeak && app.userId === content.user_id && _react2.default.createElement("i", {
355 377
               className: "comment-item-edit",
356 378
               onClick: function onClick() {
357 379
                 return _this2.props.app.handleEdit({

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


+ 1
- 1
lib/components/EditComment/EditComment.js View File

@@ -193,7 +193,7 @@ var EditComment = function (_React$Component) {
193 193
             commentId: this.props.commentId,
194 194
             userId: this.props.content.user_id,
195 195
             fileList: this.state.fileList,
196
-            value: this.state.value,
196
+            value: this.props.preRenderValue && this.props.preRenderValue(this.state.value),
197 197
             onChange: function onChange(value) {
198 198
               _this2.setState({
199 199
                 value: value

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


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

@@ -232,8 +232,8 @@ var App = function (_React$Component) {
232 232
             onClick: _this3.handleCloseClick.bind(_this3, index),
233 233
             key: file.uid,
234 234
             style: {
235
-              left: index % 3 * 112 + 110 + "px",
236
-              top: Math.floor(index / 3) * 112 + 26 + "px"
235
+              left: index % 3 * 112 + 98 + "px",
236
+              top: Math.floor(index / 3) * 112 + 16 + "px"
237 237
             }
238 238
           });
239 239
         }),

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


+ 25
- 0
lib/components/Editor/index.css View File

@@ -130,3 +130,28 @@
130 130
     height: 28px;
131 131
   }
132 132
 }
133
+
134
+.comment-img-popover {
135
+  /* 一行显示3张图 */
136
+  width: 336px;
137
+  min-height: 100px;
138
+  margin: 0 auto;
139
+}
140
+
141
+.comment-img-title {
142
+  margin: 5px auto;
143
+}
144
+.comment-img-title-counter {
145
+  color: #666;
146
+  font-weight: 400;
147
+}
148
+
149
+@media (max-width: 575px) {
150
+  .comment-img-popover {
151
+    /* 一行显示2张图 */
152
+    width: 224px;
153
+  }
154
+  .comment-img-title-counter {
155
+    display: none;
156
+  }
157
+}

+ 106
- 59
lib/components/Editor/index.js View File

@@ -36,9 +36,13 @@ require("antd/es/message/style");
36 36
 
37 37
 require("antd/es/input/style");
38 38
 
39
-var _react = require("react");
39
+var _dayjs = require("dayjs");
40 40
 
41
-var _react2 = _interopRequireDefault(_react);
41
+var _dayjs2 = _interopRequireDefault(_dayjs);
42
+
43
+var _shortid = require("shortid");
44
+
45
+var _shortid2 = _interopRequireDefault(_shortid);
42 46
 
43 47
 var _propTypes = require("prop-types");
44 48
 
@@ -48,34 +52,32 @@ var _classnames = require("classnames");
48 52
 
49 53
 var _classnames2 = _interopRequireDefault(_classnames);
50 54
 
51
-var _reactIntlUniversal = require("react-intl-universal");
52
-
53
-var _reactIntlUniversal2 = _interopRequireDefault(_reactIntlUniversal);
54
-
55
-var _dayjs = require("dayjs");
55
+var _react = require("react");
56 56
 
57
-var _dayjs2 = _interopRequireDefault(_dayjs);
57
+var _react2 = _interopRequireDefault(_react);
58 58
 
59
-var _shortid = require("shortid");
59
+var _reactIntlUniversal = require("react-intl-universal");
60 60
 
61
-var _shortid2 = _interopRequireDefault(_shortid);
61
+var _reactIntlUniversal2 = _interopRequireDefault(_reactIntlUniversal);
62 62
 
63
-var _constant = require("../../constant");
63
+var _Emoji = require("./Emoji");
64 64
 
65
-var _helper = require("../../helper");
65
+var _Emoji2 = _interopRequireDefault(_Emoji);
66 66
 
67 67
 var _Upload = require("./Upload");
68 68
 
69 69
 var _Upload2 = _interopRequireDefault(_Upload);
70 70
 
71
-var _Emoji = require("./Emoji");
72
-
73
-var _Emoji2 = _interopRequireDefault(_Emoji);
74
-
75 71
 var _Comment = require("../../Comment");
76 72
 
77 73
 var _Comment2 = _interopRequireDefault(_Comment);
78 74
 
75
+var _utils = require("./../../utils");
76
+
77
+var _constant = require("../../constant");
78
+
79
+var _helper = require("../../helper");
80
+
79 81
 require("./index.css");
80 82
 
81 83
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -138,6 +140,8 @@ var Editor = function (_React$Component) {
138 140
     _this.handlePaste = _this.handlePaste.bind(_this);
139 141
     _this.resetState = _this.resetState.bind(_this);
140 142
     _this.handleEmojiScroll = _this.handleEmojiScroll.bind(_this);
143
+    _this.handlePressEnter = _this.handlePressEnter.bind(_this);
144
+    _this.invokeFileListChange = _this.invokeFileListChange.bind(_this);
141 145
     return _this;
142 146
   }
143 147
 
@@ -212,8 +216,7 @@ var Editor = function (_React$Component) {
212 216
       if (fileList.length > this.props.maxUpload) {
213 217
         list = fileList.slice(0, this.props.maxUpload);
214 218
       }
215
-      this.props.handleChangeFileList(list);
216
-      this.setState({ fileList: list });
219
+      this.invokeFileListChange(list);
217 220
     }
218 221
 
219 222
     /**
@@ -251,8 +254,33 @@ var Editor = function (_React$Component) {
251 254
         }
252 255
         return item;
253 256
       });
254
-      this.props.handleChangeFileList(fileList);
255
-      this.setState({ fileMap: fileMap, fileList: fileList });
257
+      this.setState({ fileMap: fileMap });
258
+      this.invokeFileListChange(fileList);
259
+    }
260
+
261
+    /**
262
+     *  **统一处理fileList的修改**
263
+     *  1. upload
264
+     *  2. paste
265
+     *  PS: 移动端需要做额外操作
266
+     *  -- evo 20200223
267
+     */
268
+
269
+  }, {
270
+    key: "invokeFileListChange",
271
+    value: function invokeFileListChange(fileList) {
272
+      var _props2 = this.props,
273
+          limitOne = _props2.limitOne,
274
+          handleChangeFileList = _props2.handleChangeFileList;
275
+
276
+      handleChangeFileList(fileList);
277
+      this.setState({ fileList: fileList });
278
+      if (limitOne && _utils.isMobile) {
279
+        var file = fileList[0];
280
+        if (file && file.status === "done" && !file.thumbUrl.includes("data:image")) {
281
+          this.setState({ uploadVisible: false });
282
+        }
283
+      }
256 284
     }
257 285
 
258 286
     /**
@@ -292,10 +320,7 @@ var Editor = function (_React$Component) {
292 320
             type: file.type,
293 321
             uid: new Date().valueOf()
294 322
           });
295
-          _this2.props.handleChangeFileList(fileList);
296
-          _this2.setState({
297
-            fileList: fileList
298
-          });
323
+          _this2.invokeFileListChange(fileList);
299 324
         }).catch(function (e) {
300 325
           var msg = e.message || _constant.ERROR_DEFAULT;
301 326
           if (_this2.props.showError) {
@@ -378,10 +403,10 @@ var Editor = function (_React$Component) {
378 403
   }, {
379 404
     key: "checkDisabledSubmit",
380 405
     value: function checkDisabledSubmit() {
381
-      var _props2 = this.props,
382
-          btnDisabled = _props2.btnDisabled,
383
-          value = _props2.value,
384
-          fileList = _props2.fileList;
406
+      var _props3 = this.props,
407
+          btnDisabled = _props3.btnDisabled,
408
+          value = _props3.value,
409
+          fileList = _props3.fileList;
385 410
 
386 411
       if (btnDisabled) {
387 412
         return true;
@@ -400,29 +425,48 @@ var Editor = function (_React$Component) {
400 425
       }
401 426
       return true;
402 427
     }
428
+
429
+    /**
430
+     *  **处理Enter事件**
431
+     *  1. `allowEnterSubmit`为true时enter触发submit事件
432
+     *  2. `e.preventDefault`为了防止enter事件后仍触发换行
433
+     *  3. enter事件开启后,仍可以用`shift + enter`触发换行
434
+     *  -- evo 20200222
435
+     */
436
+
437
+  }, {
438
+    key: "handlePressEnter",
439
+    value: function handlePressEnter(e) {
440
+      if (this.props.allowEnterSubmit) {
441
+        if (!e.shiftKey) {
442
+          e.preventDefault();
443
+          this.handleSubmit();
444
+        }
445
+      }
446
+    }
403 447
   }, {
404 448
     key: "render",
405 449
     value: function render() {
406 450
       var _this4 = this;
407 451
 
408
-      var _props3 = this.props,
409
-          value = _props3.value,
410
-          rows = _props3.rows,
411
-          showEmoji = _props3.showEmoji,
412
-          showUpload = _props3.showUpload,
413
-          multiple = _props3.multiple,
414
-          emojiPopoverPlacement = _props3.emojiPopoverPlacement,
415
-          uploadPopoverPlacement = _props3.uploadPopoverPlacement,
416
-          uploadOverlayClassName = _props3.uploadOverlayClassName,
417
-          fileList = _props3.fileList,
418
-          maxUpload = _props3.maxUpload,
419
-          btnLoading = _props3.btnLoading,
420
-          button = _props3.button,
421
-          emojiToolIcon = _props3.emojiToolIcon,
422
-          imageToolIcon = _props3.imageToolIcon,
423
-          maxLength = _props3.maxLength,
424
-          autoFocus = _props3.autoFocus,
425
-          app = _props3.app;
452
+      var _props4 = this.props,
453
+          value = _props4.value,
454
+          rows = _props4.rows,
455
+          showEmoji = _props4.showEmoji,
456
+          showUpload = _props4.showUpload,
457
+          multiple = _props4.multiple,
458
+          emojiPopoverPlacement = _props4.emojiPopoverPlacement,
459
+          uploadPopoverPlacement = _props4.uploadPopoverPlacement,
460
+          uploadOverlayClassName = _props4.uploadOverlayClassName,
461
+          fileList = _props4.fileList,
462
+          maxUpload = _props4.maxUpload,
463
+          btnLoading = _props4.btnLoading,
464
+          button = _props4.button,
465
+          emojiToolIcon = _props4.emojiToolIcon,
466
+          imageToolIcon = _props4.imageToolIcon,
467
+          maxLength = _props4.maxLength,
468
+          autoFocus = _props4.autoFocus,
469
+          app = _props4.app;
426 470
 
427 471
       var placeholder = this.props.placeholder || _reactIntlUniversal2.default.get("editor.placeholder");
428 472
       var btnSubmitText = this.props.btnSubmitText || _reactIntlUniversal2.default.get("editor.SubmitBtn");
@@ -431,6 +475,7 @@ var Editor = function (_React$Component) {
431 475
       var inputValue = value || this.state.value;
432 476
       var uploadFileList = fileList || this.state.fileList;
433 477
       var isLogin = app.currentUser && (app.currentUser.user_id > 0 || app.currentUser.id > 0);
478
+
434 479
       return _react2.default.createElement(
435 480
         "div",
436 481
         { className: "comment-editor-container", onPaste: this.handlePaste },
@@ -449,11 +494,12 @@ var Editor = function (_React$Component) {
449 494
             _react2.default.createElement(TextArea, {
450 495
               value: inputValue,
451 496
               onChange: function onChange(e) {
452
-                return _this4.handleChange(e.target.value);
497
+                _this4.handleChange(e.target.value);
453 498
               },
454 499
               rows: rows,
455 500
               placeholder: placeholder,
456
-              autoFocus: autoFocus
501
+              autoFocus: autoFocus,
502
+              onPressEnter: this.handlePressEnter
457 503
             }),
458 504
             _react2.default.createElement(
459 505
               "div",
@@ -503,13 +549,7 @@ var Editor = function (_React$Component) {
503 549
                     },
504 550
                     content: _react2.default.createElement(
505 551
                       "div",
506
-                      {
507
-                        style: {
508
-                          width: 336, // 一行显示3张
509
-                          minHeight: 100,
510
-                          margin: "0 auto"
511
-                        }
512
-                      },
552
+                      { className: "comment-img-popover" },
513 553
                       _react2.default.createElement(_Upload2.default, {
514 554
                         onRef: function onRef(node) {
515 555
                           return _this4.uploadRef = node;
@@ -526,14 +566,14 @@ var Editor = function (_React$Component) {
526 566
                     ),
527 567
                     title: _react2.default.createElement(
528 568
                       "div",
529
-                      { style: { margin: "5px auto" } },
569
+                      { className: "comment-img-title" },
530 570
                       _react2.default.createElement(
531 571
                         "span",
532 572
                         null,
533 573
                         _reactIntlUniversal2.default.get("editor.uploadTip"),
534 574
                         maxUpload >= 2 ? _react2.default.createElement(
535 575
                           "span",
536
-                          { style: { color: "#666", fontWeight: 400 } },
576
+                          { className: "comment-img-title-counter" },
537 577
                           _reactIntlUniversal2.default.get("editor.uploadCount", {
538 578
                             count: maxUpload - uploadFileList.length
539 579
                           })
@@ -628,7 +668,11 @@ Editor.propTypes = {
628 668
   imageToolIcon: _propTypes2.default.node,
629 669
   showError: _propTypes2.default.bool,
630 670
   onError: _propTypes2.default.func,
631
-  maxLength: _propTypes2.default.number
671
+  maxLength: _propTypes2.default.number,
672
+  // Enter事件相关
673
+  allowEnterSubmit: _propTypes2.default.bool,
674
+  // 私信仅允许选中一个,此处可以优化为通用的limit
675
+  limitOne: _propTypes2.default.bool
632 676
 };
633 677
 
634 678
 Editor.defaultProps = {
@@ -648,7 +692,10 @@ Editor.defaultProps = {
648 692
   showError: true,
649 693
   maxLength: 5000,
650 694
   app: {},
651
-  handleChangeFileList: function handleChangeFileList() {}
695
+  handleChangeFileList: function handleChangeFileList() {},
696
+  // Enter事件相关
697
+  allowEnterSubmit: false,
698
+  limitOne: false
652 699
 };
653 700
 
654 701
 exports.default = (0, _Comment2.default)(Editor);

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


+ 10
- 8
lib/index.js View File

@@ -68,16 +68,15 @@ var Index = function (_React$Component) {
68 68
           fileList: this.state.fileList,
69 69
           value: this.state.value,
70 70
           onChange: function onChange(value) {
71
-            _this2.setState({
72
-              value: value
73
-            });
71
+            _this2.setState({ value: value });
74 72
           },
75 73
           handleChangeFileList: function handleChangeFileList(fileList) {
76
-            console.log("----", fileList);
77 74
             _this2.setState({
78 75
               fileList: fileList
79 76
             });
80
-          }
77
+          },
78
+          allowEnterSubmit: true,
79
+          limitOne: true
81 80
         }))
82 81
       );
83 82
     }
@@ -123,8 +122,8 @@ window.renderComment = renderComment;
123 122
 if (process.env.NODE_ENV !== "production") {
124 123
   renderComment({
125 124
     id: "root-comment",
126
-    type: 1,
127
-    businessId: "test",
125
+    type: 3,
126
+    businessId: "5ea8320dedd68200018e733d",
128 127
     businessUserId: 4,
129 128
     userId: 58297,
130 129
     currentUser: {
@@ -177,7 +176,7 @@ if (process.env.NODE_ENV !== "production") {
177 176
       });
178 177
     },
179 178
     onCountChange: function onCountChange(c) {
180
-      console.log(c);
179
+      // console.log(c);
181 180
     },
182 181
     onDelete: function onDelete(type, data) {
183 182
       console.log(type, data);
@@ -194,6 +193,9 @@ if (process.env.NODE_ENV !== "production") {
194 193
     sendMessage: function sendMessage(id) {
195 194
       console.log("sendMessage", id);
196 195
     },
196
+    preRenderValue: function preRenderValue(value) {
197
+      return "" + value;
198
+    },
197 199
     editorProps: {
198 200
       onCommentSuccess: function onCommentSuccess(data) {
199 201
         console.log(data);

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


+ 2
- 0
lib/lang/en-US.js View File

@@ -16,6 +16,8 @@ var USdata = {
16 16
   "comment.totalComment": "Total {total, plural, =1 {one comment} other {# comments}}",
17 17
   "comment.reply": "Reply",
18 18
   "comment.moreComment": "More comments",
19
+  "comment.filterSpeak": "Show imitations only",
20
+  "comment.speakComment": "Imitation audio",
19 21
 
20 22
   "reply.totalReply": "Total {total, plural, =1 {one reply} other {# replies}}",
21 23
   "reply.moreReply": "More replies",

+ 1
- 1
lib/lang/en-US.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../../src/lang/en-US.js"],"names":["USdata"],"mappings":";;;;;AAAA,IAAMA,SAAS;AACb,2BAAyB,uBADZ;AAEb,wBAAsB,eAFT;AAGb,sBAAoB,2BAHP;AAIb,sBAAoB,MAJP;AAKb,sBAAoB,iBALP;AAMb,wBAAsB,iCANT;AAOb,sBAAoB,QAPP;;AASb,qBAAmB,SATN;AAUb,0BACE,4DAXW;AAYb,mBAAiB,OAZJ;AAab,yBAAuB,eAbV;;AAeb,sBAAoB,yDAfP;AAgBb,qBAAmB,cAhBN;AAiBb,oBAAkB,cAjBL;;AAmBb,sBAAoB,MAnBP;AAoBb,0BAAwB,kBApBX;;AAsBb,sBAAoB,SAtBP;AAuBb,mBAAiB,SAvBJ;AAwBb,uBAAqB,QAxBR;AAyBb,uBAAqB,QAzBR;;AA2Bb,2BAAyB,kBA3BZ;AA4Bb,wBAAsB,cA5BT;AA6Bb,qBAAmB,kBA7BN;AA8Bb,qBAAmB,eA9BN;AA+Bb,yBAAuB,kBA/BV;AAgCb,0BAAwB,YAhCX;AAiCb,+BAA6B,SAjChB;AAkCb,yBAAuB;AAlCV,CAAf;;kBAqCeA,M","file":"en-US.js","sourcesContent":["const USdata = {\n  \"editor.alreadyEntered\": \"{count} words entered\",\n  \"editor.placeholder\": \"Say something\",\n  \"editor.maxLength\": \"Maximum {maxLength} words\",\n  \"editor.SubmitBtn\": \"Send\",\n  \"editor.uploadTip\": \"Upload pictures\",\n  \"editor.uploadCount\": \"(You could upload {count} more)\",\n  \"editor.uploadBtn\": \"Upload\",\n\n  \"comment.tourist\": \"Visitor\",\n  \"comment.totalComment\":\n    \"Total {total, plural, =1 {one comment} other {# comments}}\",\n  \"comment.reply\": \"Reply\",\n  \"comment.moreComment\": \"More comments\",\n\n  \"reply.totalReply\": \"Total {total, plural, =1 {one reply} other {# replies}}\",\n  \"reply.moreReply\": \"More replies\",\n  \"reply.collapse\": \"Fold replies\",\n\n  \"picture.collapse\": \"Fold\",\n  \"picture.viewOriginal\": \"See the original\",\n\n  \"popConfirm.title\": \"Delete?\",\n  \"popConfirm.ok\": \"Confirm\",\n  \"popConfirm.cancel\": \"Cancel\",\n  \"popConfirm.delete\": \"Delete\",\n\n  \"message.noMoreComment\": \"No more comments\",\n  \"message.noMoreData\": \"No more data\",\n  \"message.notNull\": \"It's still empty\",\n  \"message.success\": \"Comments sent\",\n  \"message.replyNoNull\": \"It's still empty\",\n  \"message.replySuccess\": \"Reply sent\",\n  \"message.cancelLickSuccess\": \"Unliked\",\n  \"message.likeSuccess\": \"Liked\"\n};\n\nexport default USdata;\n"]}
1
+{"version":3,"sources":["../../src/lang/en-US.js"],"names":["USdata"],"mappings":";;;;;AAAA,IAAMA,SAAS;AACb,2BAAyB,uBADZ;AAEb,wBAAsB,eAFT;AAGb,sBAAoB,2BAHP;AAIb,sBAAoB,MAJP;AAKb,sBAAoB,iBALP;AAMb,wBAAsB,iCANT;AAOb,sBAAoB,QAPP;;AASb,qBAAmB,SATN;AAUb,0BACE,4DAXW;AAYb,mBAAiB,OAZJ;AAab,yBAAuB,eAbV;AAcb,yBAAuB,sBAdV;AAeb,0BAAwB,iBAfX;;AAiBb,sBAAoB,yDAjBP;AAkBb,qBAAmB,cAlBN;AAmBb,oBAAkB,cAnBL;;AAqBb,sBAAoB,MArBP;AAsBb,0BAAwB,kBAtBX;;AAwBb,sBAAoB,SAxBP;AAyBb,mBAAiB,SAzBJ;AA0Bb,uBAAqB,QA1BR;AA2Bb,uBAAqB,QA3BR;;AA6Bb,2BAAyB,kBA7BZ;AA8Bb,wBAAsB,cA9BT;AA+Bb,qBAAmB,kBA/BN;AAgCb,qBAAmB,eAhCN;AAiCb,yBAAuB,kBAjCV;AAkCb,0BAAwB,YAlCX;AAmCb,+BAA6B,SAnChB;AAoCb,yBAAuB;AApCV,CAAf;;kBAuCeA,M","file":"en-US.js","sourcesContent":["const USdata = {\n  \"editor.alreadyEntered\": \"{count} words entered\",\n  \"editor.placeholder\": \"Say something\",\n  \"editor.maxLength\": \"Maximum {maxLength} words\",\n  \"editor.SubmitBtn\": \"Send\",\n  \"editor.uploadTip\": \"Upload pictures\",\n  \"editor.uploadCount\": \"(You could upload {count} more)\",\n  \"editor.uploadBtn\": \"Upload\",\n\n  \"comment.tourist\": \"Visitor\",\n  \"comment.totalComment\":\n    \"Total {total, plural, =1 {one comment} other {# comments}}\",\n  \"comment.reply\": \"Reply\",\n  \"comment.moreComment\": \"More comments\",\n  \"comment.filterSpeak\": \"Show imitations only\",\n  \"comment.speakComment\": \"Imitation audio\",\n\n  \"reply.totalReply\": \"Total {total, plural, =1 {one reply} other {# replies}}\",\n  \"reply.moreReply\": \"More replies\",\n  \"reply.collapse\": \"Fold replies\",\n\n  \"picture.collapse\": \"Fold\",\n  \"picture.viewOriginal\": \"See the original\",\n\n  \"popConfirm.title\": \"Delete?\",\n  \"popConfirm.ok\": \"Confirm\",\n  \"popConfirm.cancel\": \"Cancel\",\n  \"popConfirm.delete\": \"Delete\",\n\n  \"message.noMoreComment\": \"No more comments\",\n  \"message.noMoreData\": \"No more data\",\n  \"message.notNull\": \"It's still empty\",\n  \"message.success\": \"Comments sent\",\n  \"message.replyNoNull\": \"It's still empty\",\n  \"message.replySuccess\": \"Reply sent\",\n  \"message.cancelLickSuccess\": \"Unliked\",\n  \"message.likeSuccess\": \"Liked\"\n};\n\nexport default USdata;\n"]}

+ 2
- 0
lib/lang/zh-CN.js View File

@@ -16,6 +16,8 @@ var CNdata = {
16 16
   "comment.totalComment": "共{total}条评论",
17 17
   "comment.reply": "回复",
18 18
   "comment.moreComment": "更多评论",
19
+  "comment.filterSpeak": "只显示跟读",
20
+  "comment.speakComment": "跟读语音",
19 21
 
20 22
   "reply.totalReply": "共{total}条回复",
21 23
   "reply.moreReply": "更多回复",

+ 1
- 1
lib/lang/zh-CN.js.map View File

@@ -1 +1 @@
1
-{"version":3,"sources":["../../src/lang/zh-CN.js"],"names":["CNdata"],"mappings":";;;;;AAAA,IAAMA,SAAS;AACb,2BAAyB,yBADZ;AAEb,wBAAsB,UAFT;AAGb,sBAAoB,iBAHP;AAIb,sBAAoB,IAJP;AAKb,sBAAoB,MALP;AAMb,wBAAsB,mBANT;AAOb,sBAAoB,IAPP;;AASb,qBAAmB,IATN;AAUb,0BAAwB,aAVX;AAWb,mBAAiB,IAXJ;AAYb,yBAAuB,MAZV;;AAcb,sBAAoB,aAdP;AAeb,qBAAmB,MAfN;AAgBb,oBAAkB,MAhBL;;AAkBb,sBAAoB,IAlBP;AAmBb,0BAAwB,MAnBX;;AAqBb,sBAAoB,QArBP;AAsBb,mBAAiB,IAtBJ;AAuBb,uBAAqB,IAvBR;AAwBb,uBAAqB,IAxBR;;AA0Bb,2BAAyB,SA1BZ;AA2Bb,wBAAsB,UA3BT;AA4Bb,qBAAmB,OA5BN;AA6Bb,qBAAmB,QA7BN;AA8Bb,yBAAuB,OA9BV;AA+Bb,0BAAwB,QA/BX;AAgCb,+BAA6B,QAhChB;AAiCb,yBAAuB;AAjCV,CAAf;;kBAoCeA,M","file":"zh-CN.js","sourcesContent":["const CNdata = {\n  \"editor.alreadyEntered\": \"已输入{count}/{maxLength}字\",\n  \"editor.placeholder\": \"说点什么吧...\",\n  \"editor.maxLength\": \"字数上限{maxLength}\",\n  \"editor.SubmitBtn\": \"发送\",\n  \"editor.uploadTip\": \"上传图片\",\n  \"editor.uploadCount\": \"(您还能上传{count}张图片)\",\n  \"editor.uploadBtn\": \"上传\",\n\n  \"comment.tourist\": \"游客\",\n  \"comment.totalComment\": \"共{total}条评论\",\n  \"comment.reply\": \"回复\",\n  \"comment.moreComment\": \"更多评论\",\n\n  \"reply.totalReply\": \"共{total}条回复\",\n  \"reply.moreReply\": \"更多回复\",\n  \"reply.collapse\": \"收起回复\",\n\n  \"picture.collapse\": \"收起\",\n  \"picture.viewOriginal\": \"查看原图\",\n\n  \"popConfirm.title\": \"确定要删除吗\",\n  \"popConfirm.ok\": \"确定\",\n  \"popConfirm.cancel\": \"取消\",\n  \"popConfirm.delete\": \"删除\",\n\n  \"message.noMoreComment\": \"没有更多评论了\",\n  \"message.noMoreData\": \"没有更多数据了!\",\n  \"message.notNull\": \"没写内容呢\",\n  \"message.success\": \"评论已发送!\",\n  \"message.replyNoNull\": \"没写内容呢\",\n  \"message.replySuccess\": \"回复已发送!\",\n  \"message.cancelLickSuccess\": \"已取消点赞!\",\n  \"message.likeSuccess\": \"已赞!\"\n};\n\nexport default CNdata;\n"]}
1
+{"version":3,"sources":["../../src/lang/zh-CN.js"],"names":["CNdata"],"mappings":";;;;;AAAA,IAAMA,SAAS;AACb,2BAAyB,yBADZ;AAEb,wBAAsB,UAFT;AAGb,sBAAoB,iBAHP;AAIb,sBAAoB,IAJP;AAKb,sBAAoB,MALP;AAMb,wBAAsB,mBANT;AAOb,sBAAoB,IAPP;;AASb,qBAAmB,IATN;AAUb,0BAAwB,aAVX;AAWb,mBAAiB,IAXJ;AAYb,yBAAuB,MAZV;AAab,yBAAuB,OAbV;AAcb,0BAAwB,MAdX;;AAgBb,sBAAoB,aAhBP;AAiBb,qBAAmB,MAjBN;AAkBb,oBAAkB,MAlBL;;AAoBb,sBAAoB,IApBP;AAqBb,0BAAwB,MArBX;;AAuBb,sBAAoB,QAvBP;AAwBb,mBAAiB,IAxBJ;AAyBb,uBAAqB,IAzBR;AA0Bb,uBAAqB,IA1BR;;AA4Bb,2BAAyB,SA5BZ;AA6Bb,wBAAsB,UA7BT;AA8Bb,qBAAmB,OA9BN;AA+Bb,qBAAmB,QA/BN;AAgCb,yBAAuB,OAhCV;AAiCb,0BAAwB,QAjCX;AAkCb,+BAA6B,QAlChB;AAmCb,yBAAuB;AAnCV,CAAf;;kBAsCeA,M","file":"zh-CN.js","sourcesContent":["const CNdata = {\n  \"editor.alreadyEntered\": \"已输入{count}/{maxLength}字\",\n  \"editor.placeholder\": \"说点什么吧...\",\n  \"editor.maxLength\": \"字数上限{maxLength}\",\n  \"editor.SubmitBtn\": \"发送\",\n  \"editor.uploadTip\": \"上传图片\",\n  \"editor.uploadCount\": \"(您还能上传{count}张图片)\",\n  \"editor.uploadBtn\": \"上传\",\n\n  \"comment.tourist\": \"游客\",\n  \"comment.totalComment\": \"共{total}条评论\",\n  \"comment.reply\": \"回复\",\n  \"comment.moreComment\": \"更多评论\",\n  \"comment.filterSpeak\": \"只显示跟读\",\n  \"comment.speakComment\": \"跟读语音\",\n\n  \"reply.totalReply\": \"共{total}条回复\",\n  \"reply.moreReply\": \"更多回复\",\n  \"reply.collapse\": \"收起回复\",\n\n  \"picture.collapse\": \"收起\",\n  \"picture.viewOriginal\": \"查看原图\",\n\n  \"popConfirm.title\": \"确定要删除吗\",\n  \"popConfirm.ok\": \"确定\",\n  \"popConfirm.cancel\": \"取消\",\n  \"popConfirm.delete\": \"删除\",\n\n  \"message.noMoreComment\": \"没有更多评论了\",\n  \"message.noMoreData\": \"没有更多数据了!\",\n  \"message.notNull\": \"没写内容呢\",\n  \"message.success\": \"评论已发送!\",\n  \"message.replyNoNull\": \"没写内容呢\",\n  \"message.replySuccess\": \"回复已发送!\",\n  \"message.cancelLickSuccess\": \"已取消点赞!\",\n  \"message.likeSuccess\": \"已赞!\"\n};\n\nexport default CNdata;\n"]}

+ 10
- 0
lib/utils.js View File

@@ -0,0 +1,10 @@
1
+"use strict";
2
+
3
+Object.defineProperty(exports, "__esModule", {
4
+  value: true
5
+});
6
+/**
7
+ * 575: 为了和media query中的判断保持一致
8
+ */
9
+var isMobile = exports.isMobile = typeof window.orientation === "number" && document.body.offsetWidth < 575;
10
+//# sourceMappingURL=utils.js.map

+ 1
- 0
lib/utils.js.map View File

@@ -0,0 +1 @@
1
+{"version":3,"sources":["../src/utils.js"],"names":["isMobile","window","orientation","document","body","offsetWidth"],"mappings":";;;;;AAAA;;;AAGO,IAAMA,8BACX,OAAOC,OAAOC,WAAd,KAA8B,QAA9B,IAA0CC,SAASC,IAAT,CAAcC,WAAd,GAA4B,GADjE","file":"utils.js","sourcesContent":["/**\n * 575: 为了和media query中的判断保持一致\n */\nexport const isMobile =\n  typeof window.orientation === \"number\" && document.body.offsetWidth < 575;\n"]}

+ 4
- 4
lib/version.json View File

@@ -1,8 +1,8 @@
1 1
 {
2 2
     "name":       "comment",
3
-    "buildDate":  1574306910894,
3
+    "buildDate":  1588435719936,
4 4
     "version":    "1.0.4",
5
-    "numCommits": 195,
6
-    "hash":       "db0a99c",
7
-    "dirty":      false
5
+    "numCommits": 215,
6
+    "hash":       "7d03c9b",
7
+    "dirty":      true
8 8
 }

+ 1
- 1
package.json View File

@@ -16,7 +16,7 @@
16 16
   "dependencies": {
17 17
     "antd": "^3.19.3",
18 18
     "axios": "^0.18.0",
19
-    "dayjs": "^1.7.2",
19
+    "dayjs": "^1.8.25",
20 20
     "js-cookie": "^2.2.0",
21 21
     "prop-types": "^15.6.2",
22 22
     "react": "^16.4.1",

+ 8
- 4
src/App.js View File

@@ -190,13 +190,13 @@ class App extends Component {
190 190
   /**
191 191
    * 获取评论列表
192 192
    */
193
-  sGetComment({ page = 1 } = {}) {
193
+  sGetComment({ page = 1, filterSpeak = 0 } = {}) {
194 194
     const { pageType } = this.props;
195 195
     this.handleChangeLoading("sGetComment", true);
196 196
     const { API, type, businessId, limit } = this.props;
197 197
     this.axios
198 198
       .get(
199
-        `${API}/comments?type=${type}&business_id=${businessId}&page=${page}&limit=${limit}`
199
+        `${API}/comments?type=${type}&business_id=${businessId}&is_speak=${filterSpeak}&page=${page}&limit=${limit}`
200 200
       )
201 201
       .then(response => {
202 202
         const { list, page, total } = response.data;
@@ -594,6 +594,7 @@ class App extends Component {
594 594
       sUpdateComment: this.sUpdateComment,
595 595
       handleEdit: this.handleEdit
596 596
     };
597
+
597 598
     return (
598 599
       this.state.initDone && (
599 600
         <CommentContext.Provider value={value}>
@@ -617,6 +618,7 @@ class App extends Component {
617 618
               userId={this.state.content.user_id}
618 619
               content={this.state.content}
619 620
               handleClose={this.handleClose}
621
+              preRenderValue={this.props.preRenderValue}
620 622
             />
621 623
           )}
622 624
         </CommentContext.Provider>
@@ -647,7 +649,8 @@ App.propTypes = {
647 649
   onUpdateComment: PropTypes.func,
648 650
   locales: PropTypes.string, //  传入的语言环境, en-US/zh-CN
649 651
   onCountChange: PropTypes.func, // 评论数量变更时的回调
650
-  onCommentFail: PropTypes.func // 评论失败时的回调
652
+  onCommentFail: PropTypes.func, // 评论失败时的回调
653
+  preRenderValue: PropTypes.func // 编辑器渲染前对值需要做的工作
651 654
 };
652 655
 
653 656
 App.defaultProps = {
@@ -673,7 +676,8 @@ App.defaultProps = {
673 676
   onUpdateComment: () => {},
674 677
   onBeforeUpdateComment: () => {},
675 678
   onCountChange: () => {},
676
-  onCommentFail: () => {}
679
+  onCommentFail: () => {},
680
+  preRenderValue: v => v
677 681
 };
678 682
 
679 683
 export { Editor, RenderText };

BIN
src/assert/btn_audio_play.png View File


BIN
src/assert/loading.gif View File


+ 144
- 0
src/components/AudioPlayer/index.js View File

@@ -0,0 +1,144 @@
1
+import React from "react";
2
+import { Slider, Icon } from "antd";
3
+import dayjs from "dayjs";
4
+import durationPlugin from "dayjs/plugin/duration";
5
+import utcPlugin from "dayjs/plugin/utc";
6
+import "./index.less";
7
+import iconLoading from "../../assert/loading.gif";
8
+import btnAudioPlay from "../../assert/btn_audio_play.png";
9
+
10
+dayjs.extend(durationPlugin);
11
+dayjs.extend(utcPlugin);
12
+
13
+class AudioPlayer extends React.Component {
14
+  constructor(props) {
15
+    super(props);
16
+    this.state = {
17
+      duration: 0,
18
+      currentDuration: 0,
19
+      isPlaying: false,
20
+      isLoading: false
21
+    };
22
+  }
23
+
24
+  componentDidMount() {
25
+    if (this.player) {
26
+      const { src } = this.props;
27
+      this.player.oncanplay = event =>
28
+        this.setState({ duration: event.target.duration });
29
+      this.player.onended = () =>
30
+        this.setState({ isPlaying: false, currentDuration: 0 });
31
+      this.player.onpause = () =>
32
+        this.setState({ isPlaying: false, isLoading: false });
33
+      this.player.onplay = () =>
34
+        this.setState({ isPlaying: false, isLoading: true });
35
+      this.player.onplaying = () =>
36
+        this.setState({ isPlaying: true, isLoading: false });
37
+      this.player.ontimeupdate = event =>
38
+        this.setState({ currentDuration: event.target.currentTime });
39
+      this.player.src = src;
40
+    }
41
+  }
42
+
43
+  componentDidUpdate(prevProps) {
44
+    if (this.props.src !== prevProps.src) {
45
+      this.player.oncanplay = event =>
46
+        this.setState({ duration: event.target.duration });
47
+      this.player.onended = () =>
48
+        this.setState({ isPlaying: false, currentDuration: 0 });
49
+      this.player.onpause = () =>
50
+        this.setState({ isPlaying: false, isLoading: false });
51
+      this.player.onplay = () =>
52
+        this.setState({ isPlaying: false, isLoading: true });
53
+      this.player.onplaying = () =>
54
+        this.setState({ isPlaying: true, isLoading: false });
55
+      this.player.ontimeupdate = event =>
56
+        this.setState({ currentDuration: event.target.currentTime });
57
+      this.player.src = this.props.src;
58
+    }
59
+  }
60
+
61
+  clickPlayOrPause(isPlaying) {
62
+    if (!this.player) {
63
+      return;
64
+    }
65
+    if (isPlaying) {
66
+      this.player.pause();
67
+    } else {
68
+      // 如果是播放,先暂停其他的播放
69
+      const playerList = document.getElementsByTagName("audio");
70
+      if (playerList) {
71
+        Array.from(playerList).forEach(player => {
72
+          if (!player.paused) {
73
+            player.pause();
74
+          }
75
+        });
76
+      }
77
+      this.player.play();
78
+    }
79
+  }
80
+
81
+  getPlayIcon() {
82
+    const { isPlaying, isLoading } = this.state;
83
+    let playIconElem;
84
+
85
+    if (isLoading) {
86
+      playIconElem = (
87
+        <img className="icon-loading" src={iconLoading} alt="iconLoading" />
88
+      );
89
+    } else if (isPlaying) {
90
+      playIconElem = <Icon className="pause" type="pause" />;
91
+    } else {
92
+      playIconElem = (
93
+        <img className="icon-loading" src={btnAudioPlay} alt="iconPlaying" />
94
+      );
95
+    }
96
+    return playIconElem;
97
+  }
98
+
99
+  render() {
100
+    const { currentDuration, isPlaying, duration } = this.state;
101
+    return (
102
+      <div className="comment-item-speak-audio-container">
103
+        {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
104
+        <audio
105
+          ref={ref => {
106
+            this.player = ref;
107
+          }}
108
+          style={{ display: "none" }}
109
+        />
110
+        <span
111
+          className="icon"
112
+          onClick={() => {
113
+            this.clickPlayOrPause(isPlaying);
114
+          }}
115
+        >
116
+          {this.getPlayIcon()}
117
+        </span>
118
+        <Slider
119
+          step={0.001}
120
+          className="slider"
121
+          tooltipVisible={false}
122
+          value={currentDuration}
123
+          max={duration}
124
+          onChange={value => {
125
+            if (this.player) {
126
+              this.player.currentTime = value;
127
+            }
128
+          }}
129
+        />
130
+        <span className="time">
131
+          {dayjs
132
+            .utc(dayjs.duration(currentDuration, "seconds").asMilliseconds())
133
+            .format("mm:ss")}
134
+          /
135
+          {dayjs
136
+            .utc(dayjs.duration(duration, "seconds").asMilliseconds())
137
+            .format("mm:ss")}
138
+        </span>
139
+      </div>
140
+    );
141
+  }
142
+}
143
+
144
+export default AudioPlayer;

+ 65
- 0
src/components/AudioPlayer/index.less View File

@@ -0,0 +1,65 @@
1
+.comment-item-speak-audio-container {
2
+  background-color: #f5f5f5;
3
+  border: 1px solid rgba(210, 210, 210, 1);
4
+  border-radius: 4px;
5
+  display: flex;
6
+  align-items: center;
7
+  height: 100%;
8
+
9
+  .icon {
10
+    cursor: pointer;
11
+    width: 28px;
12
+    height: 28px;
13
+    line-height: 28px;
14
+    text-align: center;
15
+    border-radius: 14px;
16
+    background-color: #fff;
17
+    color: #71c135;
18
+    margin-left: 12px;
19
+    font-size: 14px;
20
+  }
21
+
22
+  .icon-loading {
23
+    width: 100%;
24
+    height: 100%;
25
+  }
26
+
27
+  .slider {
28
+    margin: 0 0 0 16px;
29
+    width: 195px;
30
+
31
+    :global {
32
+      .ant-slider-rail {
33
+        background-color: #dfdfdf;
34
+      }
35
+    }
36
+  }
37
+
38
+  .time {
39
+    margin-left: 14px;
40
+    color: #848484;
41
+    font-size: 12px;
42
+  }
43
+}
44
+
45
+@media screen and (max-width: 616px) and (min-width: 449px) {
46
+  .comment-item-speak-audio-container {
47
+    .slider {
48
+      width: 195px;
49
+    }
50
+  }
51
+}
52
+@media screen and (max-width: 449px) and (min-width: 365px) {
53
+  .comment-item-speak-audio-container {
54
+    .slider {
55
+      width: 114px;
56
+    }
57
+  }
58
+}
59
+@media screen and (max-width: 365px) {
60
+  .comment-item-speak-audio-container {
61
+    .slider {
62
+      width: 60px;
63
+    }
64
+  }
65
+}

+ 12
- 0
src/components/CommentList/index.css View File

@@ -17,3 +17,15 @@
17 17
 .comment-list-pagination {
18 18
   text-align: center;
19 19
 }
20
+
21
+.comment-list-filter-speak {
22
+  margin-left: 20px;
23
+  font-size: 14px;
24
+  color: rgba(93, 93, 93, 0.65);
25
+}
26
+
27
+@media screen and (max-width: 365px) {
28
+  .comment-list-filter-speak {
29
+    float: right;
30
+  }
31
+}

+ 35
- 6
src/components/CommentList/index.js View File

@@ -1,5 +1,5 @@
1 1
 import React, { Component } from "react";
2
-import { Spin, Pagination } from "antd";
2
+import { Spin, Pagination, Checkbox } from "antd";
3 3
 import intl from "react-intl-universal";
4 4
 import Comment from "../../Comment";
5 5
 import CommentBox from "../CommentBox";
@@ -8,7 +8,9 @@ import "./index.css";
8 8
 class CommentList extends Component {
9 9
   constructor(props) {
10 10
     super(props);
11
-    this.state = {};
11
+    this.state = {
12
+      filterSpeak: 0
13
+    };
12 14
   }
13 15
 
14 16
   componentWillMount() {
@@ -27,6 +29,7 @@ class CommentList extends Component {
27 29
       onPageChange,
28 30
       onGetMoreBtnClick
29 31
     } = this.props.app;
32
+    const { filterSpeak } = this.state;
30 33
     if (pageType === "slice") {
31 34
       // 截断多余评论,通过点击查看更多跳转
32 35
       return (
@@ -40,7 +43,7 @@ class CommentList extends Component {
40 43
           <div
41 44
             className="comment-list-show-more"
42 45
             onClick={() => {
43
-              sGetComment({ page: page + 1 });
46
+              sGetComment({ page: page + 1, filterSpeak });
44 47
               onPageChange(page + 1);
45 48
             }}
46 49
           >
@@ -58,7 +61,7 @@ class CommentList extends Component {
58 61
             current={page}
59 62
             total={total}
60 63
             onChange={p => {
61
-              sGetComment({ page: p });
64
+              sGetComment({ page: p, filterSpeak });
62 65
               onPageChange(p);
63 66
             }}
64 67
           />
@@ -68,7 +71,14 @@ class CommentList extends Component {
68 71
   }
69 72
 
70 73
   render() {
71
-    const { list, total, loading } = this.props.app;
74
+    const {
75
+      list,
76
+      total,
77
+      loading,
78
+      isSpeak,
79
+      sGetComment,
80
+      onPageChange
81
+    } = this.props.app;
72 82
 
73 83
     const spinning = Boolean(
74 84
       loading.sGetComment || loading.sCommentFavor || loading.sReplyFavor
@@ -77,7 +87,26 @@ class CommentList extends Component {
77 87
       <div>
78 88
         <Spin spinning={spinning}>
79 89
           {/* <div>共 {total} 条评论</div> */}
80
-          <div>{intl.get("comment.totalComment", { total })}</div>
90
+          <div>
91
+            {intl.get("comment.totalComment", { total })}
92
+            {isSpeak && (
93
+              <Checkbox
94
+                className="comment-list-filter-speak"
95
+                onChange={e => {
96
+                  this.setState({
97
+                    filterSpeak: e.target.checked ? 1 : 0
98
+                  });
99
+                  sGetComment({
100
+                    page: 1,
101
+                    filterSpeak: e.target.checked ? 1 : 0
102
+                  });
103
+                  onPageChange(1);
104
+                }}
105
+              >
106
+                {intl.get("comment.filterSpeak")}
107
+              </Checkbox>
108
+            )}
109
+          </div>
81 110
           {list.map(item => (
82 111
             <CommentBox
83 112
               content={item}

+ 27
- 0
src/components/ContentItem/index.css View File

@@ -33,6 +33,21 @@
33 33
   word-break: break-all;
34 34
 }
35 35
 
36
+.comment-item-speak {
37
+  margin-top: 4px;
38
+}
39
+
40
+.comment-item-speak-message {
41
+  font-size: 14px;
42
+  color: #71c135;
43
+}
44
+
45
+.comment-item-speak-audio-wrapper {
46
+  margin-top: 8px;
47
+  width: 350px;
48
+  height: 44px;
49
+}
50
+
36 51
 .comment-item-bottom {
37 52
   display: flex;
38 53
   justify-content: flex-end;
@@ -156,6 +171,10 @@
156 171
     width: 85%;
157 172
     margin-left: 10px;
158 173
   }
174
+
175
+  .comment-item-speak-audio-wrapper {
176
+    width: 350px;
177
+  }
159 178
 }
160 179
 
161 180
 @media screen and (max-width: 449px) and (min-width: 365px) {
@@ -164,6 +183,10 @@
164 183
     width: 80%;
165 184
     margin-left: 10px;
166 185
   }
186
+
187
+  .comment-item-speak-audio-wrapper {
188
+    width: 266px;
189
+  }
167 190
 }
168 191
 
169 192
 @media screen and (max-width: 365px) {
@@ -172,6 +195,10 @@
172 195
     width: 75%;
173 196
     margin-left: 10px;
174 197
   }
198
+
199
+  .comment-item-speak-audio-wrapper {
200
+    width: 218px;
201
+  }
175 202
 }
176 203
 
177 204
 @media (max-width: 575px) {

+ 78
- 59
src/components/ContentItem/index.js View File

@@ -14,6 +14,7 @@ import { renderContent } from "../../helper";
14 14
 import { IMAGE_SPLIT } from "../../constant";
15 15
 import "./index.css";
16 16
 import ImagePreviewer from "../ImagePreviewer/ImagePreviewer";
17
+import AudioPlayer from "../AudioPlayer";
17 18
 
18 19
 // dayjs.locale("zh-cn");
19 20
 dayjs.extend(relativeTime);
@@ -103,6 +104,7 @@ class CommentItem extends Component {
103 104
       user_id,
104 105
       page
105 106
     } = this.props;
107
+    const { medias, is_speak: isSpeak } = content;
106 108
     const { locale, showHoverCard, showEdit } = this.props.app;
107 109
     const { showInput } = this.state;
108 110
     let newContent = content.content;
@@ -206,54 +208,66 @@ class CommentItem extends Component {
206 208
               )
207 209
             }}
208 210
           />
211
+
212
+          {isSpeak && (
213
+            <div className="comment-item-speak">
214
+              <span className="comment-item-speak-message">
215
+                [{intl.get("comment.speakComment")}]
216
+              </span>
217
+              <div className="comment-item-speak-audio-wrapper">
218
+                <AudioPlayer src={medias && medias[0] && medias[0].url} />
219
+              </div>
220
+            </div>
221
+          )}
209 222
           {// image为空时不渲染comment-item-image
210
-          imageList.length > 0 && imageList[0] !== "" && (
211
-            <div className="comment-item-image">
212
-              {!this.state.showPreviewer &&
213
-                imgs.map((item, index) => {
214
-                  if (item.type === "divider") {
223
+          imageList.length > 0 &&
224
+            imageList[0] !== "" && (
225
+              <div className="comment-item-image">
226
+                {!this.state.showPreviewer &&
227
+                  imgs.map((item, index) => {
228
+                    if (item.type === "divider") {
229
+                      return (
230
+                        <div className="comment-item-image-wrapper" key={index}>
231
+                          <div className="comment-img-divider" />
232
+                          {/* <img src={item} alt={item} className="comment-img" /> */}
233
+                        </div>
234
+                      );
235
+                    }
215 236
                     return (
216
-                      <div className="comment-item-image-wrapper" key={index}>
217
-                        <div className="comment-img-divider" />
237
+                      <div
238
+                        className="comment-item-image-wrapper"
239
+                        key={index}
240
+                        onClick={() => {
241
+                          let i = index;
242
+                          if (needClear) {
243
+                            if (index > 3) {
244
+                              i -= 1;
245
+                            }
246
+                            if (index > 7) {
247
+                              i -= 1;
248
+                            }
249
+                          }
250
+                          this.showPreviewer(i);
251
+                        }}
252
+                      >
253
+                        <div
254
+                          style={{ backgroundImage: `url(${item})` }}
255
+                          className="comment-img-thumbnail"
256
+                        />
218 257
                         {/* <img src={item} alt={item} className="comment-img" /> */}
219 258
                       </div>
220 259
                     );
221
-                  }
222
-                  return (
223
-                    <div
224
-                      className="comment-item-image-wrapper"
225
-                      key={index}
226
-                      onClick={() => {
227
-                        let i = index;
228
-                        if (needClear) {
229
-                          if (index > 3) {
230
-                            i -= 1;
231
-                          }
232
-                          if (index > 7) {
233
-                            i -= 1;
234
-                          }
235
-                        }
236
-                        this.showPreviewer(i);
237
-                      }}
238
-                    >
239
-                      <div
240
-                        style={{ backgroundImage: `url(${item})` }}
241
-                        className="comment-img-thumbnail"
242
-                      />
243
-                      {/* <img src={item} alt={item} className="comment-img" /> */}
244
-                    </div>
245
-                  );
246
-                })}
247
-              {this.state.showPreviewer && (
248
-                <ImagePreviewer
249
-                  list={imageList}
250
-                  index={this.state.previewerIndex}
251
-                  onFold={this.hidePreviewer}
252
-                />
253
-              )}
254
-              <div className="clearfix" />
255
-            </div>
256
-          )}
260
+                  })}
261
+                {this.state.showPreviewer && (
262
+                  <ImagePreviewer
263
+                    list={imageList}
264
+                    index={this.state.previewerIndex}
265
+                    onFold={this.hidePreviewer}
266
+                  />
267
+                )}
268
+                <div className="clearfix" />
269
+              </div>
270
+            )}
257 271
           <div className="comment-item-bottom">
258 272
             {content.reply_count ? (
259 273
               <div>
@@ -264,21 +278,23 @@ class CommentItem extends Component {
264 278
                 </a>
265 279
               </div>
266 280
             ) : null}
267
-            {showEdit && app.userId === content.user_id && (
268
-              <i
269
-                className="comment-item-edit"
270
-                onClick={() =>
271
-                  this.props.app.handleEdit({
272
-                    action,
273
-                    replyId,
274
-                    commentId,
275
-                    userId: content.user_id,
276
-                    content,
277
-                    replyPage: page
278
-                  })
279
-                }
280
-              />
281
-            )}
281
+            {showEdit &&
282
+              !isSpeak &&
283
+              app.userId === content.user_id && (
284
+                <i
285
+                  className="comment-item-edit"
286
+                  onClick={() =>
287
+                    this.props.app.handleEdit({
288
+                      action,
289
+                      replyId,
290
+                      commentId,
291
+                      userId: content.user_id,
292
+                      content,
293
+                      replyPage: page
294
+                    })
295
+                  }
296
+                />
297
+              )}
282 298
             {app.userId === content.user_id && (
283 299
               <Popconfirm
284 300
                 // title="确定要删除吗?"
@@ -318,7 +334,10 @@ class CommentItem extends Component {
318 334
                 }
319 335
               />
320 336
             </div>
321
-            <span>&nbsp;{content.favor_count}</span>
337
+            <span>
338
+              &nbsp;
339
+              {content.favor_count}
340
+            </span>
322 341
             <div
323 342
               onClick={this.handleToggleInput}
324 343
               className="comment-item-reply"

+ 4
- 1
src/components/EditComment/EditComment.js View File

@@ -111,7 +111,10 @@ class EditComment extends React.Component {
111 111
               commentId={this.props.commentId}
112 112
               userId={this.props.content.user_id}
113 113
               fileList={this.state.fileList}
114
-              value={this.state.value}
114
+              value={
115
+                this.props.preRenderValue &&
116
+                this.props.preRenderValue(this.state.value)
117
+              }
115 118
               onChange={value => {
116 119
                 this.setState({
117 120
                   value

+ 25
- 0
src/components/Editor/index.css View File

@@ -130,3 +130,28 @@
130 130
     height: 28px;
131 131
   }
132 132
 }
133
+
134
+.comment-img-popover {
135
+  /* 一行显示3张图 */
136
+  width: 336px;
137
+  min-height: 100px;
138
+  margin: 0 auto;
139
+}
140
+
141
+.comment-img-title {
142
+  margin: 5px auto;
143
+}
144
+.comment-img-title-counter {
145
+  color: #666;
146
+  font-weight: 400;
147
+}
148
+
149
+@media (max-width: 575px) {
150
+  .comment-img-popover {
151
+    /* 一行显示2张图 */
152
+    width: 224px;
153
+  }
154
+  .comment-img-title-counter {
155
+    display: none;
156
+  }
157
+}

+ 82
- 29
src/components/Editor/index.js View File

@@ -1,15 +1,16 @@
1
-import React, { Fragment } from "react";
1
+import dayjs from "dayjs";
2
+import shortid from "shortid";
2 3
 import PropTypes from "prop-types";
3
-import { Icon, Button, Popover, Input, message } from "antd";
4 4
 import classnames from "classnames";
5
+import React, { Fragment } from "react";
5 6
 import intl from "react-intl-universal";
6
-import dayjs from "dayjs";
7
-import shortid from "shortid";
8
-import { OSS_LINK } from "../../constant";
9
-import { isFunction } from "../../helper";
10
-import Upload from "./Upload";
7
+import { Icon, Button, Popover, Input, message } from "antd";
11 8
 import Emoji from "./Emoji";
9
+import Upload from "./Upload";
12 10
 import Comment from "../../Comment";
11
+import { isMobile } from "./../../utils";
12
+import { OSS_LINK } from "../../constant";
13
+import { isFunction } from "../../helper";
13 14
 import {
14 15
   OSS_ENDPOINT,
15 16
   OSS_BUCKET,
@@ -58,7 +59,7 @@ class Editor extends React.Component {
58 59
       value: props.value || "", // 编辑器里面的值
59 60
       fileList: props.fileList || [], // 图片列表
60 61
       fileMap: {}, // 已经上传的图片路径和 uid 的映射 { uid: path }
61
-      uploadVisible: false
62
+      uploadVisible: false // 上传图片弹窗是否可视
62 63
     };
63 64
     this.handleChange = this.handleChange.bind(this);
64 65
     this.handleClickEmoji = this.handleClickEmoji.bind(this);
@@ -69,10 +70,12 @@ class Editor extends React.Component {
69 70
     this.handlePaste = this.handlePaste.bind(this);
70 71
     this.resetState = this.resetState.bind(this);
71 72
     this.handleEmojiScroll = this.handleEmojiScroll.bind(this);
73
+    this.handlePressEnter = this.handlePressEnter.bind(this);
74
+    this.invokeFileListChange = this.invokeFileListChange.bind(this);
72 75
   }
73 76
 
74 77
   componentDidMount() {
75
-    const { app, onRef } = this.props;
78
+    const { app, onRef, uploadDefaultShow } = this.props;
76 79
     if (
77 80
       app.currentUser &&
78 81
       (app.currentUser.user_id > 0 || app.currentUser.id > 0)
@@ -82,6 +85,14 @@ class Editor extends React.Component {
82 85
     if (isFunction(onRef)) {
83 86
       onRef(this);
84 87
     }
88
+    if (uploadDefaultShow) {
89
+      const uploadFileList = this.props.fileList || this.state.fileList;
90
+      if (uploadFileList.length > 0) {
91
+        this.setState({
92
+          uploadVisible: true,
93
+        });
94
+      }
95
+    }
85 96
   }
86 97
 
87 98
   handleEmojiScroll(e) {
@@ -130,8 +141,7 @@ class Editor extends React.Component {
130 141
     if (fileList.length > this.props.maxUpload) {
131 142
       list = fileList.slice(0, this.props.maxUpload);
132 143
     }
133
-    this.props.handleChangeFileList(list);
134
-    this.setState({ fileList: list });
144
+    this.invokeFileListChange(list);
135 145
   }
136 146
 
137 147
   /**
@@ -160,8 +170,31 @@ class Editor extends React.Component {
160 170
       }
161 171
       return item;
162 172
     });
163
-    this.props.handleChangeFileList(fileList);
164
-    this.setState({ fileMap, fileList });
173
+    this.setState({ fileMap });
174
+    this.invokeFileListChange(fileList);
175
+  }
176
+
177
+  /**
178
+   *  **统一处理fileList的修改**
179
+   *  1. upload
180
+   *  2. paste
181
+   *  PS: 移动端需要做额外操作
182
+   *  -- evo 20200223
183
+   */
184
+  invokeFileListChange(fileList) {
185
+    const { limitOne, handleChangeFileList } = this.props;
186
+    handleChangeFileList(fileList);
187
+    this.setState({ fileList });
188
+    if (limitOne && isMobile) {
189
+      const file = fileList[0];
190
+      if (
191
+        file &&
192
+        file.status === "done" &&
193
+        !file.thumbUrl.includes("data:image")
194
+      ) {
195
+        this.setState({ uploadVisible: false });
196
+      }
197
+    }
165 198
   }
166 199
 
167 200
   /**
@@ -197,10 +230,7 @@ class Editor extends React.Component {
197 230
             type: file.type,
198 231
             uid: new Date().valueOf()
199 232
           });
200
-          this.props.handleChangeFileList(fileList);
201
-          this.setState({
202
-            fileList
203
-          });
233
+          this.invokeFileListChange(fileList);
204 234
         })
205 235
         .catch(e => {
206 236
           const msg = e.message || ERROR_DEFAULT;
@@ -294,6 +324,22 @@ class Editor extends React.Component {
294 324
     return true;
295 325
   }
296 326
 
327
+  /**
328
+   *  **处理Enter事件**
329
+   *  1. `allowEnterSubmit`为true时enter触发submit事件
330
+   *  2. `e.preventDefault`为了防止enter事件后仍触发换行
331
+   *  3. enter事件开启后,仍可以用`shift + enter`触发换行
332
+   *  -- evo 20200222
333
+   */
334
+  handlePressEnter(e) {
335
+    if (this.props.allowEnterSubmit) {
336
+      if (!e.shiftKey) {
337
+        e.preventDefault();
338
+        this.handleSubmit();
339
+      }
340
+    }
341
+  }
342
+
297 343
   render() {
298 344
     const {
299 345
       value,
@@ -326,6 +372,7 @@ class Editor extends React.Component {
326 372
     const isLogin =
327 373
       app.currentUser &&
328 374
       (app.currentUser.user_id > 0 || app.currentUser.id > 0);
375
+
329 376
     return (
330 377
       <div className="comment-editor-container" onPaste={this.handlePaste}>
331 378
         {isLogin ? (
@@ -339,10 +386,13 @@ class Editor extends React.Component {
339 386
             <div className="comment-editor">
340 387
               <TextArea
341 388
                 value={inputValue}
342
-                onChange={e => this.handleChange(e.target.value)}
389
+                onChange={e => {
390
+                  this.handleChange(e.target.value);
391
+                }}
343 392
                 rows={rows}
344 393
                 placeholder={placeholder}
345 394
                 autoFocus={autoFocus}
395
+                onPressEnter={this.handlePressEnter}
346 396
               />
347 397
 
348 398
               <div className="comment-toolbar">
@@ -390,13 +440,7 @@ class Editor extends React.Component {
390 440
                         });
391 441
                       }}
392 442
                       content={
393
-                        <div
394
-                          style={{
395
-                            width: 336, // 一行显示3张
396
-                            minHeight: 100,
397
-                            margin: "0 auto"
398
-                          }}
399
-                        >
443
+                        <div className="comment-img-popover">
400 444
                           <Upload
401 445
                             onRef={node => (this.uploadRef = node)}
402 446
                             multiple={multiple}
@@ -411,11 +455,11 @@ class Editor extends React.Component {
411 455
                         </div>
412 456
                       }
413 457
                       title={
414
-                        <div style={{ margin: "5px auto" }}>
458
+                        <div className="comment-img-title">
415 459
                           <span>
416 460
                             {intl.get("editor.uploadTip")}
417 461
                             {maxUpload >= 2 ? (
418
-                              <span style={{ color: "#666", fontWeight: 400 }}>
462
+                              <span className="comment-img-title-counter">
419 463
                                 {intl.get("editor.uploadCount", {
420 464
                                   count: maxUpload - uploadFileList.length
421 465
                                 })}
@@ -503,9 +547,14 @@ Editor.propTypes = {
503 547
   button: PropTypes.node,
504 548
   emojiToolIcon: PropTypes.node,
505 549
   imageToolIcon: PropTypes.node,
550
+  uploadDefaultShow: PropTypes.bool,
506 551
   showError: PropTypes.bool,
507 552
   onError: PropTypes.func,
508
-  maxLength: PropTypes.number
553
+  maxLength: PropTypes.number,
554
+  // Enter事件相关
555
+  allowEnterSubmit: PropTypes.bool,
556
+  // 私信仅允许选中一个,此处可以优化为通用的limit
557
+  limitOne: PropTypes.bool
509 558
 };
510 559
 
511 560
 Editor.defaultProps = {
@@ -513,6 +562,7 @@ Editor.defaultProps = {
513 562
   // placeholder: "说点什么吧",
514 563
   showEmoji: true,
515 564
   showUpload: true,
565
+  uploadDefaultShow: true,
516 566
   multiple: true,
517 567
   emojiPopoverPlacement: "bottomLeft",
518 568
   closeUploadWhenBlur: false,
@@ -525,7 +575,10 @@ Editor.defaultProps = {
525 575
   showError: true,
526 576
   maxLength: 5000,
527 577
   app: {},
528
-  handleChangeFileList: () => {}
578
+  handleChangeFileList: () => {},
579
+  // Enter事件相关
580
+  allowEnterSubmit: false,
581
+  limitOne: false
529 582
 };
530 583
 
531 584
 export default Comment(Editor);

+ 9
- 7
src/index.js View File

@@ -35,16 +35,15 @@ class Index extends React.Component {
35 35
           fileList={this.state.fileList}
36 36
           value={this.state.value}
37 37
           onChange={value => {
38
-            this.setState({
39
-              value
40
-            });
38
+            this.setState({ value });
41 39
           }}
42 40
           handleChangeFileList={fileList => {
43
-            console.log("----", fileList);
44 41
             this.setState({
45 42
               fileList
46 43
             });
47 44
           }}
45
+          allowEnterSubmit
46
+          limitOne
48 47
         />
49 48
       </App>
50 49
     );
@@ -88,8 +87,8 @@ window.renderComment = renderComment;
88 87
 if (process.env.NODE_ENV !== "production") {
89 88
   renderComment({
90 89
     id: "root-comment",
91
-    type: 1,
92
-    businessId: "test",
90
+    type: 3,
91
+    businessId: "5ea8320dedd68200018e733d",
93 92
     businessUserId: 4,
94 93
     userId: 58297,
95 94
     currentUser: {
@@ -142,7 +141,7 @@ if (process.env.NODE_ENV !== "production") {
142 141
       });
143 142
     },
144 143
     onCountChange: c => {
145
-      console.log(c);
144
+      // console.log(c);
146 145
     },
147 146
     onDelete: (type, data) => {
148 147
       console.log(type, data);
@@ -159,6 +158,9 @@ if (process.env.NODE_ENV !== "production") {
159 158
     sendMessage: id => {
160 159
       console.log("sendMessage", id);
161 160
     },
161
+    preRenderValue: value => {
162
+      return `${value}`;
163
+    },
162 164
     editorProps: {
163 165
       onCommentSuccess: data => {
164 166
         console.log(data);

+ 2
- 0
src/lang/en-US.js View File

@@ -12,6 +12,8 @@ const USdata = {
12 12
     "Total {total, plural, =1 {one comment} other {# comments}}",
13 13
   "comment.reply": "Reply",
14 14
   "comment.moreComment": "More comments",
15
+  "comment.filterSpeak": "Show imitations only",
16
+  "comment.speakComment": "Imitation audio",
15 17
 
16 18
   "reply.totalReply": "Total {total, plural, =1 {one reply} other {# replies}}",
17 19
   "reply.moreReply": "More replies",

+ 2
- 0
src/lang/zh-CN.js View File

@@ -11,6 +11,8 @@ const CNdata = {
11 11
   "comment.totalComment": "共{total}条评论",
12 12
   "comment.reply": "回复",
13 13
   "comment.moreComment": "更多评论",
14
+  "comment.filterSpeak": "只显示跟读",
15
+  "comment.speakComment": "跟读语音",
14 16
 
15 17
   "reply.totalReply": "共{total}条回复",
16 18
   "reply.moreReply": "更多回复",

+ 5
- 0
src/utils.js View File

@@ -0,0 +1,5 @@
1
+/**
2
+ * 575: 为了和media query中的判断保持一致
3
+ */
4
+export const isMobile =
5
+  typeof window.orientation === "number" && document.body.offsetWidth < 575;