@subiz/widget-v4 4.1.210 → 4.1.212

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +21 -2
  2. package/assets/img/dot_chat.svg +3 -0
  3. package/assets/less/chatbox.less +9 -0
  4. package/assets/less/form.less +10 -0
  5. package/assets/less/message.less +114 -13
  6. package/assets/less/widget.less +15 -18
  7. package/compiled_src/call/css.js +1 -1
  8. package/compiled_src/chatbox/Buttons.js +7 -4
  9. package/compiled_src/chatbox/ChatButton.js +127 -29
  10. package/compiled_src/chatbox/Event.js +37 -5
  11. package/compiled_src/chatbox/Events.js +348 -129
  12. package/compiled_src/chatbox/FormAttachment.js +1 -1
  13. package/compiled_src/chatbox/Header.js +7 -2
  14. package/compiled_src/chatbox/InfoMessage.js +1 -1
  15. package/compiled_src/chatbox/MessageInput.js +24 -9
  16. package/compiled_src/chatbox/OrderAttachments.js +13 -13
  17. package/compiled_src/chatbox/QuoteMessage.js +3 -2
  18. package/compiled_src/chatbox/TextMessage.js +22 -18
  19. package/compiled_src/chatbox/Widget.js +200 -129
  20. package/compiled_src/chatbox/block.js +39 -29
  21. package/compiled_src/chatbox/conversation-history.js +44 -0
  22. package/compiled_src/chatbox/event-layout.js +59 -0
  23. package/compiled_src/chatbox/greeting.js +32 -0
  24. package/compiled_src/chatbox/main.js +3 -0
  25. package/compiled_src/common.js +13 -9
  26. package/compiled_src/form/css.js +1 -1
  27. package/compiled_src/lib.js +22 -32
  28. package/compiled_src/notif/css.js +1 -1
  29. package/compiled_src/plugin.js +11 -18
  30. package/compiled_src/popup/css.js +1 -1
  31. package/compiled_src/popup/popup.js +1 -1
  32. package/compiled_src/store/api.js +0 -2
  33. package/compiled_src/store/api_fake.js +1 -1
  34. package/compiled_src/store/attachments.js +28 -0
  35. package/compiled_src/store/chain.js +24 -10
  36. package/compiled_src/store/kv.js +9 -13
  37. package/compiled_src/store/lsdb.js +1 -1
  38. package/compiled_src/store/store.js +356 -117
  39. package/compiled_src/util/common.js +48 -41
  40. package/compiled_src/util/event.js +15 -0
  41. package/compiled_src/util/pubsub.js +28 -13
  42. package/compiled_src/util/url.js +18 -0
  43. package/dist/127.js +13 -13
  44. package/dist/132.js +1 -1
  45. package/dist/1a0f6f3ff1a.127.js +1 -0
  46. package/dist/{1a0d80f6e72.132.js → 1a0f6f3ff1a.132.js} +1 -1
  47. package/dist/{1a0d80f6e72.27.js → 1a0f6f3ff1a.27.js} +1 -1
  48. package/dist/1a0f6f3ff1a.278.js +1 -0
  49. package/dist/{1a0d80f6e72.339.js → 1a0f6f3ff1a.339.js} +1 -1
  50. package/dist/1a0f6f3ff1a.459.js +1 -0
  51. package/dist/1a0f6f3ff1a.app.js +1 -0
  52. package/dist/{1a0d80f6e72.chat.html → 1a0f6f3ff1a.chat.html} +1 -1
  53. package/dist/1a0f6f3ff1a.chat.js +1 -0
  54. package/dist/{1a0d80f6e72.form.html → 1a0f6f3ff1a.form.html} +1 -1
  55. package/dist/1a0f6f3ff1a.form.js +1 -0
  56. package/dist/{1a0d80f6e72.index.html → 1a0f6f3ff1a.index.html} +1 -1
  57. package/dist/{1a0d80f6e72.library.html → 1a0f6f3ff1a.library.html} +1 -1
  58. package/dist/1a0f6f3ff1a.library.js +1 -0
  59. package/dist/{1a0d80f6e72.preview.html → 1a0f6f3ff1a.preview.html} +1 -1
  60. package/dist/1a0f6f3ff1a.preview.js +1 -0
  61. package/dist/1a0f6f3ff1a.webview.js +1 -0
  62. package/dist/27.js +2 -2
  63. package/dist/459.js +2 -2
  64. package/dist/HASH +1 -1
  65. package/dist/assets/img/dot_chat.335fd8a.svg +3 -0
  66. package/dist/screenshoot.js +1523 -619
  67. package/package.json +12 -5
  68. package/src/chatbox/Buttons.js +5 -4
  69. package/src/chatbox/ChatButton.js +133 -39
  70. package/src/chatbox/Event.js +38 -5
  71. package/src/chatbox/Events.js +306 -134
  72. package/src/chatbox/FormAttachment.js +1 -1
  73. package/src/chatbox/Header.js +5 -4
  74. package/src/chatbox/InfoMessage.js +1 -1
  75. package/src/chatbox/MessageInput.js +15 -10
  76. package/src/chatbox/OrderAttachments.js +13 -13
  77. package/src/chatbox/QuoteMessage.js +3 -2
  78. package/src/chatbox/TextMessage.js +21 -20
  79. package/src/chatbox/Widget.js +150 -150
  80. package/src/chatbox/block.js +29 -27
  81. package/src/chatbox/conversation-history.js +37 -0
  82. package/src/chatbox/event-layout.js +72 -0
  83. package/src/chatbox/greeting.js +13 -0
  84. package/src/chatbox/main.js +3 -0
  85. package/src/common.js +15 -9
  86. package/src/lib.js +22 -35
  87. package/src/plugin.js +11 -13
  88. package/src/popup/popup.js +1 -1
  89. package/src/store/api.js +0 -2
  90. package/src/store/api_fake.js +1 -1
  91. package/src/store/attachments.js +21 -0
  92. package/src/store/chain.js +25 -12
  93. package/src/store/kv.js +10 -19
  94. package/src/store/lsdb.js +1 -1
  95. package/src/store/store.js +313 -130
  96. package/src/util/common.js +48 -43
  97. package/src/util/event.js +7 -0
  98. package/src/util/pubsub.js +27 -8
  99. package/src/util/url.js +14 -0
  100. package/test.cjs +59 -0
  101. package/assets/less/contact.less +0 -126
  102. package/compiled_src/util/common.test.js +0 -414
  103. package/compiled_src/util/event.test.js +0 -38
  104. package/compiled_src/util/extractor.test.js +0 -299
  105. package/compiled_src/util/extractor.test2.js +0 -61
  106. package/compiled_src/util/gate.test.js +0 -18
  107. package/compiled_src/util/pubsub.test.js +0 -35
  108. package/dist/1a0d80f6e72.127.js +0 -1
  109. package/dist/1a0d80f6e72.278.js +0 -1
  110. package/dist/1a0d80f6e72.459.js +0 -1
  111. package/dist/1a0d80f6e72.74.js +0 -1
  112. package/dist/1a0d80f6e72.997.js +0 -1
  113. package/dist/1a0d80f6e72.app.js +0 -1
  114. package/dist/1a0d80f6e72.chat.js +0 -1
  115. package/dist/1a0d80f6e72.form.js +0 -1
  116. package/dist/1a0d80f6e72.library.js +0 -1
  117. package/dist/1a0d80f6e72.preview.js +0 -1
  118. package/dist/1a0d80f6e72.webview.js +0 -1
  119. package/dist/74.js +0 -376
  120. package/dist/997.js +0 -37
  121. package/src/contact/contact.js +0 -400
  122. package/src/contact/css.js +0 -1
  123. package/src/util/common.test.js +0 -301
  124. package/src/util/event.test.js +0 -31
  125. package/src/util/extractor.test.js +0 -240
  126. package/src/util/extractor.test2.js +0 -56
  127. package/src/util/gate.test.js +0 -20
  128. package/src/util/pubsub.test.js +0 -37
  129. /package/dist/{1a0d80f6e72.185.js → 1a0f6f3ff1a.185.js} +0 -0
  130. /package/dist/{1a0d80f6e72.490.js → 1a0f6f3ff1a.490.js} +0 -0
