@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/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subiz/widget-v4",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.212",
|
|
4
4
|
"main": "./compiled_src/lib.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "npx webpack serve --config webpack.dev.js",
|
|
7
|
-
"build": "rm -rf dist &&
|
|
7
|
+
"build": "npm run lib && rm -rf dist && webpack --config webpack.screenshoot.js && webpack --config webpack.prod.js",
|
|
8
8
|
"report": "npx webpack --config webpack.report.js",
|
|
9
|
-
"lib": "
|
|
9
|
+
"lib": "babel src --out-dir compiled_src --ignore \"**/*.test.js,**/*.test2.js\"",
|
|
10
|
+
"test": "node test.cjs",
|
|
11
|
+
"lint": "eslint src",
|
|
12
|
+
"check": "npm run lint && npm test",
|
|
13
|
+
"prepack": "npm run check && npm run lib"
|
|
10
14
|
},
|
|
11
15
|
"dependencies": {
|
|
12
16
|
"@subiz/ajax": "^3.0.22",
|
|
@@ -16,8 +20,8 @@
|
|
|
16
20
|
"core-js": "^3.50.0",
|
|
17
21
|
"css-loader": "5.2.7",
|
|
18
22
|
"file-loader": "^6.2.0",
|
|
19
|
-
"less": "^4.
|
|
20
|
-
"less-loader": "^
|
|
23
|
+
"less": "^4.9.1",
|
|
24
|
+
"less-loader": "^13.0.0",
|
|
21
25
|
"preact": "10.19.3"
|
|
22
26
|
},
|
|
23
27
|
"devDependencies": {
|
|
@@ -28,7 +32,10 @@
|
|
|
28
32
|
"@babel/preset-env": "^7.29.7",
|
|
29
33
|
"babel-loader": "^10.1.1",
|
|
30
34
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
|
35
|
+
"eslint": "^10.11.0",
|
|
36
|
+
"globals": "^17.13.0",
|
|
31
37
|
"html-webpack-plugin": "^5.6.8",
|
|
38
|
+
"jsdom": "^30.1.1",
|
|
32
39
|
"puppeteer": "^25.12.0",
|
|
33
40
|
"tape": "^5.10.2",
|
|
34
41
|
"webpack": "^5.111.1",
|
package/src/chatbox/Buttons.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import sb from '../util/index.js'
|
|
2
2
|
import {h} from 'preact'
|
|
3
|
+
import {safeUrl} from '../util/url.js'
|
|
3
4
|
|
|
4
5
|
export default function Buttons(props) {
|
|
5
6
|
if (sb.size(props.buttons) === 0) return null
|
|
6
7
|
var $buttons = sb.map(props.buttons, (button, index) => {
|
|
7
8
|
if (button.type === 'call_button') {
|
|
8
9
|
return (
|
|
9
|
-
<a class='url-button-attachment' href={'tel:' + button.phone_number} key={index} title={button.title}>
|
|
10
|
+
<a class='url-button-attachment' href={safeUrl('tel:' + button.phone_number)} key={index} title={button.title}>
|
|
10
11
|
{props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || `${props.t('call')} ${button.phone_number}`}
|
|
11
12
|
</a>
|
|
12
13
|
)
|
|
@@ -14,7 +15,7 @@ export default function Buttons(props) {
|
|
|
14
15
|
|
|
15
16
|
if (button.type === 'url_button') {
|
|
16
17
|
return (
|
|
17
|
-
<a class='url-button-attachment' href={button.url} target='_blank' key={index} title={button.title}>
|
|
18
|
+
<a class='url-button-attachment' href={safeUrl(button.url) || undefined} target='_blank' rel='noopener noreferrer' key={index} title={button.title}>
|
|
18
19
|
{props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || button.url}
|
|
19
20
|
</a>
|
|
20
21
|
)
|
|
@@ -22,9 +23,9 @@ export default function Buttons(props) {
|
|
|
22
23
|
|
|
23
24
|
if (button.type === 'postback_button') {
|
|
24
25
|
return (
|
|
25
|
-
<
|
|
26
|
+
<button type='button' class='url-button-attachment' key={index} onClick={(_) => props.onSelect(button)}>
|
|
26
27
|
{props.t.i18n(button.i18n_title) || button.title}
|
|
27
|
-
</
|
|
28
|
+
</button>
|
|
28
29
|
)
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import sb from '../util/index.js'
|
|
2
2
|
import Emoji from './Emoji.js'
|
|
3
3
|
var defaultBubble = require('../../assets/img/bubble_default.svg')
|
|
4
|
+
const dotChatIcon = require('../../assets/img/dot_chat.svg')
|
|
4
5
|
let defaultAvatar = require('../../assets/img/default_avatar.png')
|
|
5
6
|
import {Com} from '../common.js'
|
|
6
7
|
import Block from './block.js'
|
|
@@ -378,30 +379,7 @@ export default class ChatButton extends Com {
|
|
|
378
379
|
)
|
|
379
380
|
}
|
|
380
381
|
|
|
381
|
-
|
|
382
|
-
let accountSetting = this.props.plugin.chatbox
|
|
383
|
-
accountSetting = this.fixMissingSetting(accountSetting)
|
|
384
|
-
let imgsrc =
|
|
385
|
-
sb.get(accountSetting, 'desktop_button.background_image_url') ||
|
|
386
|
-
sb.get(accountSetting, 'desktop_button.background_mask_url') ||
|
|
387
|
-
defaultBubble
|
|
388
|
-
|
|
389
|
-
if (this.props.store.isMobile()) {
|
|
390
|
-
imgsrc =
|
|
391
|
-
sb.get(accountSetting, 'mobile_button.background_image_url') ||
|
|
392
|
-
sb.get(accountSetting, 'mobile_button.background_mask_url') ||
|
|
393
|
-
imgsrc
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
imgsrc = this.props.store.replaceFileUrl(imgsrc)
|
|
397
|
-
await sb.preloadImg(imgsrc)
|
|
398
|
-
this.setState({ready: true})
|
|
399
|
-
this.readyGate.open()
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
renderButton() {
|
|
403
|
-
if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_button = sb.Date.now()
|
|
404
|
-
|
|
382
|
+
getButtonAppearance() {
|
|
405
383
|
let accountSetting = this.props.plugin.chatbox
|
|
406
384
|
accountSetting = this.fixMissingSetting(accountSetting)
|
|
407
385
|
let imgsrc =
|
|
@@ -433,24 +411,94 @@ export default class ChatButton extends Com {
|
|
|
433
411
|
}
|
|
434
412
|
}
|
|
435
413
|
|
|
414
|
+
const imageUrl = (url) => {
|
|
415
|
+
const asset = normalizeButtonImageUrl(this.props.store.replaceFileUrl(url))
|
|
416
|
+
return asset === dotChatIcon ? asset : addVersionParam(asset)
|
|
417
|
+
}
|
|
418
|
+
imgsrc = imageUrl(imgsrc)
|
|
419
|
+
imgurl = imageUrl(imgurl)
|
|
420
|
+
maskurl = imageUrl(maskurl)
|
|
421
|
+
const defaultMask = !imgurl && !maskurl ? DEF_BG_URL : ''
|
|
422
|
+
const urls = [
|
|
423
|
+
...new Set([imgsrc, imgurl && encodeURI(imgurl), maskurl && encodeURI(maskurl), defaultMask].filter(Boolean)),
|
|
424
|
+
]
|
|
425
|
+
return {
|
|
426
|
+
imgsrc,
|
|
427
|
+
imgurl,
|
|
428
|
+
maskurl,
|
|
429
|
+
background,
|
|
430
|
+
position,
|
|
431
|
+
imgwidth,
|
|
432
|
+
imgheight,
|
|
433
|
+
urls,
|
|
434
|
+
maskAsset: maskurl ? encodeURI(maskurl) : defaultMask,
|
|
435
|
+
key: JSON.stringify([urls, maskurl, defaultMask]),
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
componentDidUpdate() {
|
|
440
|
+
if (this.getButtonAppearance().key !== this._buttonImageKey) this.prepareButtonImage()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async prepareButtonImage() {
|
|
444
|
+
const appearance = this.getButtonAppearance()
|
|
445
|
+
const version = (this._buttonImageVersion = (this._buttonImageVersion || 0) + 1)
|
|
446
|
+
this._buttonImageKey = appearance.key
|
|
447
|
+
this.readyGate.close()
|
|
448
|
+
const [loaded, mask] = await Promise.all([
|
|
449
|
+
Promise.all(appearance.urls.filter((url) => url !== appearance.maskAsset).map((url) => preloadButtonImage(url))),
|
|
450
|
+
appearance.maskAsset ? prepareButtonMask(appearance.maskAsset) : Promise.resolve(null),
|
|
451
|
+
])
|
|
452
|
+
const stale = () => !this._isMounted || this._buttonImageVersion !== version
|
|
453
|
+
if (stale()) {
|
|
454
|
+
if (mask) URL.revokeObjectURL(mask)
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
const fallback = loaded.some((success) => !success) || (!!appearance.maskAsset && !mask)
|
|
458
|
+
const ready = !fallback || (await preloadButtonImage(defaultBubble))
|
|
459
|
+
if (stale()) {
|
|
460
|
+
if (mask) URL.revokeObjectURL(mask)
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl)
|
|
464
|
+
this._buttonMaskUrl = mask
|
|
465
|
+
// Commit the hidden style before revealing even when images are cached.
|
|
466
|
+
// Otherwise older browsers can paint only the final state and skip the transition.
|
|
467
|
+
if (this._buttonElement) window.getComputedStyle(this._buttonElement).transform
|
|
468
|
+
this.setState({ready, readyImageKey: appearance.key, fallbackButtonImage: fallback, buttonMaskUrl: mask})
|
|
469
|
+
this.readyGate.open()
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
componentWillUnmount() {
|
|
473
|
+
if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl)
|
|
474
|
+
if (this._animateTimeout) clearTimeout(this._animateTimeout)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
renderButton() {
|
|
478
|
+
if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_button = sb.Date.now()
|
|
479
|
+
const appearance = this.getButtonAppearance()
|
|
480
|
+
let {imgsrc, imgurl, maskurl, background, position, imgwidth, imgheight} = appearance
|
|
481
|
+
// Never attach a remote CSS mask while the local copy is being prepared.
|
|
482
|
+
if (appearance.maskAsset) {
|
|
483
|
+
maskurl = this.state.readyImageKey === appearance.key ? this.state.buttonMaskUrl : ''
|
|
484
|
+
if (imgsrc === appearance.maskAsset) imgsrc = maskurl || ''
|
|
485
|
+
}
|
|
486
|
+
if (this.state.readyImageKey === appearance.key && this.state.fallbackButtonImage) {
|
|
487
|
+
imgsrc = imgurl = defaultBubble
|
|
488
|
+
maskurl = ''
|
|
489
|
+
}
|
|
490
|
+
|
|
436
491
|
let whitebgcls = 'chat-button--white-bg'
|
|
437
492
|
if (!imgurl && maskurl) whitebgcls += ' chat-button--white-bg--mask-only'
|
|
438
493
|
let imgcontainerstyle = ''
|
|
439
494
|
|
|
440
|
-
imgurl = this.props.store.replaceFileUrl(imgurl)
|
|
441
|
-
imgsrc = this.props.store.replaceFileUrl(imgsrc)
|
|
442
|
-
maskurl = this.props.store.replaceFileUrl(maskurl)
|
|
443
|
-
imgurl = addVersionParam(imgurl)
|
|
444
|
-
imgsrc = addVersionParam(imgsrc)
|
|
445
|
-
maskurl = addVersionParam(maskurl)
|
|
446
|
-
|
|
447
495
|
if (!imgurl && !maskurl) {
|
|
448
496
|
whitebgcls += ' chat-button--white-bg--mask-only'
|
|
449
497
|
imgcontainerstyle = `
|
|
450
|
-
-webkit-mask-image:
|
|
451
|
-
-o-mask-image:
|
|
452
|
-
-moz-mask-image:
|
|
453
|
-
mask-image:
|
|
498
|
+
-webkit-mask-image: none !important;
|
|
499
|
+
-o-mask-image: none !important;
|
|
500
|
+
-moz-mask-image: none !important;
|
|
501
|
+
mask-image: none !important;
|
|
454
502
|
height: ${imgheight}px !important;
|
|
455
503
|
width: ${imgwidth}px !important;
|
|
456
504
|
background: ${background} !important;
|
|
@@ -509,16 +557,23 @@ export default class ChatButton extends Com {
|
|
|
509
557
|
|
|
510
558
|
`
|
|
511
559
|
|
|
512
|
-
if (!this.state.ready) cls += ' hidden'
|
|
560
|
+
if (!this.state.ready || this.state.readyImageKey !== appearance.key) cls += ' hidden'
|
|
513
561
|
|
|
514
562
|
return (
|
|
515
|
-
<
|
|
563
|
+
<button
|
|
564
|
+
ref={(element) => {this._buttonElement = element}}
|
|
565
|
+
type='button'
|
|
566
|
+
class={cls}
|
|
567
|
+
aria-label={this.props.t('chat')}
|
|
568
|
+
aria-expanded={!this.props.isShow}
|
|
569
|
+
onClick={(e) => this.onClick(e)}
|
|
570
|
+
>
|
|
516
571
|
<div class={whitebgcls} style={whiteBgStyle}></div>
|
|
517
572
|
<div class='chat-button--img-container' style={imgcontainerstyle}>
|
|
518
573
|
<img width='70' height='70' alt='chat button' class='chat-button--img' src={imgsrc} />
|
|
519
574
|
</div>
|
|
520
575
|
{this.renderUnread()}
|
|
521
|
-
</
|
|
576
|
+
</button>
|
|
522
577
|
)
|
|
523
578
|
}
|
|
524
579
|
|
|
@@ -551,7 +606,7 @@ export default class ChatButton extends Com {
|
|
|
551
606
|
}
|
|
552
607
|
return defmsg
|
|
553
608
|
}
|
|
554
|
-
return <Block block={block} />
|
|
609
|
+
return <Block block={block} store={this.props.store} t={this.props.t} />
|
|
555
610
|
}
|
|
556
611
|
|
|
557
612
|
let message = sb.get(ev, 'data.message.text', defmsg)
|
|
@@ -582,6 +637,11 @@ function randomAgent(agentM) {
|
|
|
582
637
|
|
|
583
638
|
const Ms30Mins = 30 * 60 * 1000
|
|
584
639
|
|
|
640
|
+
function normalizeButtonImageUrl(url) {
|
|
641
|
+
if (/^https:\/\/(?:storage\.googleapis\.com\/public-gcs\.subiz-cdn\.com|public-gcs\.subiz-cdn\.com|vcdn\.subiz-cdn\.com)\/dashboard-v4\/public\/img\/bubble\/dot_chat\.svg(?=$|[?#])/.test(url)) return dotChatIcon
|
|
642
|
+
return url
|
|
643
|
+
}
|
|
644
|
+
|
|
585
645
|
// v=5 trick to clear cache
|
|
586
646
|
const DEF_BG_URL = 'https://vcdn.subiz-cdn.com/file/bubble_11.svg?v=5'
|
|
587
647
|
const DEF_BG = 'linear-gradient(280.16deg, #0053d3 22.03%, #0265ff 78.2%)'
|
|
@@ -600,3 +660,37 @@ function addVersionParam(url, version = 8) {
|
|
|
600
660
|
return hash ? newUrl + '#' + hash : newUrl
|
|
601
661
|
}
|
|
602
662
|
}
|
|
663
|
+
|
|
664
|
+
// Use the exact rendered URL, including its version, for both images and masks.
|
|
665
|
+
function preloadButtonImage(src, {crossOrigin = false} = {}) {
|
|
666
|
+
return new Promise((resolve) => {
|
|
667
|
+
const image = new Image()
|
|
668
|
+
if (crossOrigin) image.crossOrigin = 'anonymous'
|
|
669
|
+
image.onerror = () => resolve(false)
|
|
670
|
+
image.onload = async () => {
|
|
671
|
+
try {
|
|
672
|
+
if (image.decode) await image.decode()
|
|
673
|
+
resolve(true)
|
|
674
|
+
} catch (_) {
|
|
675
|
+
resolve(false)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
image.src = src
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// CSS masks use a separate CORS image request. Pin the loaded bytes locally so
|
|
683
|
+
// revealing the white backing never races another network request by CSS.
|
|
684
|
+
async function prepareButtonMask(src) {
|
|
685
|
+
let url
|
|
686
|
+
try {
|
|
687
|
+
const response = await fetch(src)
|
|
688
|
+
if (!response.ok) return null
|
|
689
|
+
url = URL.createObjectURL(await response.blob())
|
|
690
|
+
if (await preloadButtonImage(url)) return url
|
|
691
|
+
} catch (_) {
|
|
692
|
+
// Use the bundled fallback if the custom mask cannot be loaded.
|
|
693
|
+
}
|
|
694
|
+
if (url) URL.revokeObjectURL(url)
|
|
695
|
+
return null
|
|
696
|
+
}
|
package/src/chatbox/Event.js
CHANGED
|
@@ -28,11 +28,31 @@ export default class Event extends Com {
|
|
|
28
28
|
this.props.store.onConvoEvent(this, (ev) => this.onEvent(ev))
|
|
29
29
|
this.refForm = createRef()
|
|
30
30
|
this.state = {pongs: sb.get(this.props.event, 'ev.data.message.pongs') || []}
|
|
31
|
+
const ev = this.props.event.ev
|
|
32
|
+
const age = (this.props.store.now ? this.props.store.now() : sb.Date.now()) - ev.created
|
|
33
|
+
// Capture this once: acknowledgement keeps the same component and animation.
|
|
34
|
+
this.animateSend = !props.isHistory && ev.type === 'message_sent' &&
|
|
35
|
+
sb.get(ev, 'by.type') === 'user' && ev.id === sb.get(ev, 'source_event_id') &&
|
|
36
|
+
age >= 0 && age < 2000
|
|
31
37
|
this.textMessageRef = createRef()
|
|
32
38
|
this.fileAttachmentRef = createRef()
|
|
33
39
|
this.imageAttachmentRef = createRef()
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
componentDidMount() {
|
|
43
|
+
if (!this.animateSend) return
|
|
44
|
+
// Paint the starting position before transitioning to the resting position.
|
|
45
|
+
this.sendAnimationFrame = window.requestAnimationFrame(() => {
|
|
46
|
+
this.sendAnimationFrame = window.requestAnimationFrame(() => {
|
|
47
|
+
if (this._isMounted) this.setState({sendEntered: true})
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
componentWillUnmount() {
|
|
53
|
+
if (this.sendAnimationFrame) window.cancelAnimationFrame(this.sendAnimationFrame)
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
focusSourceEventFromQuote = (ev) => {
|
|
37
57
|
let $textMessage = this.textMessageRef.current
|
|
38
58
|
if ($textMessage && typeof $textMessage.focusSourceEventFromQuote === 'function') {
|
|
@@ -90,6 +110,7 @@ export default class Event extends Com {
|
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
chooseQuickReply(reply) {
|
|
113
|
+
if (this.props.isHistory) return
|
|
93
114
|
let ev = sb.get(this.props.event, 'ev', {})
|
|
94
115
|
let message = {text: reply.title, format: 'plaintext'}
|
|
95
116
|
if (reply.payload) {
|
|
@@ -107,6 +128,7 @@ export default class Event extends Com {
|
|
|
107
128
|
}
|
|
108
129
|
|
|
109
130
|
choosePostbackBtn(button) {
|
|
131
|
+
if (this.props.isHistory) return
|
|
110
132
|
let ev = sb.get(this.props.event, 'ev', {})
|
|
111
133
|
let message = {text: button.title, format: 'plaintext'}
|
|
112
134
|
if (button.payload) {
|
|
@@ -252,6 +274,14 @@ export default class Event extends Com {
|
|
|
252
274
|
|
|
253
275
|
renderMessageSent() {
|
|
254
276
|
let ev = this.props.event.ev
|
|
277
|
+
if (sb.get(ev, 'data.message.deleted')) {
|
|
278
|
+
let byType = sb.get(ev, 'by.type') === 'user' ? 'user' : 'agent'
|
|
279
|
+
return (
|
|
280
|
+
<div class={'convo-event ' + byType + '-message'} data-ev-id={ev.id}>
|
|
281
|
+
<TextMessage ev={ev} t={this.props.t} store={this.props.store} />
|
|
282
|
+
</div>
|
|
283
|
+
)
|
|
284
|
+
}
|
|
255
285
|
let attachments = sb.get(ev, 'data.message.attachments')
|
|
256
286
|
let hasForm = sb.find(attachments, (att) => att.type === 'form')
|
|
257
287
|
if (sb.isEmptyMessage(sb.get(ev, 'data.message'))) return null
|
|
@@ -312,7 +342,7 @@ export default class Event extends Com {
|
|
|
312
342
|
ev={ev}
|
|
313
343
|
/>
|
|
314
344
|
<OrderAttachments t={this.props.t} store={this.props.store} ev={ev} />
|
|
315
|
-
<QuickReplies store={this.props.store} ev={ev} onSelect={(e) => this.chooseQuickReply(e)} />
|
|
345
|
+
{!this.props.isHistory && <QuickReplies store={this.props.store} ev={ev} onSelect={(e) => this.chooseQuickReply(e)} />}
|
|
316
346
|
{this.renderReactions()}
|
|
317
347
|
{this.renderAIDevNote()}
|
|
318
348
|
</div>
|
|
@@ -321,14 +351,14 @@ export default class Event extends Com {
|
|
|
321
351
|
)
|
|
322
352
|
}
|
|
323
353
|
|
|
324
|
-
if (sb.get(ev, '
|
|
354
|
+
if (sb.get(ev, 'source_event_id') === ev.id && this.props.store.now() - ev.created < 900) {
|
|
325
355
|
sb.setTimeout(() => this.setState({refresh: this.state.refresh}), 1000)
|
|
326
356
|
}
|
|
327
357
|
|
|
328
358
|
// only show sending while the message is send too long (>300ms) but haven't returned from
|
|
329
359
|
// the server
|
|
330
360
|
let $dot = <div class='sending_dot'></div>
|
|
331
|
-
if (this.props.store.now() - ev.created < 900 || sb.get(ev, '
|
|
361
|
+
if (this.props.store.now() - ev.created < 900 || sb.get(ev, 'source_event_id') !== ev.id) {
|
|
332
362
|
$dot = <div class='sending_dot sending_dot__hide'></div>
|
|
333
363
|
}
|
|
334
364
|
|
|
@@ -352,11 +382,12 @@ export default class Event extends Com {
|
|
|
352
382
|
return (
|
|
353
383
|
<div class='convo-event user-message' data-ev-id={ev.id}>
|
|
354
384
|
{$sender}
|
|
355
|
-
<div class='message-user__container'>
|
|
385
|
+
<div class={'message-user__container' + (this.animateSend && !this.props.isHistory ? ' message-user__container--send' + (this.state.sendEntered ? '' : ' message-user__container--enter') : '')}>
|
|
356
386
|
{$dot}
|
|
357
387
|
<TextMessage
|
|
358
388
|
ref={this.textMessageRef}
|
|
359
389
|
store={this.props.store}
|
|
390
|
+
t={this.props.t}
|
|
360
391
|
ev={ev}
|
|
361
392
|
refresh={this.state.refresh}
|
|
362
393
|
onLink={this.props.onLink}
|
|
@@ -376,6 +407,7 @@ export default class Event extends Com {
|
|
|
376
407
|
}
|
|
377
408
|
|
|
378
409
|
async onSendReaction(action) {
|
|
410
|
+
if (this.props.isHistory) return
|
|
379
411
|
if (this._reacting) return
|
|
380
412
|
let ev = this.props.event.ev
|
|
381
413
|
let cid = sb.get(ev, 'data.message.conversation_id')
|
|
@@ -423,6 +455,7 @@ export default class Event extends Com {
|
|
|
423
455
|
}
|
|
424
456
|
|
|
425
457
|
renderReactions() {
|
|
458
|
+
if (this.props.isHistory) return null
|
|
426
459
|
if (this.props.store.isAIDevMode()) return null
|
|
427
460
|
let cid = sb.get(this.props.event.ev, 'data.message.conversation_id', '')
|
|
428
461
|
if (cid.startsWith('cslight')) return null
|
|
@@ -515,7 +548,7 @@ export default class Event extends Com {
|
|
|
515
548
|
let byid = sb.get(ev, 'by.display_id', sb.get(ev, 'by.id')) || ''
|
|
516
549
|
if (bytype === 'user') return sb.getUserDisplayName(this.props.store.matchUser())
|
|
517
550
|
let acc = this.props.store.matchAccount()
|
|
518
|
-
if (bytype === 'agent') return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
|
|
551
|
+
if (bytype === 'agent' || sb.get(ev, 'by.display_type') === 'agent') return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
|
|
519
552
|
if (byid.startsWith('at')) return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
|
|
520
553
|
if (bytype === 'bot') {
|
|
521
554
|
let bot = this.props.store.matchBot()[byid]
|