@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.
- package/README.md +21 -2
- package/assets/img/dot_chat.svg +3 -0
- 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 +15 -18
- package/compiled_src/call/css.js +1 -1
- package/compiled_src/chatbox/Buttons.js +7 -4
- package/compiled_src/chatbox/ChatButton.js +127 -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 +7 -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/chatbox/main.js +3 -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/127.js +13 -13
- package/dist/132.js +1 -1
- package/dist/1a0f6f3ff1a.127.js +1 -0
- package/dist/{1a0d80f6e72.132.js → 1a0f6f3ff1a.132.js} +1 -1
- package/dist/{1a0d80f6e72.27.js → 1a0f6f3ff1a.27.js} +1 -1
- package/dist/1a0f6f3ff1a.278.js +1 -0
- package/dist/{1a0d80f6e72.339.js → 1a0f6f3ff1a.339.js} +1 -1
- package/dist/1a0f6f3ff1a.459.js +1 -0
- package/dist/1a0f6f3ff1a.app.js +1 -0
- package/dist/{1a0d80f6e72.chat.html → 1a0f6f3ff1a.chat.html} +1 -1
- package/dist/1a0f6f3ff1a.chat.js +1 -0
- package/dist/{1a0d80f6e72.form.html → 1a0f6f3ff1a.form.html} +1 -1
- package/dist/1a0f6f3ff1a.form.js +1 -0
- package/dist/{1a0d80f6e72.index.html → 1a0f6f3ff1a.index.html} +1 -1
- package/dist/{1a0d80f6e72.library.html → 1a0f6f3ff1a.library.html} +1 -1
- package/dist/1a0f6f3ff1a.library.js +1 -0
- package/dist/{1a0d80f6e72.preview.html → 1a0f6f3ff1a.preview.html} +1 -1
- package/dist/1a0f6f3ff1a.preview.js +1 -0
- package/dist/1a0f6f3ff1a.webview.js +1 -0
- package/dist/27.js +2 -2
- package/dist/459.js +2 -2
- package/dist/HASH +1 -1
- package/dist/assets/img/dot_chat.335fd8a.svg +3 -0
- package/dist/screenshoot.js +1523 -619
- package/package.json +12 -5
- package/src/chatbox/Buttons.js +5 -4
- package/src/chatbox/ChatButton.js +133 -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 +5 -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/chatbox/main.js +3 -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.459.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/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 → 1a0f6f3ff1a.185.js} +0 -0
- /package/dist/{1a0d80f6e72.490.js → 1a0f6f3ff1a.490.js} +0 -0
package/src/chatbox/Widget.js
CHANGED
|
@@ -8,6 +8,7 @@ var checkedimg = require('../../assets/img/done_gray.svg')
|
|
|
8
8
|
var empty = require('../../assets/img/empty.svg')
|
|
9
9
|
import MessageInput from './MessageInput.js'
|
|
10
10
|
import Events from './Events.js'
|
|
11
|
+
import {widgetGreeting} from './greeting.js'
|
|
11
12
|
// import Typing from './Typing.js'
|
|
12
13
|
import Rating from './Rating.js'
|
|
13
14
|
import ConnectionStatus from './ConnectionStatus.js'
|
|
@@ -28,7 +29,6 @@ export default class Widget extends Com {
|
|
|
28
29
|
super(props)
|
|
29
30
|
this.userSent = {}
|
|
30
31
|
|
|
31
|
-
this.debugAgentSentEvIds = {}
|
|
32
32
|
// check user had input form
|
|
33
33
|
this.wasFormInputed = false
|
|
34
34
|
this.setState({
|
|
@@ -50,56 +50,6 @@ export default class Widget extends Com {
|
|
|
50
50
|
if (!this.props.store.isMobile()) this.focusInput()
|
|
51
51
|
|
|
52
52
|
this.updateFormDataFields()
|
|
53
|
-
this.debugRTAgentMessageSent()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
findMissingRTMessage = async (started) => {
|
|
57
|
-
let messageSentDict = this.props.store.listAgentRealtimeMessageSent()
|
|
58
|
-
|
|
59
|
-
let res1 = await this.props.store.sbzListConversations()
|
|
60
|
-
let conversions = sb.get(res1, 'body.conversations') || []
|
|
61
|
-
let cids = sb.map(conversions, (convo) => convo.id)
|
|
62
|
-
|
|
63
|
-
let promises = sb.map(cids, (cid) => this.props.store.sbzListMessages(cid))
|
|
64
|
-
let msgReses = await Promise.all(promises)
|
|
65
|
-
sb.map(msgReses, (msgRes) => {
|
|
66
|
-
let messages = sb.get(msgRes, 'body') || []
|
|
67
|
-
sb.map(messages, (ev) => {
|
|
68
|
-
if (ev.created < started) return
|
|
69
|
-
//if (ev.created > Date.now() - 30_000) return
|
|
70
|
-
if (ev.type !== 'message_sent') return
|
|
71
|
-
if (sb.get(ev, 'by.type') === 'user') return
|
|
72
|
-
|
|
73
|
-
console.log('check ev pass condition', ev)
|
|
74
|
-
if (!messageSentDict[ev.id] && !this.debugAgentSentEvIds[ev.id]) {
|
|
75
|
-
let accid = sb.get(this.props.store.matchAccount(), 'id')
|
|
76
|
-
this.props.store.sendBugReport(
|
|
77
|
-
`[WIDGET ${accid}] Missing agent message sent evid ${ev.id}, cid ${sb.convoOfEv(ev)}, ev_created ${ev.created}, FE timestamp ${Date.now()}, Init Debug timestamp ${started}`,
|
|
78
|
-
)
|
|
79
|
-
this.debugAgentSentEvIds[ev.id] = true
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
debugRTAgentMessageSent = async () => {
|
|
86
|
-
try {
|
|
87
|
-
let debugAccountIds = this.props.store.matchDebugAccountIds()
|
|
88
|
-
let accid = sb.get(this.props.store.matchAccount(), 'id')
|
|
89
|
-
if (!debugAccountIds.includes(accid)) return
|
|
90
|
-
|
|
91
|
-
console.log('START check RT agent message with BE source')
|
|
92
|
-
let started = Date.now()
|
|
93
|
-
this.findMissingRTMessage(started)
|
|
94
|
-
|
|
95
|
-
for (let i = 0; i < 100_000; i++) {
|
|
96
|
-
await sb.sleep(120_000)
|
|
97
|
-
console.log('LOOP check RT agent message with BE source', i)
|
|
98
|
-
this.findMissingRTMessage(started)
|
|
99
|
-
}
|
|
100
|
-
} catch (err) {
|
|
101
|
-
this.props.store.sendBugReport(`debugRTAgentMessageSent ERROR ${err.stack}`)
|
|
102
|
-
}
|
|
103
53
|
}
|
|
104
54
|
|
|
105
55
|
applyMessage(text) {
|
|
@@ -115,7 +65,7 @@ export default class Widget extends Com {
|
|
|
115
65
|
|
|
116
66
|
if (prevProp.cid !== this.props.cid) {
|
|
117
67
|
this.closeOrder()
|
|
118
|
-
this.setState({clickedJoin: false, quickReplyMessage: {}})
|
|
68
|
+
this.setState({clickedJoin: false, restartForm: false, quickReplyMessage: {}})
|
|
119
69
|
sb.setTimeout(() => {
|
|
120
70
|
// focus Events then focus MessageInput
|
|
121
71
|
// if there is any pending form, MessageInput will be disabled
|
|
@@ -128,23 +78,16 @@ export default class Widget extends Com {
|
|
|
128
78
|
// this.updateFormDataFields()
|
|
129
79
|
}
|
|
130
80
|
|
|
81
|
+
componentWillUnmount() {
|
|
82
|
+
this.cancelContainerFocus()
|
|
83
|
+
}
|
|
84
|
+
|
|
131
85
|
ShowTyping() {
|
|
132
86
|
this.$events && this.$events.ShowTyping()
|
|
133
87
|
}
|
|
134
88
|
|
|
135
89
|
shouldShowForm() {
|
|
136
|
-
|
|
137
|
-
let form
|
|
138
|
-
let accountSetting = this.props.plugin.chatbox || {}
|
|
139
|
-
let acc = this.props.store.matchAccount() || {}
|
|
140
|
-
if (sb.get(accountSetting, 'prechat_form.enabled')) form = accountSetting.prechat_form
|
|
141
|
-
|
|
142
|
-
let offlineEnabled = sb.get(accountSetting, 'offline_form.enabled')
|
|
143
|
-
if (this.isOffline(acc, accountSetting) && offlineEnabled) form = accountSetting.offline_form
|
|
144
|
-
|
|
145
|
-
if (!this.hasSentMessage(this.props.cid) && form && (sb.size(form.groups) || form.group_required)) return true
|
|
146
|
-
let formRequired = this.isFormRequired() // nhap thieu truong require
|
|
147
|
-
return (formRequired && !this.props.cid) || (formRequired && this.props.cid && this.state.clickedJoin)
|
|
90
|
+
return !!(this.state.restartForm || this.state.clickedJoin)
|
|
148
91
|
}
|
|
149
92
|
|
|
150
93
|
render() {
|
|
@@ -226,38 +169,18 @@ export default class Widget extends Com {
|
|
|
226
169
|
|
|
227
170
|
if (
|
|
228
171
|
field.is_required &&
|
|
229
|
-
(field.value
|
|
172
|
+
(field.value == null || (typeof field.value === 'string' && !field.value.trim()) || (sb.size(field.value) === 0 && Array.isArray(field.value)))
|
|
230
173
|
) {
|
|
231
174
|
field.is_empty_error = true
|
|
232
175
|
field.error = this.props.t('error_empty')
|
|
233
176
|
} else field.error = ''
|
|
234
177
|
|
|
235
|
-
if (key
|
|
236
|
-
|
|
237
|
-
if (!sb.isEmail(field.value)) {
|
|
238
|
-
field.error = this.props.t('error_email_invalid') // 'The email is not valid'
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
let value = field.value || ''
|
|
242
|
-
if (typeof value !== 'string') value = value + '' // try to makesure use ''.trim doesnt errror
|
|
243
|
-
if (value.trim() && !sb.isEmail(field.value)) {
|
|
244
|
-
field.error = this.props.t('error_email_invalid') // 'The email is not valid'
|
|
245
|
-
}
|
|
246
|
-
}
|
|
178
|
+
if (key === 'attr:emails' && !field.error && String(field.value ?? '').trim() && !sb.isEmail(field.value)) {
|
|
179
|
+
field.error = this.props.t('error_email_invalid')
|
|
247
180
|
}
|
|
248
181
|
|
|
249
|
-
if (key
|
|
250
|
-
|
|
251
|
-
if (!sb.isPhone(field.value)) {
|
|
252
|
-
field.error = this.props.t('error_phone_invalid') // 'The email is not valid'
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
let value = field.value || ''
|
|
256
|
-
if (typeof value !== 'string') value = value + '' // try to makesure use ''.trim doesnt errror
|
|
257
|
-
if (value.trim() && !sb.isPhone(field.value)) {
|
|
258
|
-
field.error = this.props.t('error_phone_invalid') // 'The email is not valid'
|
|
259
|
-
}
|
|
260
|
-
}
|
|
182
|
+
if (key === 'attr:phones' && !field.error && String(field.value ?? '').trim() && !sb.isPhone(field.value)) {
|
|
183
|
+
field.error = this.props.t('error_phone_invalid')
|
|
261
184
|
}
|
|
262
185
|
newformdata[key] = field
|
|
263
186
|
})
|
|
@@ -265,7 +188,7 @@ export default class Widget extends Com {
|
|
|
265
188
|
}
|
|
266
189
|
|
|
267
190
|
async submitPrechatForm(form, formName) {
|
|
268
|
-
if (this.state.submiting) return
|
|
191
|
+
if (this.state.submiting || this.state.creating) return
|
|
269
192
|
// check error first
|
|
270
193
|
let newData = this.validateForm(this.state.formData)
|
|
271
194
|
let hasError = sb.find(newData, (field) => !!field.error)
|
|
@@ -282,6 +205,8 @@ export default class Widget extends Com {
|
|
|
282
205
|
let is_lead = false
|
|
283
206
|
sb.map(data, (field, key) => {
|
|
284
207
|
if (!key.startsWith('attr:')) return
|
|
208
|
+
if (key === 'attr:phones') field.value = String(field.value ?? '').trim().replace(/[(). \-]/g, '')
|
|
209
|
+
if (key === 'attr:emails') field.value = String(field.value ?? '').trim()
|
|
285
210
|
var attr = {key: key.substr(5)}
|
|
286
211
|
is_lead = is_lead || key == 'emails' || key == 'phones'
|
|
287
212
|
if (field.type === 'number') attr.number = parseInt(field.value)
|
|
@@ -293,7 +218,8 @@ export default class Widget extends Com {
|
|
|
293
218
|
|
|
294
219
|
if (is_lead) sb.setUserAttr(user, {key: 'lead_source', text: 'widget'})
|
|
295
220
|
|
|
296
|
-
|
|
221
|
+
const question = String(sb.get(data, 'question.value', '') ?? '').trim()
|
|
222
|
+
const firstmsg = question || (/^vi(?:[-_]|$)/i.test(navigator.language || '') ? 'Xin chào' : 'Hello')
|
|
297
223
|
let groupid = sb.get(data, 'group.value', '')
|
|
298
224
|
|
|
299
225
|
// creating convo
|
|
@@ -309,15 +235,15 @@ export default class Widget extends Com {
|
|
|
309
235
|
|
|
310
236
|
if (_creating != this._creating) return // outdated
|
|
311
237
|
if (this.props.cid && this.props.cid.startsWith('cslight')) {
|
|
312
|
-
this.sendMessage({
|
|
238
|
+
await this.sendMessage({
|
|
313
239
|
text: firstmsg,
|
|
314
240
|
format: 'plaintext',
|
|
315
241
|
fields: [{key: 'to_agent_group', value: sb.stringify(groupid)}],
|
|
316
242
|
attachments: this.state.quickReplyMessage.attachments,
|
|
317
|
-
})
|
|
243
|
+
}, {formSubmitted: true, groupId: groupid})
|
|
318
244
|
return
|
|
319
245
|
}
|
|
320
|
-
if (this.props.cid) {
|
|
246
|
+
if (this.props.cid && sb.get(this.props.store.matchConvo(this.props.cid), 'state') !== 'ended') {
|
|
321
247
|
// this.props.store.setUser(user)
|
|
322
248
|
this.setState({clickedJoin: false})
|
|
323
249
|
// form will auto disappear after rerender
|
|
@@ -339,7 +265,7 @@ export default class Widget extends Com {
|
|
|
339
265
|
return
|
|
340
266
|
}
|
|
341
267
|
|
|
342
|
-
let message = {text: firstmsg, format: 'plaintext'}
|
|
268
|
+
let message = {text: firstmsg, format: 'plaintext', attachments: this.state.quickReplyMessage.attachments}
|
|
343
269
|
let req = {group_id: groupid, message: firstmsg, locale: this.props.t.locale(), initial_by: user.id}
|
|
344
270
|
|
|
345
271
|
this.setState({creatingError: null})
|
|
@@ -355,9 +281,12 @@ export default class Widget extends Com {
|
|
|
355
281
|
this.userSent[newconvoid] = true
|
|
356
282
|
await this.props.store.sendConvoEvent(newconvoid, 'message_sent', {message})
|
|
357
283
|
if (_creating != this._creating) return // outdated
|
|
284
|
+
if (form.end_conversation_after_submit) await this.props.store.endConvo(newconvoid)
|
|
285
|
+
if (_creating != this._creating) return
|
|
358
286
|
this.setState({
|
|
359
287
|
creating: false,
|
|
360
288
|
clickedJoin: false,
|
|
289
|
+
restartForm: false,
|
|
361
290
|
quickReplyMessage: {},
|
|
362
291
|
})
|
|
363
292
|
this.props.onGo({view: 'chat', cid: newconvoid})
|
|
@@ -370,7 +299,7 @@ export default class Widget extends Com {
|
|
|
370
299
|
if (form.question_required || this.state.quickReplyMessage.text) {
|
|
371
300
|
formData['question'] = {
|
|
372
301
|
key: 'question',
|
|
373
|
-
is_required:
|
|
302
|
+
is_required: false,
|
|
374
303
|
label: this.props.t.i18n(sb.get(form, `i18n_question_label`, {})) || form.question_label,
|
|
375
304
|
type: 'text',
|
|
376
305
|
value: this.state.quickReplyMessage.text,
|
|
@@ -404,6 +333,7 @@ export default class Widget extends Com {
|
|
|
404
333
|
if (!field) return
|
|
405
334
|
field.value = val
|
|
406
335
|
field.error = undefined
|
|
336
|
+
field.is_empty_error = false
|
|
407
337
|
this.setState({formData})
|
|
408
338
|
}
|
|
409
339
|
|
|
@@ -546,8 +476,8 @@ export default class Widget extends Com {
|
|
|
546
476
|
renderFields(form, formName) {
|
|
547
477
|
if (sb.size(this.state.formData) === 0) return null
|
|
548
478
|
let $fields = sb.map(this.state.formData, (field, key) => {
|
|
549
|
-
//
|
|
550
|
-
if (this.state.quickReplyMessage.
|
|
479
|
+
// Bot quick replies already display their selected answer.
|
|
480
|
+
if (key === 'question' && sb.find(this.state.quickReplyMessage.attachments, att => att.type === 'postback')) return null
|
|
551
481
|
|
|
552
482
|
let $field = null
|
|
553
483
|
var errclass = field.error ? 'input--error' : ''
|
|
@@ -584,7 +514,11 @@ export default class Widget extends Com {
|
|
|
584
514
|
this['form_' + encode(key)] = ref
|
|
585
515
|
}}
|
|
586
516
|
class={`form-control ${errclass}`}
|
|
587
|
-
type='text'
|
|
517
|
+
type={key === 'attr:phones' ? 'tel' : key === 'attr:emails' ? 'email' : 'text'}
|
|
518
|
+
inputMode={key === 'attr:phones' ? 'tel' : key === 'attr:emails' ? 'email' : undefined}
|
|
519
|
+
autoComplete={key === 'attr:phones' ? 'tel' : key === 'attr:emails' ? 'email' : undefined}
|
|
520
|
+
aria-invalid={field.error ? 'true' : undefined}
|
|
521
|
+
aria-describedby={field.error ? 'form-error_' + encode(key) : undefined}
|
|
588
522
|
/>
|
|
589
523
|
)
|
|
590
524
|
}
|
|
@@ -680,7 +614,7 @@ export default class Widget extends Com {
|
|
|
680
614
|
{field.is_required ? <span class='red'>*</span> : ''}
|
|
681
615
|
</label>
|
|
682
616
|
{$field}
|
|
683
|
-
{field.error ? <div class='form-control--valid-input--error'>{field.error}</div> : ''}
|
|
617
|
+
{field.error ? <div id={'form-error_' + encode(key)} role='alert' class='form-control--valid-input--error'>{field.error}</div> : ''}
|
|
684
618
|
</div>
|
|
685
619
|
)
|
|
686
620
|
})
|
|
@@ -693,9 +627,10 @@ export default class Widget extends Com {
|
|
|
693
627
|
// replace ↵ character
|
|
694
628
|
title = title.replace('↵', '')
|
|
695
629
|
let btncls = 'btn--start-convo '
|
|
696
|
-
let hasError = sb.find(this.state.formData, (field) => !!field.error)
|
|
697
630
|
let newData = this.validateForm(this.state.formData)
|
|
698
631
|
let hasEmptyError = sb.find(newData, (field) => !!field.is_empty_error)
|
|
632
|
+
const phone = String(sb.get(this.state.formData, 'attr:phones.value', '') ?? '').trim()
|
|
633
|
+
const phoneTooShort = phone.length > 0 && phone.length < 5
|
|
699
634
|
let bodycls = 'widget-body'
|
|
700
635
|
if (this.props.store.isEmbededChatMode()) bodycls += ' embed-chat'
|
|
701
636
|
return (
|
|
@@ -714,7 +649,7 @@ export default class Widget extends Com {
|
|
|
714
649
|
<button
|
|
715
650
|
class={btncls}
|
|
716
651
|
onClick={(_) => this.submitPrechatForm(form, formName)}
|
|
717
|
-
disabled={!!hasEmptyError ||
|
|
652
|
+
disabled={!!hasEmptyError || phoneTooShort || this.state.creating}
|
|
718
653
|
>
|
|
719
654
|
{submitLabel}
|
|
720
655
|
</button>
|
|
@@ -724,7 +659,19 @@ export default class Widget extends Com {
|
|
|
724
659
|
}
|
|
725
660
|
|
|
726
661
|
goToConvo(cid) {
|
|
727
|
-
|
|
662
|
+
let view = 'chat'
|
|
663
|
+
if (!cid) {
|
|
664
|
+
const greeting = widgetGreeting(this.props.plugin.chatbox, this.props.t)
|
|
665
|
+
cid = this.props.store.startWidgetConvo(greeting, this.props.t.locale()).convo.id
|
|
666
|
+
const form = this.startingConversationForm()
|
|
667
|
+
const setting = this.props.plugin.chatbox || {}
|
|
668
|
+
const offline = sb.get(setting, 'offline_form.enabled') && this.isOffline(this.props.store.matchAccount() || {}, setting)
|
|
669
|
+
if (form && (offline || this.isFormRequired(cid) || sb.size(form.groups) || form.group_required)) {
|
|
670
|
+
view = offline ? 'offline_form' : 'prechat_form'
|
|
671
|
+
this.setState({clickedJoin: false, restartForm: false, quickReplyMessage: {}})
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
this.props.onGo({view, cid})
|
|
728
675
|
}
|
|
729
676
|
|
|
730
677
|
renderConvoList() {
|
|
@@ -794,7 +741,7 @@ export default class Widget extends Com {
|
|
|
794
741
|
sb.setTimeout(() => $input && $input.blur(), 300) // force lose focus
|
|
795
742
|
}
|
|
796
743
|
|
|
797
|
-
focusInput(text) {
|
|
744
|
+
focusInput(text, repeat = true) {
|
|
798
745
|
if (this.props.store.isPreview) return
|
|
799
746
|
if (this.shouldShowForm()) {
|
|
800
747
|
// focus on the element
|
|
@@ -821,6 +768,7 @@ export default class Widget extends Com {
|
|
|
821
768
|
if (text != undefined && typeof text === 'string') {
|
|
822
769
|
$input.applyMessage(text)
|
|
823
770
|
}
|
|
771
|
+
if (!repeat) return $input.focus()
|
|
824
772
|
sb.setTimeout(() => $input.focus())
|
|
825
773
|
sb.setTimeout(() => $input.focus(), 300) // force focus
|
|
826
774
|
}
|
|
@@ -837,16 +785,72 @@ export default class Widget extends Com {
|
|
|
837
785
|
this.setState({formData, quickReplyMessage: message})
|
|
838
786
|
}
|
|
839
787
|
|
|
840
|
-
sendMessage = async (message) => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
this.
|
|
845
|
-
|
|
788
|
+
sendMessage = async (message, {formSubmitted = false, groupId} = {}) => {
|
|
789
|
+
if (this._sendingNewConversation) return false
|
|
790
|
+
if (sb.get(this.props.store.matchConvo(this.props.cid), 'state') === 'ended') {
|
|
791
|
+
const form = this.startingConversationForm()
|
|
792
|
+
if (form && (sb.size(form.groups) || form.group_required || this.isFormRequired(''))) {
|
|
793
|
+
this.setState({restartForm: true, quickReplyMessage: message})
|
|
794
|
+
return
|
|
795
|
+
}
|
|
796
|
+
this._sendingNewConversation = true
|
|
797
|
+
this.setState({creating: true, creatingError: null})
|
|
798
|
+
try {
|
|
799
|
+
const {convo, error} = await this.props.store.startConvo({
|
|
800
|
+
locale: this.props.t.locale(),
|
|
801
|
+
message: message.text,
|
|
802
|
+
initial_by: this.props.store.matchUser().id,
|
|
803
|
+
})
|
|
804
|
+
if (error) {
|
|
805
|
+
this.setState({creatingError: error})
|
|
806
|
+
return false
|
|
807
|
+
}
|
|
808
|
+
await this.props.store.sendConvoEvent(convo.id, 'message_sent', {message})
|
|
809
|
+
this.props.onGo({view: 'chat', cid: convo.id})
|
|
810
|
+
} finally {
|
|
811
|
+
this._sendingNewConversation = false
|
|
812
|
+
this.setState({creating: false})
|
|
813
|
+
}
|
|
814
|
+
return
|
|
815
|
+
}
|
|
816
|
+
const form = this.startingConversationForm()
|
|
817
|
+
const needsGroup = form && !this.hasSentMessage(this.props.cid) && (sb.size(form.groups) || form.group_required)
|
|
818
|
+
if (!formSubmitted && (this.isFormRequired() || needsGroup)) {
|
|
819
|
+
this.setState({clickedJoin: true, quickReplyMessage: message})
|
|
846
820
|
return
|
|
847
821
|
}
|
|
848
822
|
|
|
849
823
|
let text = message.text
|
|
824
|
+
const localConvo = this.props.store.matchConvo(this.props.cid)
|
|
825
|
+
if (localConvo && localConvo.light_type === 'widget') {
|
|
826
|
+
this._sendingNewConversation = true
|
|
827
|
+
this.setState({creating: true, creatingError: null})
|
|
828
|
+
try {
|
|
829
|
+
const {convo, error} = await this.props.store.startConvo({
|
|
830
|
+
locale: localConvo.locale, message: text, group_id: groupId,
|
|
831
|
+
initial_by: this.props.store.matchUser().id,
|
|
832
|
+
greeting_message: localConvo.events[0],
|
|
833
|
+
})
|
|
834
|
+
if (error || !convo) {
|
|
835
|
+
this.setState({creatingError: error || 'start_failed'})
|
|
836
|
+
return false
|
|
837
|
+
}
|
|
838
|
+
this.props.store.promoteWidgetConvo(localConvo.id, convo.id)
|
|
839
|
+
const sending = this.props.store.sendConvoEvent(convo.id, 'message_sent', {message})
|
|
840
|
+
this.props.onGo({view: 'chat', cid: convo.id})
|
|
841
|
+
await sending
|
|
842
|
+
this.userSent[convo.id] = true
|
|
843
|
+
if (formSubmitted && form && form.end_conversation_after_submit) await this.props.store.endConvo(convo.id)
|
|
844
|
+
this.setState({clickedJoin: false, restartForm: false, quickReplyMessage: {}})
|
|
845
|
+
} catch (error) {
|
|
846
|
+
this.setState({creatingError: error})
|
|
847
|
+
return false
|
|
848
|
+
} finally {
|
|
849
|
+
this._sendingNewConversation = false
|
|
850
|
+
this.setState({creating: false})
|
|
851
|
+
}
|
|
852
|
+
return
|
|
853
|
+
}
|
|
850
854
|
|
|
851
855
|
if (this.props.cid && this.props.cid.startsWith('cslight')) {
|
|
852
856
|
let cid = this.props.cid
|
|
@@ -896,9 +900,10 @@ export default class Widget extends Com {
|
|
|
896
900
|
return
|
|
897
901
|
// show error and try again
|
|
898
902
|
}
|
|
899
|
-
|
|
900
|
-
if (_creating != this._creating) return // outdated
|
|
903
|
+
const sending = this.props.store.sendConvoEvent(convo.id, 'message_sent', {message})
|
|
901
904
|
this.props.onGo({view: 'chat', cid: convo.id})
|
|
905
|
+
await sending
|
|
906
|
+
if (_creating != this._creating) return // outdated
|
|
902
907
|
this.setState({creating: false})
|
|
903
908
|
return
|
|
904
909
|
}
|
|
@@ -929,21 +934,11 @@ export default class Widget extends Com {
|
|
|
929
934
|
t={this.props.t}
|
|
930
935
|
/>
|
|
931
936
|
)
|
|
932
|
-
let convo = this.props.store.matchConvo(this.props.cid) || {}
|
|
933
|
-
if (this.isFormRequired() || convo.state === 'ended') {
|
|
934
|
-
$input = (
|
|
935
|
-
<div class='message-input message-input--preform'>
|
|
936
|
-
<button class='btn--start-convo' onClick={(e) => this.onStartConvoClick(e)}>
|
|
937
|
-
{this.props.t('offline_preform_btn')}
|
|
938
|
-
</button>
|
|
939
|
-
</div>
|
|
940
|
-
)
|
|
941
|
-
}
|
|
942
937
|
let bodycls = 'widget-body'
|
|
943
938
|
if (this.props.store.isEmbededChatMode()) bodycls += ' embed-chat'
|
|
944
939
|
return (
|
|
945
940
|
<div class={bodycls}>
|
|
946
|
-
<div class='widget-body--content' onClick={(e) => this.onContainerClick(e)}>
|
|
941
|
+
<div class='widget-body--content' onPointerDown={() => this.cancelContainerFocus()} onClick={(e) => this.onContainerClick(e)}>
|
|
947
942
|
<Events
|
|
948
943
|
isShow={this.props.isShow}
|
|
949
944
|
onLink={this.onLink}
|
|
@@ -952,7 +947,7 @@ export default class Widget extends Com {
|
|
|
952
947
|
onFormSubmit={(e) => this.onEventFormSubmit(e)}
|
|
953
948
|
ref={(ref) => (this.$events = ref)}
|
|
954
949
|
cid={this.props.cid}
|
|
955
|
-
creating={this.creating}
|
|
950
|
+
creating={this.state.creating}
|
|
956
951
|
onSend={(e) => this.sendMessage(e)}
|
|
957
952
|
onPostback={(e) => this.createPostback(e)}
|
|
958
953
|
t={this.props.t}
|
|
@@ -1004,48 +999,53 @@ export default class Widget extends Com {
|
|
|
1004
999
|
this.focusInput()
|
|
1005
1000
|
}
|
|
1006
1001
|
|
|
1007
|
-
// end or visit unsent convo which require form
|
|
1008
|
-
async onStartConvoClick() {
|
|
1009
|
-
var convo = this.props.store.matchConvo(this.props.cid) || {}
|
|
1010
|
-
if (convo.state === 'ended') return this.props.onGo({view: 'chat', cid: ''})
|
|
1011
|
-
this.setState({clickedJoin: true, quickReplyMessage: {}})
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
1002
|
onEventFormSubmit() {
|
|
1015
1003
|
this.focusInput()
|
|
1016
1004
|
}
|
|
1017
1005
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1006
|
+
cancelContainerFocus() {
|
|
1007
|
+
if (this.containerFocusTimer) sb.clearTimeout(this.containerFocusTimer)
|
|
1008
|
+
this.containerFocusTimer = null
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
onContainerClick(event) {
|
|
1012
|
+
this.cancelContainerFocus()
|
|
1013
|
+
const target = event && event.target
|
|
1014
|
+
const element = target && (target.nodeType === 1 ? target : target.parentElement)
|
|
1015
|
+
if (element && element.closest('a, button, input, textarea, select, [contenteditable]')) return
|
|
1016
|
+
if (event && event.detail > 1) return
|
|
1017
|
+
if (this.props.store.isMobile() || window.getSelection()?.toString()) return
|
|
1018
|
+
const cid = this.props.cid
|
|
1019
|
+
// Give a second click or a drag time to select text before moving focus.
|
|
1020
|
+
this.containerFocusTimer = sb.setTimeout(() => {
|
|
1021
|
+
this.containerFocusTimer = null
|
|
1022
|
+
if (!this._isMounted || this.props.cid !== cid || this.props.isShow === false || window.getSelection()?.toString()) return
|
|
1023
|
+
this.focusInput(undefined, false)
|
|
1024
|
+
}, 300)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
startingConversationForm() {
|
|
1028
|
+
const setting = this.props.plugin.chatbox || {}
|
|
1029
|
+
const account = this.props.store.matchAccount() || {}
|
|
1030
|
+
if (this.isOffline(account, setting) && sb.get(setting, 'offline_form.enabled')) return setting.offline_form
|
|
1031
|
+
if (sb.get(setting, 'prechat_form.enabled')) return setting.prechat_form
|
|
1024
1032
|
}
|
|
1025
1033
|
|
|
1026
|
-
isFormRequired() {
|
|
1034
|
+
isFormRequired(cid = this.props.cid) {
|
|
1027
1035
|
// neu isRequiredForm = true nhung form = null => show button khi click vao thi show form
|
|
1028
1036
|
|
|
1029
1037
|
// isRequiredForm tra ve true neu (nguoi dung chua tung tra loi)
|
|
1030
1038
|
// + hoi thoai da dc phan phoi nhung chua nhap du cac truong (nho exclude group, bo qua ca truong message)
|
|
1031
1039
|
// + hoi thoai moi hoac chua duoc phan phoi va chua nhap du cac truong (dac biet la group). Neu k co group, ma cac truong da dc nhap du, ko quan tam toi da nhap msg hay chua, isrequiredform = false
|
|
1032
1040
|
|
|
1033
|
-
let form
|
|
1034
|
-
// choose form to display
|
|
1035
|
-
let accountSetting = this.props.plugin.chatbox || {}
|
|
1036
|
-
let acc = this.props.store.matchAccount() || {}
|
|
1037
|
-
if (sb.get(accountSetting, 'prechat_form.enabled')) form = accountSetting.prechat_form
|
|
1038
|
-
|
|
1039
|
-
//auto detect form
|
|
1040
|
-
let offlineEnabled = sb.get(accountSetting, 'offline_form.enabled')
|
|
1041
|
-
if (this.isOffline(acc, accountSetting) && offlineEnabled) form = accountSetting.offline_form
|
|
1041
|
+
let form = this.startingConversationForm()
|
|
1042
1042
|
|
|
1043
1043
|
if (!form) return false
|
|
1044
1044
|
|
|
1045
1045
|
let formData = this.fillForm(form)
|
|
1046
1046
|
|
|
1047
1047
|
// dont care about group if the conversation is assigned
|
|
1048
|
-
let convo = this.props.store.matchConvo(
|
|
1048
|
+
let convo = this.props.store.matchConvo(cid) || {}
|
|
1049
1049
|
|
|
1050
1050
|
if (convo.state === 'ended') return true
|
|
1051
1051
|
// allow button "Tiep tuc chat" display when sent quick_replies msg
|
|
@@ -1057,7 +1057,7 @@ export default class Widget extends Com {
|
|
|
1057
1057
|
sb.each(formData, (field) => {
|
|
1058
1058
|
if (field.value) isAllFiledEmpty = false
|
|
1059
1059
|
})
|
|
1060
|
-
if (isAllFiledEmpty && !this.userHasSent(
|
|
1060
|
+
if (isAllFiledEmpty && !this.userHasSent(cid)) return true
|
|
1061
1061
|
|
|
1062
1062
|
return !!sb.find(formData, (field) => field.is_required && (field.value == null || field.value === ''))
|
|
1063
1063
|
}
|
|
@@ -1226,7 +1226,7 @@ export default class Widget extends Com {
|
|
|
1226
1226
|
// add question field
|
|
1227
1227
|
data['question'] = {
|
|
1228
1228
|
key: 'question',
|
|
1229
|
-
is_required:
|
|
1229
|
+
is_required: false,
|
|
1230
1230
|
label: this.props.t.i18n(sb.get(form, `i18n_question_label`, {})) || form.question_label,
|
|
1231
1231
|
type: 'text',
|
|
1232
1232
|
}
|