@@ -4,6 +4,9 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _eventLayout = require("./event-layout.js");
8
+ var _conversationHistory = require("./conversation-history.js");
9
+ var _greeting = require("./greeting.js");
7
10
  var _Event = _interopRequireDefault(require("./Event.js"));
8
11
  var _PoweredBy = _interopRequireDefault(require("./PoweredBy.js"));
9
12
  var _index = _interopRequireDefault(require("../util/index.js"));
@@ -37,6 +40,11 @@ class Events extends _common.Com {
37
40
  this.checkAndSeen();
38
41
  }
39
42
  componentDidMount() {
43
+ this.syncTimer = _index.default.setInterval(() => this.syncMessages(), 120_000);
44
+ this.resumeSync = () => this.syncMessages();
45
+ window.addEventListener('focus', this.resumeSync);
46
+ document.addEventListener('visibilitychange', this.resumeSync);
47
+ this.syncMessages();
40
48
  this.setState({
41
49
  isShowTyping: false,
42
50
  typing_event: {}
@@ -71,13 +79,123 @@ class Events extends _common.Com {
71
79
  }, 5000);
72
80
  });
73
81
  this.props.store.onMessageUpdated(this, async ev => {
74
- if (_index.default.convoOfEv(ev) !== this.props.cid) return;
75
- await this.props.store.reloadConvoEvents(this.props.cid);
82
+ const cid = _index.default.convoOfEv(ev);
83
+ if (!this.visibleCounts || !this.visibleCounts[cid]) return;
84
+ await this.props.store.reloadConvoEvents(cid);
76
85
  this.forceUpdate();
77
86
  });
87
+ this.restoreInitialBottom();
88
+ this.observeHistoryStart();
78
89
  }
