@subiz/widget-v4 4.1.210 → 4.1.211
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.
- package/README.md +21 -2
- package/assets/less/chatbox.less +9 -0
- package/assets/less/form.less +10 -0
- package/assets/less/message.less +114 -13
- package/assets/less/widget.less +21 -16
- package/compiled_src/call/css.js +1 -1
- package/compiled_src/chatbox/Buttons.js +7 -4
- package/compiled_src/chatbox/ChatButton.js +115 -29
- package/compiled_src/chatbox/Event.js +37 -5
- package/compiled_src/chatbox/Events.js +348 -129
- package/compiled_src/chatbox/FormAttachment.js +1 -1
- package/compiled_src/chatbox/Header.js +6 -2
- package/compiled_src/chatbox/InfoMessage.js +1 -1
- package/compiled_src/chatbox/MessageInput.js +24 -9
- package/compiled_src/chatbox/OrderAttachments.js +13 -13
- package/compiled_src/chatbox/QuoteMessage.js +3 -2
- package/compiled_src/chatbox/TextMessage.js +22 -18
- package/compiled_src/chatbox/Widget.js +200 -129
- package/compiled_src/chatbox/block.js +39 -29
- package/compiled_src/chatbox/conversation-history.js +44 -0
- package/compiled_src/chatbox/event-layout.js +59 -0
- package/compiled_src/chatbox/greeting.js +32 -0
- package/compiled_src/common.js +13 -9
- package/compiled_src/form/css.js +1 -1
- package/compiled_src/lib.js +22 -32
- package/compiled_src/notif/css.js +1 -1
- package/compiled_src/plugin.js +11 -18
- package/compiled_src/popup/css.js +1 -1
- package/compiled_src/popup/popup.js +1 -1
- package/compiled_src/store/api.js +0 -2
- package/compiled_src/store/api_fake.js +1 -1
- package/compiled_src/store/attachments.js +28 -0
- package/compiled_src/store/chain.js +24 -10
- package/compiled_src/store/kv.js +9 -13
- package/compiled_src/store/lsdb.js +1 -1
- package/compiled_src/store/store.js +356 -117
- package/compiled_src/util/common.js +48 -41
- package/compiled_src/util/event.js +15 -0
- package/compiled_src/util/pubsub.js +28 -13
- package/compiled_src/util/url.js +18 -0
- package/dist/.HASH +1 -0
- package/dist/127.js +13 -13
- package/dist/132.js +1 -1
- package/dist/1a0ddc53cbb.127.js +1 -0
- package/dist/{1a0d80f6e72.132.js → 1a0ddc53cbb.132.js} +1 -1
- package/dist/1a0ddc53cbb.278.js +1 -0
- package/dist/{1a0d80f6e72.339.js → 1a0ddc53cbb.339.js} +1 -1
- package/dist/1a0ddc53cbb.app.js +1 -0
- package/dist/{1a0d80f6e72.chat.html → 1a0ddc53cbb.chat.html} +1 -1
- package/dist/1a0ddc53cbb.chat.js +1 -0
- package/dist/{1a0d80f6e72.form.html → 1a0ddc53cbb.form.html} +1 -1
- package/dist/1a0ddc53cbb.form.js +1 -0
- package/dist/{1a0d80f6e72.index.html → 1a0ddc53cbb.index.html} +1 -1
- package/dist/{1a0d80f6e72.library.html → 1a0ddc53cbb.library.html} +1 -1
- package/dist/1a0ddc53cbb.library.js +1 -0
- package/dist/{1a0d80f6e72.preview.html → 1a0ddc53cbb.preview.html} +1 -1
- package/dist/1a0ddc53cbb.preview.js +1 -0
- package/dist/1a0ddc53cbb.webview.js +1 -0
- package/dist/screenshoot.js +1492 -616
- package/package.json +10 -3
- package/src/chatbox/Buttons.js +5 -4
- package/src/chatbox/ChatButton.js +120 -39
- package/src/chatbox/Event.js +38 -5
- package/src/chatbox/Events.js +306 -134
- package/src/chatbox/FormAttachment.js +1 -1
- package/src/chatbox/Header.js +4 -4
- package/src/chatbox/InfoMessage.js +1 -1
- package/src/chatbox/MessageInput.js +15 -10
- package/src/chatbox/OrderAttachments.js +13 -13
- package/src/chatbox/QuoteMessage.js +3 -2
- package/src/chatbox/TextMessage.js +21 -20
- package/src/chatbox/Widget.js +150 -150
- package/src/chatbox/block.js +29 -27
- package/src/chatbox/conversation-history.js +37 -0
- package/src/chatbox/event-layout.js +72 -0
- package/src/chatbox/greeting.js +13 -0
- package/src/common.js +15 -9
- package/src/lib.js +22 -35
- package/src/plugin.js +11 -13
- package/src/popup/popup.js +1 -1
- package/src/store/api.js +0 -2
- package/src/store/api_fake.js +1 -1
- package/src/store/attachments.js +21 -0
- package/src/store/chain.js +25 -12
- package/src/store/kv.js +10 -19
- package/src/store/lsdb.js +1 -1
- package/src/store/store.js +313 -130
- package/src/util/common.js +48 -43
- package/src/util/event.js +7 -0
- package/src/util/pubsub.js +27 -8
- package/src/util/url.js +14 -0
- package/test.cjs +59 -0
- package/assets/less/contact.less +0 -126
- package/compiled_src/util/common.test.js +0 -414
- package/compiled_src/util/event.test.js +0 -38
- package/compiled_src/util/extractor.test.js +0 -299
- package/compiled_src/util/extractor.test2.js +0 -61
- package/compiled_src/util/gate.test.js +0 -18
- package/compiled_src/util/pubsub.test.js +0 -35
- package/dist/1a0d80f6e72.127.js +0 -1
- package/dist/1a0d80f6e72.278.js +0 -1
- package/dist/1a0d80f6e72.74.js +0 -1
- package/dist/1a0d80f6e72.997.js +0 -1
- package/dist/1a0d80f6e72.app.js +0 -1
- package/dist/1a0d80f6e72.chat.js +0 -1
- package/dist/1a0d80f6e72.form.js +0 -1
- package/dist/1a0d80f6e72.library.js +0 -1
- package/dist/1a0d80f6e72.preview.js +0 -1
- package/dist/1a0d80f6e72.webview.js +0 -1
- package/dist/74.js +0 -376
- package/dist/997.js +0 -37
- package/dist/HASH +0 -1
- package/src/contact/contact.js +0 -400
- package/src/contact/css.js +0 -1
- package/src/util/common.test.js +0 -301
- package/src/util/event.test.js +0 -31
- package/src/util/extractor.test.js +0 -240
- package/src/util/extractor.test2.js +0 -56
- package/src/util/gate.test.js +0 -20
- package/src/util/pubsub.test.js +0 -37
- /package/dist/{1a0d80f6e72.185.js → 1a0ddc53cbb.185.js} +0 -0
- /package/dist/{1a0d80f6e72.27.js → 1a0ddc53cbb.27.js} +0 -0
- /package/dist/{1a0d80f6e72.459.js → 1a0ddc53cbb.459.js} +0 -0
- /package/dist/{1a0d80f6e72.490.js → 1a0ddc53cbb.490.js} +0 -0
package/src/chatbox/Events.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {processEvents} from './event-layout.js'
|
|
2
|
+
import {conversationHistory, conversationEvents, conversationEventKey} from './conversation-history.js'
|
|
3
|
+
import {widgetGreeting} from './greeting.js'
|
|
1
4
|
import Event from './Event.js'
|
|
2
5
|
import PoweredBy from './PoweredBy.js'
|
|
3
6
|
const convoemptyimg = require('../../assets/img/convos_empty.svg')
|
|
@@ -31,6 +34,11 @@ export default class Events extends Com {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
componentDidMount() {
|
|
37
|
+
this.syncTimer = sb.setInterval(() => this.syncMessages(), 120_000)
|
|
38
|
+
this.resumeSync = () => this.syncMessages()
|
|
39
|
+
window.addEventListener('focus', this.resumeSync)
|
|
40
|
+
document.addEventListener('visibilitychange', this.resumeSync)
|
|
41
|
+
this.syncMessages()
|
|
34
42
|
this.setState({
|
|
35
43
|
isShowTyping: false,
|
|
36
44
|
typing_event: {},
|
|
@@ -67,14 +75,127 @@ export default class Events extends Com {
|
|
|
67
75
|
})
|
|
68
76
|
|
|
69
77
|
this.props.store.onMessageUpdated(this, async (ev) => {
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
const cid = sb.convoOfEv(ev)
|
|
79
|
+
if (!this.visibleCounts || !this.visibleCounts[cid]) return
|
|
80
|
+
await this.props.store.reloadConvoEvents(cid)
|
|
72
81
|
this.forceUpdate()
|
|
73
82
|
})
|
|
83
|
+
this.restoreInitialBottom()
|
|
84
|
+
this.observeHistoryStart()
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
componentDidUpdate(prevProps) {
|
|
77
|
-
if (prevProps.cid !== this.props.cid) this.loadConvo()
|
|
88
|
+
if (prevProps.cid !== this.props.cid && !this.isDraftPromotion(prevProps, this.props)) this.loadConvo()
|
|
89
|
+
if (prevProps.cid !== this.props.cid || (!prevProps.isShow && this.props.isShow)) this.syncMessages()
|
|
90
|
+
this.restoreInitialBottom()
|
|
91
|
+
this.observeHistoryStart()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
componentWillReceiveProps(nextProps) {
|
|
95
|
+
if (nextProps.cid === this.props.cid) return
|
|
96
|
+
if (this.isDraftPromotion(this.props, nextProps)) {
|
|
97
|
+
this.visibleCounts[nextProps.cid] = this.visibleCounts[this.props.cid] || 30
|
|
98
|
+
delete this.visibleCounts[this.props.cid]
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
// Prepare the new conversation before its first render, not after the DOM update.
|
|
102
|
+
this.loadVersion = (this.loadVersion || 0) + 1
|
|
103
|
+
this.visibleCounts = this.initialVisibleCounts(nextProps)
|
|
104
|
+
this.evRefs = {}
|
|
105
|
+
this.historyBusy = true
|
|
106
|
+
this.setState({error: false, historyError: false, historyLoading: false, isShowTyping: false, loading: nextProps.cid ? sb.Date.now() : false})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
isDraftPromotion(previous, next) {
|
|
110
|
+
return !!previous.cid && sb.get(next.store.widgetConvoOrigins, next.cid) === previous.cid
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
initialVisibleCounts(props) {
|
|
114
|
+
const counts = props.cid ? {[props.cid]: 30} : {}
|
|
115
|
+
if (!props.store.matchConvo || sb.get(props.store.matchConvo(props.cid), 'light_type') !== 'widget') return counts
|
|
116
|
+
const history = conversationHistory(props.store, props.cid).slice(0, -1).reverse()
|
|
117
|
+
let remaining = 30
|
|
118
|
+
for (const convo of history) {
|
|
119
|
+
const cached = sb.size(props.store.listEvents(convo.id))
|
|
120
|
+
if (!cached || !remaining) break
|
|
121
|
+
counts[convo.id] = Math.min(cached, remaining)
|
|
122
|
+
remaining -= counts[convo.id]
|
|
123
|
+
if (!sb.get(props.store.outofmsgM, convo.id)) break
|
|
124
|
+
}
|
|
125
|
+
return counts
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
syncMessages() {
|
|
129
|
+
if (!this._isMounted || !this.props.isShow || document.visibilityState === 'hidden') return
|
|
130
|
+
const ids = Object.keys(this.visibleCounts || {}).filter(cid => this.visibleCounts[cid])
|
|
131
|
+
return Promise.all(ids.map(cid => this.props.store.syncConvoEvents(cid)))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
componentWillUnmount() {
|
|
135
|
+
sb.clearInterval(this.syncTimer)
|
|
136
|
+
window.removeEventListener('focus', this.resumeSync)
|
|
137
|
+
document.removeEventListener('visibilitychange', this.resumeSync)
|
|
138
|
+
this.loadVersion = (this.loadVersion || 0) + 1
|
|
139
|
+
if (this.timer) sb.clearTimeout(this.timer)
|
|
140
|
+
if (this.historyCheckTimer) sb.clearTimeout(this.historyCheckTimer)
|
|
141
|
+
if (this.historyObserver) this.historyObserver.disconnect()
|
|
142
|
+
if (this.scrollTimer) sb.clearTimeout(this.scrollTimer)
|
|
143
|
+
if (this.initialSizeObserver) this.initialSizeObserver.disconnect()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
stopInitialBottom() {
|
|
147
|
+
this.initialBottom = false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
restoreInitialBottom() {
|
|
151
|
+
if (!this.initialBottom || this.props.isShow === false || !this.refBody) return
|
|
152
|
+
// Restore after DOM commits, and again when images/fonts or the widget size settle.
|
|
153
|
+
// Unlike isBottom(), this records the opening intent before the content grows.
|
|
154
|
+
if (window.ResizeObserver && (this.initialObservedBody !== this.refBody || this.initialObservedContent !== this.refContent)) {
|
|
155
|
+
if (this.initialSizeObserver) this.initialSizeObserver.disconnect()
|
|
156
|
+
this.initialObservedBody = this.refBody
|
|
157
|
+
this.initialObservedContent = this.refContent
|
|
158
|
+
this.initialSizeObserver = new window.ResizeObserver(() => {
|
|
159
|
+
if (this._isMounted) this.restoreInitialBottom()
|
|
160
|
+
})
|
|
161
|
+
this.initialSizeObserver.observe(this.refBody)
|
|
162
|
+
if (this.refContent) this.initialSizeObserver.observe(this.refContent)
|
|
163
|
+
}
|
|
164
|
+
this.refBody.scrollTop = this.refBody.scrollHeight
|
|
165
|
+
if (this.checkAndSeen) this.checkAndSeen()
|
|
166
|
+
}
|
|
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
|
+
}, {root: this.refBody})
|
|
177
|
+
this.historyObserver.observe(this.historyStart)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.queueHistoryCheck()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
queueHistoryCheck() {
|
|
184
|
+
if (this.historyCheckTimer || !this._isMounted) return
|
|
185
|
+
this.historyCheckTimer = sb.setTimeout(() => {
|
|
186
|
+
this.historyCheckTimer = null
|
|
187
|
+
this.loadVisibleHistory()
|
|
188
|
+
}, 0)
|
|
189
|
+
}
|
|
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()
|
|
78
199
|
}
|
|
79
200
|
|
|
80
201
|
setEventChildRef(ele, id) {
|
|
@@ -109,24 +230,28 @@ export default class Events extends Com {
|
|
|
109
230
|
|
|
110
231
|
render() {
|
|
111
232
|
if (this.state.error) return this.renderError()
|
|
112
|
-
|
|
233
|
+
const conversations = conversationHistory(this.props.store, this.props.cid).filter(convo => this.visibleCounts && this.visibleCounts[convo.id])
|
|
234
|
+
let events = conversations.flatMap(convo => {
|
|
235
|
+
const entries = conversationEvents(this.props.store, convo).slice(-this.visibleCounts[convo.id]).filter(entry => convo.id === this.props.cid || entry.ev.type !== 'conversation_state_updated')
|
|
236
|
+
processEvents(convo, entries)
|
|
237
|
+
return entries.map(entry => ({...entry, convo}))
|
|
238
|
+
})
|
|
113
239
|
|
|
114
|
-
if (!this.state.loading &&
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
240
|
+
if (!this.state.loading && !this.props.creating && events.length === 0 && !this.props.cid) {
|
|
241
|
+
if (!this.greetingCreated) this.greetingCreated = sb.Date.now()
|
|
242
|
+
// Presentation only: never insert this greeting into the store or send it.
|
|
243
|
+
events.push({
|
|
244
|
+
mode: 'lead',
|
|
245
|
+
is_last_message_send: true,
|
|
246
|
+
ev: {
|
|
247
|
+
...widgetGreeting(this.props.chatbox, this.props.t, this.greetingCreated),
|
|
248
|
+
id: 'widget-virtual-greeting',
|
|
249
|
+
},
|
|
250
|
+
})
|
|
124
251
|
}
|
|
125
252
|
|
|
126
|
-
|
|
127
|
-
|
|
253
|
+
this.renderedIds = new Set(events.map(entry => entry.ev.id))
|
|
128
254
|
let convo = this.props.store.matchConvo(this.props.cid)
|
|
129
|
-
events = sb.map(events, (ev) => ({ev: ev}))
|
|
130
255
|
|
|
131
256
|
// console.log('-----------', this.state.isShowTyping, this.state.typing_event)
|
|
132
257
|
let ev = this.state.typing_event || {id: 'evzz' + sb.Date.now(), created: sb.Date.now(), by: {type: 'agent'}}
|
|
@@ -143,14 +268,20 @@ export default class Events extends Com {
|
|
|
143
268
|
},
|
|
144
269
|
}
|
|
145
270
|
|
|
146
|
-
|
|
271
|
+
const hasMessages = events.some(entry => entry.ev.type === 'message_sent')
|
|
147
272
|
// we dont want fakeev cause some unexpected buf when processEvents
|
|
148
273
|
events.push(fakeev)
|
|
149
274
|
|
|
150
275
|
let $events = sb.map(events, (ev) => {
|
|
151
|
-
|
|
276
|
+
const sourceConvo = ev.convo || convo
|
|
277
|
+
const isHistory = sourceConvo && sourceConvo.id !== this.props.cid
|
|
278
|
+
let isLast = !isHistory && ev.is_last_message_send && sb.get(sourceConvo, 'state') !== 'ended'
|
|
279
|
+
const sourceId = sourceConvo ? sourceConvo.id : ''
|
|
280
|
+
const key = (sb.get(this.props.store.widgetConvoOrigins, sourceId) || sourceId) + ':' + conversationEventKey(ev.ev)
|
|
152
281
|
return (
|
|
282
|
+
<fieldset class={isHistory ? 'conversation-event conversation-event--history' : 'conversation-event'} disabled={isHistory} data-conversation-id={sourceConvo && sourceConvo.id} key={key}>
|
|
153
283
|
<Event
|
|
284
|
+
isHistory={isHistory}
|
|
154
285
|
isLast={isLast}
|
|
155
286
|
chatbox={this.props.chatbox}
|
|
156
287
|
onLink={this.props.onLink}
|
|
@@ -160,44 +291,60 @@ export default class Events extends Com {
|
|
|
160
291
|
onPostback={this.props.onPostback}
|
|
161
292
|
onFocusSourceEventFromQuote={this.onFocusSourceEventFromQuote}
|
|
162
293
|
ref={(ele) => this.setEventChildRef(ele, ev.ev.id)}
|
|
163
|
-
isConvoEnded={sb.get(
|
|
294
|
+
isConvoEnded={sb.get(sourceConvo, 'state') === 'ended'}
|
|
164
295
|
event={ev}
|
|
165
296
|
t={this.props.t}
|
|
166
|
-
key={
|
|
297
|
+
key={key}
|
|
167
298
|
/>
|
|
299
|
+
</fieldset>
|
|
168
300
|
)
|
|
169
301
|
})
|
|
170
302
|
|
|
171
303
|
let $loading = null
|
|
172
|
-
|
|
173
|
-
if (this.props.creating) {
|
|
304
|
+
if (!hasMessages && (this.props.creating || this.state.loading)) {
|
|
174
305
|
$loading = <Spinner static mode={sb.get(this.props.chatbox, 'primary_color', 'dark')} size='120' center />
|
|
175
|
-
style = 'opacity: 0 !important'
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (this.state.loading) {
|
|
179
|
-
if (sb.Date.now() - this.state.loading > 500) {
|
|
180
|
-
$loading = <Spinner static mode={sb.get(this.props.chatbox, 'primary_color', 'dark')} size='120' center />
|
|
181
|
-
style = 'opacity: 0 !important'
|
|
182
|
-
}
|
|
183
306
|
}
|
|
184
307
|
|
|
185
308
|
return (
|
|
186
309
|
<Fragment>
|
|
187
310
|
{$loading}
|
|
188
311
|
<div
|
|
189
|
-
style={style}
|
|
190
312
|
ref={(ref) => (this.refBody = ref)}
|
|
191
313
|
class='widget-body--events-container'
|
|
192
314
|
onMouseOver={this.checkAndSeen}
|
|
315
|
+
onScroll={() => this.queueHistoryCheck()}
|
|
316
|
+
onWheel={() => this.stopInitialBottom()}
|
|
317
|
+
onTouchStart={() => this.stopInitialBottom()}
|
|
318
|
+
onPointerDown={() => this.stopInitialBottom()}
|
|
319
|
+
onKeyDown={() => this.stopInitialBottom()}
|
|
193
320
|
>
|
|
194
|
-
<div class='
|
|
321
|
+
{this.hasMore() && <div ref={ref => {this.historyStart = ref}} class='conversation-history-start' aria-hidden='true' />}
|
|
322
|
+
{this.state.historyLoading && !this.state.historyError && <Spinner size='16' mode='blue' />}
|
|
323
|
+
{this.state.historyError && this.renderHistoryError()}
|
|
324
|
+
<div class='widget-body--events' ref={ref => {this.refContent = ref}}>{$events}</div>
|
|
195
325
|
<PoweredBy store={this.props.store} />
|
|
196
326
|
</div>
|
|
197
327
|
</Fragment>
|
|
198
328
|
)
|
|
199
329
|
}
|
|
200
330
|
|
|
331
|
+
renderHistoryError() {
|
|
332
|
+
const t = this.props.t
|
|
333
|
+
const translated = t('load_history_error')
|
|
334
|
+
const locale = t.locale ? t.locale().toLowerCase() : 'en'
|
|
335
|
+
const fallback = locale.startsWith('vi') ? 'Chưa tải được tin nhắn cũ' : 'Couldn’t load earlier messages'
|
|
336
|
+
const text = translated && translated !== 'load_history_error' ? translated : fallback
|
|
337
|
+
return (
|
|
338
|
+
<div class='conversation-history-error' role='status'>
|
|
339
|
+
<span class='conversation-history-error__text'>{text}</span>
|
|
340
|
+
<button class='conversation-history-error__retry' type='button' disabled={this.state.historyLoading} aria-label={t('try_again')} aria-busy={!!this.state.historyLoading} onClick={() => this.loadMore()}>
|
|
341
|
+
<span class='conversation-history-error__retry-label' style={{visibility: this.state.historyLoading ? 'hidden' : 'visible'}}>{t('try_again')}</span>
|
|
342
|
+
{this.state.historyLoading && <span class='conversation-history-error__spinner' aria-hidden='true'><Spinner size='14' /></span>}
|
|
343
|
+
</button>
|
|
344
|
+
</div>
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
|
|
201
348
|
renderError() {
|
|
202
349
|
let $tryAgainBtn = (
|
|
203
350
|
<a href='#' onclick={(_) => this.loadConvo()}>
|
|
@@ -248,42 +395,131 @@ export default class Events extends Com {
|
|
|
248
395
|
}
|
|
249
396
|
|
|
250
397
|
onEvent(newev) {
|
|
251
|
-
|
|
398
|
+
const cid = sb.convoOfEv(newev)
|
|
399
|
+
if (!this.visibleCounts || !this.visibleCounts[cid]) return
|
|
400
|
+
if (newev.type === 'message_sent') {
|
|
401
|
+
const sourceEventId = sb.get(newev, 'source_event_id')
|
|
402
|
+
const key = cid + ':' + (sourceEventId || newev.id)
|
|
403
|
+
this.receivedMessageKeys = this.receivedMessageKeys || new Set()
|
|
404
|
+
const known = this.receivedMessageKeys.has(key) || (this.renderedIds && (this.renderedIds.has(newev.id) || this.renderedIds.has(sourceEventId)))
|
|
405
|
+
this.receivedMessageKeys.add(key)
|
|
406
|
+
// Acknowledgements update the existing bubble; they must not restart scrolling.
|
|
407
|
+
if (known) return
|
|
408
|
+
if (this.renderedIds) this.visibleCounts[cid]++
|
|
409
|
+
}
|
|
410
|
+
if (cid !== this.props.cid) {
|
|
411
|
+
const container = this.refBody
|
|
412
|
+
const height = container ? container.scrollHeight : 0
|
|
413
|
+
const top = container ? container.scrollTop : 0
|
|
414
|
+
this.forceUpdate()
|
|
415
|
+
sb.setTimeout(() => {if (container && container === this.refBody) container.scrollTop = top + container.scrollHeight - height})
|
|
416
|
+
return
|
|
417
|
+
}
|
|
252
418
|
let isBottom = this.isBottom()
|
|
419
|
+
if (newev.type === 'message_sent') this.stopInitialBottom()
|
|
253
420
|
|
|
254
421
|
var byme = false
|
|
255
|
-
if (sb.get(newev, 'type') === 'message_sent' && newev.id === sb.get(newev, '
|
|
422
|
+
if (sb.get(newev, 'type') === 'message_sent' && newev.id === sb.get(newev, 'source_event_id'))
|
|
256
423
|
byme = true
|
|
257
424
|
// scroll to bottom when message is sent by me
|
|
258
425
|
// auto scroll to bottom on new message when we are at the bottom
|
|
259
|
-
if (byme || isBottom) this.scrollToBottom()
|
|
426
|
+
if (byme || isBottom) this.scrollToBottom('smooth')
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
historyTarget() {
|
|
430
|
+
const conversations = conversationHistory(this.props.store, this.props.cid)
|
|
431
|
+
const first = conversations.findIndex(convo => this.visibleCounts && this.visibleCounts[convo.id])
|
|
432
|
+
if (first < 0) return
|
|
433
|
+
const convo = conversations[first]
|
|
434
|
+
const count = sb.size(this.props.store.listEvents(convo.id))
|
|
435
|
+
if (this.visibleCounts[convo.id] < count || (!convo.id.startsWith('cslight') && !sb.get(this.props.store.outofmsgM, convo.id))) return convo
|
|
436
|
+
return conversations[first - 1]
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
hasMore() {
|
|
440
|
+
return !!this.historyTarget()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async fetchPage(cid) {
|
|
444
|
+
if (cid.startsWith('cslight') || sb.get(this.props.store.outofmsgM, cid)) return {}
|
|
445
|
+
this.eventRequests = this.eventRequests || new Map()
|
|
446
|
+
let request = this.eventRequests.get(cid)
|
|
447
|
+
if (!request) {
|
|
448
|
+
request = this.props.store.fetchMoreEvents(cid, 30)
|
|
449
|
+
this.eventRequests.set(cid, request)
|
|
450
|
+
}
|
|
451
|
+
try {return await request} finally {
|
|
452
|
+
if (this.eventRequests.get(cid) === request) this.eventRequests.delete(cid)
|
|
453
|
+
}
|
|
260
454
|
}
|
|
261
455
|
|
|
262
456
|
async loadConvo() {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
this.
|
|
266
|
-
this.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
457
|
+
const cid = this.props.cid
|
|
458
|
+
this.receivedMessageKeys = new Set()
|
|
459
|
+
this.initialBottom = true
|
|
460
|
+
const version = this.loadVersion = (this.loadVersion || 0) + 1
|
|
461
|
+
this.visibleCounts = this.initialVisibleCounts(this.props)
|
|
462
|
+
this.historyBusy = true
|
|
463
|
+
this.evRefs = {}
|
|
464
|
+
this.setState({error: false, historyError: false, historyLoading: false, isShowTyping: false, loading: cid && !sb.size(this.props.store.listEvents(cid)) ? sb.Date.now() : false})
|
|
465
|
+
try {
|
|
466
|
+
if (cid && !sb.size(this.props.store.listEvents(cid))) {
|
|
467
|
+
const result = await this.fetchPage(cid)
|
|
468
|
+
if (version !== this.loadVersion || !this._isMounted) return
|
|
469
|
+
if (result && result.error) this.setState({error: !sb.size(this.props.store.listEvents(cid)), historyError: true})
|
|
470
|
+
}
|
|
471
|
+
} catch (_) {
|
|
472
|
+
if (version === this.loadVersion) this.setState({error: !sb.size(this.props.store.listEvents(cid)), historyError: true})
|
|
473
|
+
} finally {
|
|
474
|
+
if (version === this.loadVersion && this._isMounted) {
|
|
475
|
+
this.historyBusy = false
|
|
476
|
+
this.setState({loading: false})
|
|
477
|
+
if (!this.initialBottom && this.receivedMessageKeys.size && this.isBottom()) this.scrollToBottom('smooth')
|
|
478
|
+
this.queueHistoryCheck()
|
|
479
|
+
// Fetch the previous conversation on opening a draft, even if the greeting fills the viewport.
|
|
480
|
+
if (cid && cid.startsWith('cslightwidget_') && Object.keys(this.visibleCounts).length === 1 && this.hasMore()) this.loadMore()
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async loadMore() {
|
|
486
|
+
if (this.historyBusy || !this._isMounted) return
|
|
487
|
+
const target = this.historyTarget()
|
|
488
|
+
if (!target) return
|
|
489
|
+
const version = this.loadVersion
|
|
490
|
+
this.historyBusy = true
|
|
491
|
+
const container = this.refBody
|
|
492
|
+
const visible = this.visibleCounts[target.id] || 0
|
|
493
|
+
this.setState({historyLoading: true})
|
|
494
|
+
try {
|
|
495
|
+
if (visible >= sb.size(this.props.store.listEvents(target.id))) {
|
|
496
|
+
const result = await this.fetchPage(target.id)
|
|
497
|
+
if (result && result.error) throw result.error
|
|
498
|
+
}
|
|
499
|
+
if (version !== this.loadVersion || !this._isMounted) return
|
|
500
|
+
this.visibleCounts[target.id] = visible + 30
|
|
501
|
+
this.setState({historyError: false})
|
|
502
|
+
} catch (_) {
|
|
503
|
+
if (version === this.loadVersion) this.setState({historyError: true})
|
|
504
|
+
} finally {
|
|
505
|
+
if (version === this.loadVersion && this._isMounted) {
|
|
506
|
+
// The reader may have scrolled or sent a message while the request was pending.
|
|
507
|
+
const height = container ? container.scrollHeight : 0
|
|
508
|
+
const top = container ? container.scrollTop : 0
|
|
509
|
+
this.setState({historyLoading: false})
|
|
271
510
|
this.forceUpdate()
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
511
|
+
await new Promise(resolve => sb.setTimeout(resolve, 0))
|
|
512
|
+
if (version === this.loadVersion && this._isMounted) {
|
|
513
|
+
if (this.initialBottom) this.restoreInitialBottom()
|
|
514
|
+
else if (container && container === this.refBody) {
|
|
515
|
+
container.scrollTop = top + container.scrollHeight - height
|
|
516
|
+
this.lastScrollTop = container.scrollTop
|
|
517
|
+
}
|
|
518
|
+
this.historyBusy = false
|
|
519
|
+
this.queueHistoryCheck()
|
|
520
|
+
}
|
|
281
521
|
}
|
|
282
522
|
}
|
|
283
|
-
this.props.store.fetchMoreEvents(this.props.cid)
|
|
284
|
-
this.setState({error: false})
|
|
285
|
-
this.scrollToBottom()
|
|
286
|
-
// sb.setTimeout(() => this.setState({loading: false}), 50)
|
|
287
523
|
}
|
|
288
524
|
|
|
289
525
|
isBottom() {
|
|
@@ -292,15 +528,21 @@ export default class Events extends Com {
|
|
|
292
528
|
return $container.scrollHeight < $container.scrollTop + $container.offsetHeight + 50
|
|
293
529
|
}
|
|
294
530
|
|
|
295
|
-
scrollToBottom() {
|
|
296
|
-
sb.
|
|
531
|
+
scrollToBottom(behavior = 'auto') {
|
|
532
|
+
if (this.scrollTimer) sb.clearTimeout(this.scrollTimer)
|
|
533
|
+
const cid = this.props.cid
|
|
534
|
+
this.scrollTimer = sb.setTimeout(() => {
|
|
535
|
+
this.scrollTimer = null
|
|
536
|
+
if (!this._isMounted || this.props.cid !== cid) return
|
|
297
537
|
const $div = this.refBody
|
|
298
|
-
if (
|
|
538
|
+
if (!$div) return
|
|
539
|
+
const reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
540
|
+
if (behavior === 'smooth' && !reducedMotion && $div.scrollTo) {
|
|
541
|
+
$div.scrollTo({top: $div.scrollHeight, behavior: 'smooth'})
|
|
542
|
+
} else {
|
|
543
|
+
$div.scrollTop = $div.scrollHeight
|
|
544
|
+
}
|
|
299
545
|
})
|
|
300
|
-
sb.setTimeout(() => {
|
|
301
|
-
const $div = this.refBody
|
|
302
|
-
if ($div) $div.scrollTop = $div.scrollHeight
|
|
303
|
-
}, 100)
|
|
304
546
|
|
|
305
547
|
if (this.props.isShow) {
|
|
306
548
|
if (this.props.store.browserVisibility === 'visible' && document.hasFocus()) {
|
|
@@ -312,76 +554,6 @@ export default class Events extends Com {
|
|
|
312
554
|
|
|
313
555
|
// [{ev: {}, mode: 'time'}]
|
|
314
556
|
//_mode = time
|
|
315
|
-
//_mode = lead
|
|
316
|
-
function processEvents(convo, events) {
|
|
317
|
-
events = events.filter((event) => visibleEvent(event.ev))
|
|
318
|
-
if (events.length < 1) return
|
|
319
|
-
|
|
320
|
-
// show time for the first msg sent
|
|
321
|
-
// insert time
|
|
322
|
-
var lastAgentEvent
|
|
323
|
-
for (var i = 1; i < events.length; i++) {
|
|
324
|
-
let curEvent = events[i]
|
|
325
|
-
let lastEvent = events[i - 1]
|
|
326
|
-
if (sb.get(curEvent, 'ev.by.type') != 'user' && !curEvent.is_typing) {
|
|
327
|
-
lastAgentEvent = curEvent
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (visibleEvent(curEvent.ev)) {
|
|
331
|
-
let elapsedSec = sb.unixSec(curEvent.ev.created) - sb.unixSec(lastEvent.ev.created)
|
|
332
|
-
if (elapsedSec > 300) curEvent.show_time = true // 5 mins
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (lastAgentEvent) lastAgentEvent.show_time = true
|
|
337
|
-
|
|
338
|
-
for (var i = 0; i < events.length; i++) {
|
|
339
|
-
let curEvent = events[i]
|
|
340
|
-
if (sb.get(curEvent, 'ev.type') === 'message_sent') {
|
|
341
|
-
curEvent.show_time = true
|
|
342
|
-
break
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
let lastMsgSentIdx = -1
|
|
347
|
-
// insert lead
|
|
348
|
-
for (var i = 0; i < events.length; i++) {
|
|
349
|
-
let curEvent = events[i]
|
|
350
|
-
let prevEvent = events[i - 1]
|
|
351
|
-
let nextEvent = events[i + 1]
|
|
352
|
-
|
|
353
|
-
if (curEvent.ev.type === 'message_sent') {
|
|
354
|
-
// ignore fake typing message_sent event at last
|
|
355
|
-
lastMsgSentIdx = i
|
|
356
|
-
curEvent.mode = 'lead'
|
|
357
|
-
if (
|
|
358
|
-
sb.get(prevEvent, 'ev.type') === 'message_sent' &&
|
|
359
|
-
sb.get(prevEvent, 'ev.by.id') === sb.get(curEvent, 'ev.by.id')
|
|
360
|
-
) {
|
|
361
|
-
curEvent.mode = 'follow'
|
|
362
|
-
}
|
|
363
|
-
if (
|
|
364
|
-
sb.get(nextEvent, 'ev.type') !== 'message_sent' ||
|
|
365
|
-
sb.get(nextEvent, 'ev.by.id') !== sb.get(curEvent, 'ev.by.id')
|
|
366
|
-
) {
|
|
367
|
-
curEvent.is_end = true
|
|
368
|
-
}
|
|
369
|
-
continue
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (lastMsgSentIdx > -1) {
|
|
373
|
-
events[lastMsgSentIdx].is_last_message_send = true
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function visibleEvent(ev) {
|
|
378
|
-
let type = sb.get(ev, 'type')
|
|
379
|
-
if (type === 'message_sent') return true
|
|
380
|
-
if (type === 'conversation_state_updated') {
|
|
381
|
-
return sb.get(ev, 'data.conversation.state') == 'ended'
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
557
|
function isSummited(form) {
|
|
386
558
|
let fields = sb.get(form, 'fields') || []
|
|
387
559
|
return sb.find(fields, (field) => !!field.answered)
|
|
@@ -191,7 +191,7 @@ export default class FormAttachment extends Com {
|
|
|
191
191
|
}}
|
|
192
192
|
type="checkbox"
|
|
193
193
|
id={'checkbox.' + this.props.ev.id + '.' + field.name}
|
|
194
|
-
onChange={e => this.onInputChange(index,
|
|
194
|
+
onChange={e => this.onInputChange(index, e.target.checked)}
|
|
195
195
|
checked={field.value}
|
|
196
196
|
/>
|
|
197
197
|
<label class="checkbox--label" for={'checkbox.' + this.props.ev.id + '.' + field.name}>
|
package/src/chatbox/Header.js
CHANGED
|
@@ -223,13 +223,13 @@ Vui lòng hỏi chúng tôi bất cứ điều gì hoặc chia sẻ phản hồi
|
|
|
223
223
|
$unreadcount = <span class='widget-header--total-unread-convo'>{unreadcount}</span>
|
|
224
224
|
}
|
|
225
225
|
return (
|
|
226
|
-
<
|
|
226
|
+
<button type='button' aria-label={this.props.t('menu_back')}
|
|
227
227
|
class={`widget-header--wrap-button-back ${unreadcount ? 'widget-header--has-convo-unred' : ''}`}
|
|
228
228
|
onClick={(e) => this.onBackClick(e)}
|
|
229
229
|
>
|
|
230
230
|
<div class='widget-header--button-back' data-tooltip={this.props.t('menu_back')}></div>
|
|
231
231
|
{$unreadcount}
|
|
232
|
-
</
|
|
232
|
+
</button>
|
|
233
233
|
)
|
|
234
234
|
}
|
|
235
235
|
|
|
@@ -238,14 +238,14 @@ Vui lòng hỏi chúng tôi bất cứ điều gì hoặc chia sẻ phản hồi
|
|
|
238
238
|
if (accSetting.hide_close_button) return null
|
|
239
239
|
if (this.props.store.isEmbededChatMode()) return
|
|
240
240
|
return (
|
|
241
|
-
<
|
|
241
|
+
<button type='button' aria-label={this.props.t('close')}
|
|
242
242
|
class='widget-header--button-close'
|
|
243
243
|
data-tooltip={this.props.t('close')}
|
|
244
244
|
data-tooltip-position='bottom'
|
|
245
245
|
onClick={(e) => this.onCloseClick(e)}
|
|
246
246
|
>
|
|
247
247
|
<span class='widget-header--button-close-icon'></span>
|
|
248
|
-
</
|
|
248
|
+
</button>
|
|
249
249
|
)
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -29,7 +29,7 @@ export default class InfoMessage extends Com {
|
|
|
29
29
|
if (ev.type === 'conversation_state_updated' && sb.get(ev, 'data.conversation.state') === 'ended') {
|
|
30
30
|
return (
|
|
31
31
|
<div class='convo-event'>
|
|
32
|
-
<div class='info-message'>{this.props.t('conversation_ended')}</div>
|
|
32
|
+
<div class='info-message info-message--ended'>{this.props.t('conversation_ended')}</div>
|
|
33
33
|
</div>
|
|
34
34
|
)
|
|
35
35
|
}
|