79
90
  componentDidUpdate(prevProps) {
80
- if (prevProps.cid !== this.props.cid) this.loadConvo();
91
+ if (prevProps.cid !== this.props.cid && !this.isDraftPromotion(prevProps, this.props)) this.loadConvo();
92
+ if (prevProps.cid !== this.props.cid || !prevProps.isShow && this.props.isShow) this.syncMessages();
93
+ this.restoreInitialBottom();
94
+ this.observeHistoryStart();
95
+ }
96
+ componentWillReceiveProps(nextProps) {
97
+ if (nextProps.cid === this.props.cid) return;
98
+ if (this.isDraftPromotion(this.props, nextProps)) {
99
+ this.visibleCounts[nextProps.cid] = this.visibleCounts[this.props.cid] || 30;
100
+ delete this.visibleCounts[this.props.cid];
101
+ return;
102
+ }
103
+ this.loadVersion = (this.loadVersion || 0) + 1;
104
+ this.visibleCounts = this.initialVisibleCounts(nextProps);
105
+ this.evRefs = {};
106
+ this.historyBusy = true;
107
+ this.setState({
108
+ error: false,
109
+ historyError: false,
110
+ historyLoading: false,
111
+ isShowTyping: false,
112
+ loading: nextProps.cid ? _index.default.Date.now() : false
113
+ });
114
+ }
115
+ isDraftPromotion(previous, next) {
116
+ return !!previous.cid && _index.default.get(next.store.widgetConvoOrigins, next.cid) === previous.cid;
117
+ }
118
+ initialVisibleCounts(props) {
119
+ const counts = props.cid ? {
120
+ [props.cid]: 30
121
+ } : {};
122
+ if (!props.store.matchConvo || _index.default.get(props.store.matchConvo(props.cid), 'light_type') !== 'widget') return counts;
123
+ const history = (0, _conversationHistory.conversationHistory)(props.store, props.cid).slice(0, -1).reverse();
124
+ let remaining = 30;
125
+ for (const convo of history) {
126
+ const cached = _index.default.size(props.store.listEvents(convo.id));
127
+ if (!cached || !remaining) break;
128
+ counts[convo.id] = Math.min(cached, remaining);
129
+ remaining -= counts[convo.id];
130
+ if (!_index.default.get(props.store.outofmsgM, convo.id)) break;
131
+ }
132
+ return counts;
133
+ }
134
+ syncMessages() {
135
+ if (!this._isMounted || !this.props.isShow || document.visibilityState === 'hidden') return;
136
+ const ids = Object.keys(this.visibleCounts || {}).filter(cid => this.visibleCounts[cid]);
137
+ return Promise.all(ids.map(cid => this.props.store.syncConvoEvents(cid)));
138
+ }
139
+ componentWillUnmount() {
140
+ _index.default.clearInterval(this.syncTimer);
141
+ window.removeEventListener('focus', this.resumeSync);
142
+ document.removeEventListener('visibilitychange', this.resumeSync);
143
+ this.loadVersion = (this.loadVersion || 0) + 1;
144
+ if (this.timer) _index.default.clearTimeout(this.timer);
145
+ if (this.historyCheckTimer) _index.default.clearTimeout(this.historyCheckTimer);
146
+ if (this.historyObserver) this.historyObserver.disconnect();
147
+ if (this.scrollTimer) _index.default.clearTimeout(this.scrollTimer);
148
+ if (this.initialSizeObserver) this.initialSizeObserver.disconnect();
149
+ }
150
+ stopInitialBottom() {
151
+ this.initialBottom = false;
152
+ }
153
+ restoreInitialBottom() {
154
+ if (!this.initialBottom || this.props.isShow === false || !this.refBody) return;
155
+ if (window.ResizeObserver && (this.initialObservedBody !== this.refBody || this.initialObservedContent !== this.refContent)) {
156
+ if (this.initialSizeObserver) this.initialSizeObserver.disconnect();
157
+ this.initialObservedBody = this.refBody;
158
+ this.initialObservedContent = this.refContent;
159
+ this.initialSizeObserver = new window.ResizeObserver(() => {
160
+ if (this._isMounted) this.restoreInitialBottom();
161
+ });
162
+ this.initialSizeObserver.observe(this.refBody);
163
+ if (this.refContent) this.initialSizeObserver.observe(this.refContent);
164
+ }
165
+ this.refBody.scrollTop = this.refBody.scrollHeight;
166
+ if (this.checkAndSeen) this.checkAndSeen();
167
+ }
168
+ observeHistoryStart() {
169
+ if (this.observedHistoryStart !== this.historyStart || this.observedHistoryRoot !== this.refBody) {
170
+ if (this.historyObserver) this.historyObserver.disconnect();
171
+ this.observedHistoryStart = this.historyStart;
172
+ this.observedHistoryRoot = this.refBody;
173
+ if (this.historyStart && this.refBody && window.IntersectionObserver) {
174
+ this.historyObserver = new window.IntersectionObserver(entries => {
175
+ if (entries.some(entry => entry.isIntersecting)) this.queueHistoryCheck();
176
+ }, {
177
+ root: this.refBody
178
+ });
179
+ this.historyObserver.observe(this.historyStart);
180
+ }
181
+ }
182
+ this.queueHistoryCheck();
183
+ }
184
+ queueHistoryCheck() {
185
+ if (this.historyCheckTimer || !this._isMounted) return;
186
+ this.historyCheckTimer = _index.default.setTimeout(() => {
187
+ this.historyCheckTimer = null;
188
+ this.loadVisibleHistory();
189
+ }, 0);
190
+ }
191
+ loadVisibleHistory() {
192
+ if (!this._isMounted || this.props.isShow === false || this.historyBusy || this.state.historyError || !this.hasMore()) return;
193
+ if (!this.refBody || !this.historyStart) return;
194
+ const root = this.refBody.getBoundingClientRect();
195
+ const marker = this.historyStart.getBoundingClientRect();
196
+ if (root.width <= 0 || root.height <= 0 || marker.height <= 0) return;
197
+ if (marker.right <= Math.max(root.left, 0) || marker.left >= Math.min(root.right, window.innerWidth)) return;
198
+ if (marker.bottom > Math.max(root.top, 0) && marker.top < Math.min(root.bottom, window.innerHeight)) return this.loadMore();
81
199
  }
82
200
  setEventChildRef(ele, id) {
83
201
  this.evRefs[id] = ele;
@@ -104,26 +222,28 @@ class Events extends _common.Com {
104
222
  }
105
223
  render() {
106
224
  if (this.state.error) return this.renderError();
107
- let events = _index.default.map(this.props.store.listEvents(this.props.cid));
108
- if (!this.state.loading && _index.default.size(events) === 0 && !this.props.cid) {
109
- return (0, _preact.h)("div", {
110
- ref: ref => this.refBody = ref,
111
- class: "widget-body--events-container"
112
- }, (0, _preact.h)("div", {
113
- class: "widget-body--empty"
114
- }, (0, _preact.h)("div", {
115
- class: "widget-body--empty-icon"
116
- }), (0, _preact.h)("div", {
117
- class: "widget-body--empty-text"
118
- }, this.props.t('empty_convo_text'))), (0, _preact.h)(_PoweredBy.default, {
119
- store: this.props.store
225
+ const conversations = (0, _conversationHistory.conversationHistory)(this.props.store, this.props.cid).filter(convo => this.visibleCounts && this.visibleCounts[convo.id]);
226
+ let events = conversations.flatMap(convo => {
227
+ const entries = (0, _conversationHistory.conversationEvents)(this.props.store, convo).slice(-this.visibleCounts[convo.id]).filter(entry => convo.id === this.props.cid || entry.ev.type !== 'conversation_state_updated');
228
+ (0, _eventLayout.processEvents)(convo, entries);
229
+ return entries.map(entry => ({
230
+ ...entry,
231
+ convo
120
232
  }));
233
+ });
234
+ if (!this.state.loading && !this.props.creating && events.length === 0 && !this.props.cid) {
235
+ if (!this.greetingCreated) this.greetingCreated = _index.default.Date.now();
236
+ events.push({
237
+ mode: 'lead',
238
+ is_last_message_send: true,
239
+ ev: {
240
+ ...(0, _greeting.widgetGreeting)(this.props.chatbox, this.props.t, this.greetingCreated),
241
+ id: 'widget-virtual-greeting'
242
+ }
243
+ });
121
244
  }
122
- events = _index.default.orderBy(events, 'created', 'asc');
245
+ this.renderedIds = new Set(events.map(entry => entry.ev.id));
123
246
  let convo = this.props.store.matchConvo(this.props.cid);
124
- events = _index.default.map(events, ev => ({
125
- ev: ev
126
- }));
127
247
  let ev = this.state.typing_event || {
128
248
  id: 'evzz' + _index.default.Date.now(),
129
249
  created: _index.default.Date.now(),
@@ -148,11 +268,21 @@ class Events extends _common.Com {
148
268
  }
149
269
  }
150
270
  };
151
- processEvents(convo, events);
271
+ const hasMessages = events.some(entry => entry.ev.type === 'message_sent');
152
272
  events.push(fakeev);
153
273
  let $events = _index.default.map(events, ev => {
154
- let isLast = ev.is_last_message_send && _index.default.get(convo, 'state') !== 'ended';
155
- return (0, _preact.h)(_Event.default, {
274
+ const sourceConvo = ev.convo || convo;
275
+ const isHistory = sourceConvo && sourceConvo.id !== this.props.cid;
276
+ let isLast = !isHistory && ev.is_last_message_send && _index.default.get(sourceConvo, 'state') !== 'ended';
277
+ const sourceId = sourceConvo ? sourceConvo.id : '';
278
+ const key = (_index.default.get(this.props.store.widgetConvoOrigins, sourceId) || sourceId) + ':' + (0, _conversationHistory.conversationEventKey)(ev.ev);
279
+ return (0, _preact.h)("fieldset", {
280
+ class: isHistory ? 'conversation-event conversation-event--history' : 'conversation-event',
281
+ disabled: isHistory,
282
+ "data-conversation-id": sourceConvo && sourceConvo.id,
283
+ key: key
284
+ }, (0, _preact.h)(_Event.default, {
285
+ isHistory: isHistory,
156
286
  isLast: isLast,
157
287
  chatbox: this.props.chatbox,
158
288
  onLink: this.props.onLink,
@@ -162,45 +292,78 @@ class Events extends _common.Com {
162
292
  onPostback: this.props.onPostback,
163
293
  onFocusSourceEventFromQuote: this.onFocusSourceEventFromQuote,
164
294
  ref: ele => this.setEventChildRef(ele, ev.ev.id),
165
- isConvoEnded: _index.default.get(convo, 'state') === 'ended',
295
+ isConvoEnded: _index.default.get(sourceConvo, 'state') === 'ended',
166
296
  event: ev,
167
297
  t: this.props.t,
168
- key: ev.ev.id
169
- });
298
+ key: key
299
+ }));
170
300
  });
171
301
  let $loading = null;
172
- let style = 'opacity: 1 !important';
173
- if (this.props.creating) {
302
+ if (!hasMessages && (this.props.creating || this.state.loading)) {
174
303
  $loading = (0, _preact.h)(_spinner.default, {
175
304
  static: true,
176
305
  mode: _index.default.get(this.props.chatbox, 'primary_color', 'dark'),
177
306
  size: "120",
178
307
  center: true
179
308
  });
180
- style = 'opacity: 0 !important';
181
- }
182
- if (this.state.loading) {
183
- if (_index.default.Date.now() - this.state.loading > 500) {
184
- $loading = (0, _preact.h)(_spinner.default, {
185
- static: true,
186
- mode: _index.default.get(this.props.chatbox, 'primary_color', 'dark'),
187
- size: "120",
188
- center: true
189
- });
190
- style = 'opacity: 0 !important';
191
- }
192
309
  }
193
310
  return (0, _preact.h)(_preact.Fragment, null, $loading, (0, _preact.h)("div", {
194
- style: style,
195
311
  ref: ref => this.refBody = ref,
196
312
  class: "widget-body--events-container",
197
- onMouseOver: this.checkAndSeen
198
- }, (0, _preact.h)("div", {
199
- class: "widget-body--events"
313
+ onMouseOver: this.checkAndSeen,
314
+ onScroll: () => this.queueHistoryCheck(),
315
+ onWheel: () => this.stopInitialBottom(),
316
+ onTouchStart: () => this.stopInitialBottom(),
317
+ onPointerDown: () => this.stopInitialBottom(),
318
+ onKeyDown: () => this.stopInitialBottom()
319
+ }, this.hasMore() && (0, _preact.h)("div", {
320
+ ref: ref => {
321
+ this.historyStart = ref;
322
+ },
323
+ class: "conversation-history-start",
324
+ "aria-hidden": "true"
325
+ }), this.state.historyLoading && !this.state.historyError && (0, _preact.h)(_spinner.default, {
326
+ size: "16",
327
+ mode: "blue"
328
+ }), this.state.historyError && this.renderHistoryError(), (0, _preact.h)("div", {
329
+ class: "widget-body--events",
330
+ ref: ref => {
331
+ this.refContent = ref;
332
+ }
200
333
  }, $events), (0, _preact.h)(_PoweredBy.default, {
201
334
  store: this.props.store
202
335
  })));
203
336
  }
337
+ renderHistoryError() {
338
+ const t = this.props.t;
339
+ const translated = t('load_history_error');
340
+ const locale = t.locale ? t.locale().toLowerCase() : 'en';
341
+ const fallback = locale.startsWith('vi') ? 'Chưa tải được tin nhắn cũ' : 'Couldn’t load earlier messages';
342
+ const text = translated && translated !== 'load_history_error' ? translated : fallback;
343
+ return (0, _preact.h)("div", {
344
+ class: "conversation-history-error",
345
+ role: "status"
346
+ }, (0, _preact.h)("span", {
347
+ class: "conversation-history-error__text"
348
+ }, text), (0, _preact.h)("button", {
349
+ class: "conversation-history-error__retry",
350
+ type: "button",
351
+ disabled: this.state.historyLoading,
352
+ "aria-label": t('try_again'),
353
+ "aria-busy": !!this.state.historyLoading,
354
+ onClick: () => this.loadMore()
355
+ }, (0, _preact.h)("span", {
356
+ class: "conversation-history-error__retry-label",
357
+ style: {
358
+ visibility: this.state.historyLoading ? 'hidden' : 'visible'
359
+ }
360
+ }, t('try_again')), this.state.historyLoading && (0, _preact.h)("span", {
361
+ class: "conversation-history-error__spinner",
362
+ "aria-hidden": "true"
363
+ }, (0, _preact.h)(_spinner.default, {
364
+ size: "14"
365
+ }))));
366
+ }
204
367
  renderError() {
205
368
  let $tryAgainBtn = (0, _preact.h)("a", {
206
369
  href: "#",
@@ -248,61 +411,168 @@ class Events extends _common.Com {
248
411
  });
249
412
  }
250
413
  onEvent(newev) {
251
- if (_index.default.convoOfEv(newev) !== this.props.cid) return;
414
+ const cid = _index.default.convoOfEv(newev);
415
+ if (!this.visibleCounts || !this.visibleCounts[cid]) return;
416
+ if (newev.type === 'message_sent') {
417
+ const sourceEventId = _index.default.get(newev, 'source_event_id');
418
+ const key = cid + ':' + (sourceEventId || newev.id);
419
+ this.receivedMessageKeys = this.receivedMessageKeys || new Set();
420
+ const known = this.receivedMessageKeys.has(key) || this.renderedIds && (this.renderedIds.has(newev.id) || this.renderedIds.has(sourceEventId));
421
+ this.receivedMessageKeys.add(key);
422
+ if (known) return;
423
+ if (this.renderedIds) this.visibleCounts[cid]++;
424
+ }
425
+ if (cid !== this.props.cid) {
426
+ const container = this.refBody;
427
+ const height = container ? container.scrollHeight : 0;
428
+ const top = container ? container.scrollTop : 0;
429
+ this.forceUpdate();
430
+ _index.default.setTimeout(() => {
431
+ if (container && container === this.refBody) container.scrollTop = top + container.scrollHeight - height;
432
+ });
433
+ return;
434
+ }
252
435
  let isBottom = this.isBottom();
436
+ if (newev.type === 'message_sent') this.stopInitialBottom();
253
437
  var byme = false;
254
- if (_index.default.get(newev, 'type') === 'message_sent' && newev.id === _index.default.get(newev, 'data.message.idempotency_key')) byme = true;
255
- if (byme || isBottom) this.scrollToBottom();
438
+ if (_index.default.get(newev, 'type') === 'message_sent' && newev.id === _index.default.get(newev, 'source_event_id')) byme = true;
439
+ if (byme || isBottom) this.scrollToBottom('smooth');
440
+ }
441
+ historyTarget() {
442
+ const conversations = (0, _conversationHistory.conversationHistory)(this.props.store, this.props.cid);
443
+ const first = conversations.findIndex(convo => this.visibleCounts && this.visibleCounts[convo.id]);
444
+ if (first < 0) return;
445
+ const convo = conversations[first];
446
+ const count = _index.default.size(this.props.store.listEvents(convo.id));
447
+ if (this.visibleCounts[convo.id] < count || !convo.id.startsWith('cslight') && !_index.default.get(this.props.store.outofmsgM, convo.id)) return convo;
448
+ return conversations[first - 1];
449
+ }
450
+ hasMore() {
451
+ return !!this.historyTarget();
452
+ }
453
+ async fetchPage(cid) {
454
+ if (cid.startsWith('cslight') || _index.default.get(this.props.store.outofmsgM, cid)) return {};
455
+ this.eventRequests = this.eventRequests || new Map();
456
+ let request = this.eventRequests.get(cid);
457
+ if (!request) {
458
+ request = this.props.store.fetchMoreEvents(cid, 30);
459
+ this.eventRequests.set(cid, request);
460
+ }
461
+ try {
462
+ return await request;
463
+ } finally {
464
+ if (this.eventRequests.get(cid) === request) this.eventRequests.delete(cid);
465
+ }
256
466
  }
257
467
  async loadConvo() {
258
- if (!this.props.cid) return;
259
- this.lastContentOffsetY = 0;
468
+ const cid = this.props.cid;
469
+ this.receivedMessageKeys = new Set();
470
+ this.initialBottom = true;
471
+ const version = this.loadVersion = (this.loadVersion || 0) + 1;
472
+ this.visibleCounts = this.initialVisibleCounts(this.props);
473
+ this.historyBusy = true;
474
+ this.evRefs = {};
260
475
  this.setState({
261
- loading: false
476
+ error: false,
477
+ historyError: false,
478
+ historyLoading: false,
479
+ isShowTyping: false,
480
+ loading: cid && !_index.default.size(this.props.store.listEvents(cid)) ? _index.default.Date.now() : false
262
481
  });
263
- let events = this.props.store.listEvents(this.props.cid);
264
- if (_index.default.size(events) === 0) {
265
- this.setState({
266
- loading: _index.default.Date.now()
267
- });
268
- _index.default.setTimeout(() => {
269
- this.forceUpdate();
270
- }, 800);
271
- let cid = this.props.cid;
272
- let {
273
- error
274
- } = await this.props.store.fetchMoreEvents(cid);
275
- this.setState({
276
- loading: false
482
+ try {
483
+ if (cid && !_index.default.size(this.props.store.listEvents(cid))) {
484
+ const result = await this.fetchPage(cid);
485
+ if (version !== this.loadVersion || !this._isMounted) return;
486
+ if (result && result.error) this.setState({
487
+ error: !_index.default.size(this.props.store.listEvents(cid)),
488
+ historyError: true
489
+ });
490
+ }
491
+ } catch (_) {
492
+ if (version === this.loadVersion) this.setState({
493
+ error: !_index.default.size(this.props.store.listEvents(cid)),
494
+ historyError: true
277
495
  });
278
- if (cid !== this.props.cid) return;
279
- if (error) {
496
+ } finally {
497
+ if (version === this.loadVersion && this._isMounted) {
498
+ this.historyBusy = false;
280
499
  this.setState({
281
- error: true
500
+ loading: false
282
501
  });
283
- return;
502
+ if (!this.initialBottom && this.receivedMessageKeys.size && this.isBottom()) this.scrollToBottom('smooth');
503
+ this.queueHistoryCheck();
504
+ if (cid && cid.startsWith('cslightwidget_') && Object.keys(this.visibleCounts).length === 1 && this.hasMore()) this.loadMore();
284
505
  }
285
506
  }
286
- this.props.store.fetchMoreEvents(this.props.cid);
507
+ }
508
+ async loadMore() {
509
+ if (this.historyBusy || !this._isMounted) return;
510
+ const target = this.historyTarget();
511
+ if (!target) return;
512
+ const version = this.loadVersion;
513
+ this.historyBusy = true;
514
+ const container = this.refBody;
515
+ const visible = this.visibleCounts[target.id] || 0;
287
516
  this.setState({
288
- error: false
517
+ historyLoading: true
289
518
  });
290
- this.scrollToBottom();
519
+ try {
520
+ if (visible >= _index.default.size(this.props.store.listEvents(target.id))) {
521
+ const result = await this.fetchPage(target.id);
522
+ if (result && result.error) throw result.error;
523
+ }
524
+ if (version !== this.loadVersion || !this._isMounted) return;
525
+ this.visibleCounts[target.id] = visible + 30;
526
+ this.setState({
527
+ historyError: false
528
+ });
529
+ } catch (_) {
530
+ if (version === this.loadVersion) this.setState({
531
+ historyError: true
532
+ });
533
+ } finally {
534
+ if (version === this.loadVersion && this._isMounted) {
535
+ const height = container ? container.scrollHeight : 0;
536
+ const top = container ? container.scrollTop : 0;
537
+ this.setState({
538
+ historyLoading: false
539
+ });
540
+ this.forceUpdate();
541
+ await new Promise(resolve => _index.default.setTimeout(resolve, 0));
542
+ if (version === this.loadVersion && this._isMounted) {
543
+ if (this.initialBottom) this.restoreInitialBottom();else if (container && container === this.refBody) {
544
+ container.scrollTop = top + container.scrollHeight - height;
545
+ this.lastScrollTop = container.scrollTop;
546
+ }
547
+ this.historyBusy = false;
548
+ this.queueHistoryCheck();
549
+ }
550
+ }
551
+ }
291
552
  }
292
553
  isBottom() {
293
554
  let $container = this.refBody;
294
555
  if (!$container) return false;
295
556
  return $container.scrollHeight < $container.scrollTop + $container.offsetHeight + 50;
296
557
  }
297
- scrollToBottom() {
298
- _index.default.setTimeout(() => {
558
+ scrollToBottom(behavior = 'auto') {
559
+ if (this.scrollTimer) _index.default.clearTimeout(this.scrollTimer);
560
+ const cid = this.props.cid;
561
+ this.scrollTimer = _index.default.setTimeout(() => {
562
+ this.scrollTimer = null;
563
+ if (!this._isMounted || this.props.cid !== cid) return;
299
564
  const $div = this.refBody;
300
- if ($div) $div.scrollTop = $div.scrollHeight;
565
+ if (!$div) return;
566
+ const reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
567
+ if (behavior === 'smooth' && !reducedMotion && $div.scrollTo) {
568
+ $div.scrollTo({
569
+ top: $div.scrollHeight,
570
+ behavior: 'smooth'
571
+ });
572
+ } else {
573
+ $div.scrollTop = $div.scrollHeight;
574
+ }
301
575
  });
302
- _index.default.setTimeout(() => {
303
- const $div = this.refBody;
304
- if ($div) $div.scrollTop = $div.scrollHeight;
305
- }, 100);
306
576
  if (this.props.isShow) {
307
577
  if (this.props.store.browserVisibility === 'visible' && document.hasFocus()) {
308
578
  this.props.store.seenConvo(this.props.cid);
@@ -311,57 +581,6 @@ class Events extends _common.Com {
311
581
  }
312
582
  }
313
583
  exports.default = Events;
314
- function processEvents(convo, events) {
315
- events = events.filter(event => visibleEvent(event.ev));
316
- if (events.length < 1) return;
317
- var lastAgentEvent;
318
- for (var i = 1; i < events.length; i++) {
319
- let curEvent = events[i];
320
- let lastEvent = events[i - 1];
321
- if (_index.default.get(curEvent, 'ev.by.type') != 'user' && !curEvent.is_typing) {
322
- lastAgentEvent = curEvent;
323
- }
324
- if (visibleEvent(curEvent.ev)) {
325
- let elapsedSec = _index.default.unixSec(curEvent.ev.created) - _index.default.unixSec(lastEvent.ev.created);
326
- if (elapsedSec > 300) curEvent.show_time = true;
327
- }
328
- }
329
- if (lastAgentEvent) lastAgentEvent.show_time = true;
330
- for (var i = 0; i < events.length; i++) {
331
- let curEvent = events[i];
332
- if (_index.default.get(curEvent, 'ev.type') === 'message_sent') {
333
- curEvent.show_time = true;
334
- break;
335
- }
336
- }
337
- let lastMsgSentIdx = -1;
338
- for (var i = 0; i < events.length; i++) {
339
- let curEvent = events[i];
340
- let prevEvent = events[i - 1];
341
- let nextEvent = events[i + 1];
342
- if (curEvent.ev.type === 'message_sent') {
343
- lastMsgSentIdx = i;
344
- curEvent.mode = 'lead';
345
- if (_index.default.get(prevEvent, 'ev.type') === 'message_sent' && _index.default.get(prevEvent, 'ev.by.id') === _index.default.get(curEvent, 'ev.by.id')) {
346
- curEvent.mode = 'follow';
347
- }
348
- if (_index.default.get(nextEvent, 'ev.type') !== 'message_sent' || _index.default.get(nextEvent, 'ev.by.id') !== _index.default.get(curEvent, 'ev.by.id')) {
349
- curEvent.is_end = true;
350
- }
351
- continue;
352
- }
353
- }
354
- if (lastMsgSentIdx > -1) {
355
- events[lastMsgSentIdx].is_last_message_send = true;
356
- }
357
- }
358
- function visibleEvent(ev) {
359
- let type = _index.default.get(ev, 'type');
360
- if (type === 'message_sent') return true;
361
- if (type === 'conversation_state_updated') {
362
- return _index.default.get(ev, 'data.conversation.state') == 'ended';
363
- }
364
- }
365
584
  function isSummited(form) {
366
585
  let fields = _index.default.get(form, 'fields') || [];
367
586
  return _index.default.find(fields, field => !!field.answered);
@@ -173,7 +173,7 @@ class FormAttachment extends _common.Com {
173
173
  },
174
174
  type: "checkbox",
175
175
  id: 'checkbox.' + this.props.ev.id + '.' + field.name,
176
- onChange: e => this.onInputChange(index, ev.target.checked),
176
+ onChange: e => this.onInputChange(index, e.target.checked),
177
177
  checked: field.value
178
178
  }), (0, _preact.h)("label", {
179
179
  class: "checkbox--label",
@@ -182,6 +182,7 @@ Vui lòng hỏi chúng tôi bất cứ điều gì hoặc chia sẻ phản hồi
182
182
  getTitle() {
183
183
  let accSetting = this.props.plugin.chatbox || {};
184
184
  let acc = this.props.store.matchAccount() || {};
185
+ if (accSetting.hide_agent_profile) return acc.name || this.props.t('widget_title');
185
186
  let agentsJoined = this.getAgentJoined(this.props.cid);
186
187
  let caption = _index.default.get(this.props.plugin, 'chatbox.caption', {});
187
188
  let title = this.props.t.i18n(caption) || accSetting.title;
@@ -203,7 +204,9 @@ Vui lòng hỏi chúng tôi bất cứ điều gì hoặc chia sẻ phản hồi
203
204
  class: "widget-header--total-unread-convo"
204
205
  }, unreadcount);
205
206
  }
206
- return (0, _preact.h)("div", {
207
+ return (0, _preact.h)("button", {
208
+ type: "button",
209
+ "aria-label": this.props.t('menu_back'),
207
210
  class: `widget-header--wrap-button-back ${unreadcount ? 'widget-header--has-convo-unred' : ''}`,
208
211
  onClick: e => this.onBackClick(e)
209
212
  }, (0, _preact.h)("div", {
@@ -215,7 +218,9 @@ Vui lòng hỏi chúng tôi bất cứ điều gì hoặc chia sẻ phản hồi
215
218
  let accSetting = this.props.plugin.chatbox || {};
216
219
  if (accSetting.hide_close_button) return null;
217
220
  if (this.props.store.isEmbededChatMode()) return;
218
- return (0, _preact.h)("div", {
221
+ return (0, _preact.h)("button", {
222
+ type: "button",
223
+ "aria-label": this.props.t('close'),
219
224
  class: "widget-header--button-close",
220
225
  "data-tooltip": this.props.t('close'),
221
226
  "data-tooltip-position": "bottom",
@@ -33,7 +33,7 @@ class InfoMessage extends _common.Com {
33
33
  return (0, _preact.h)("div", {
34
34
  class: "convo-event"
35
35
  }, (0, _preact.h)("div", {
36
- class: "info-message"
36
+ class: "info-message info-message--ended"
37
37
  }, this.props.t('conversation_ended')));
38
38
  }
39
39
  return null;