@subiz/widget-v4 4.1.191 → 4.1.196

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/assets/less/chatbox.less +0 -1
  2. package/assets/less/message.less +71 -4
  3. package/compiled_src/chatbox/Event.js +37 -6
  4. package/compiled_src/chatbox/Events.js +20 -0
  5. package/compiled_src/chatbox/FileAttachments.js +36 -4
  6. package/compiled_src/chatbox/ImageAttachments.js +31 -4
  7. package/compiled_src/chatbox/QuoteMessage.js +99 -0
  8. package/compiled_src/chatbox/Rating.js +4 -0
  9. package/compiled_src/chatbox/TextMessage.js +49 -4
  10. package/compiled_src/chatbox/Widget.js +43 -1
  11. package/compiled_src/store/api.js +2 -0
  12. package/compiled_src/store/store.js +45 -3
  13. package/compiled_src/util/common.js +13 -1
  14. package/dist/127.js +273 -0
  15. package/dist/132.js +2802 -0
  16. package/dist/185.js +1827 -0
  17. package/dist/19b9941db90.127.js +1 -0
  18. package/dist/19b9941db90.132.js +1 -0
  19. package/dist/19b9941db90.185.js +1 -0
  20. package/dist/19b9941db90.27.js +1 -0
  21. package/dist/19b9941db90.278.js +1 -0
  22. package/dist/19b9941db90.339.js +1 -0
  23. package/dist/19b9941db90.459.js +1 -0
  24. package/dist/19b9941db90.490.js +1 -0
  25. package/dist/19b9941db90.74.js +1 -0
  26. package/dist/19b9941db90.997.js +1 -0
  27. package/dist/19b9941db90.app.js +1 -0
  28. package/dist/19b9941db90.chat.html +1 -0
  29. package/dist/19b9941db90.chat.js +1 -0
  30. package/dist/19b9941db90.form.html +1 -0
  31. package/dist/19b9941db90.form.js +1 -0
  32. package/dist/19b9941db90.index.html +72 -0
  33. package/dist/19b9941db90.library.html +72 -0
  34. package/dist/19b9941db90.library.js +1 -0
  35. package/dist/19b9941db90.preview.html +759 -0
  36. package/dist/19b9941db90.preview.js +1 -0
  37. package/dist/19b9941db90.webview.js +1 -0
  38. package/dist/27.js +35 -0
  39. package/dist/459.js +35 -0
  40. package/dist/490.js +305 -0
  41. package/dist/74.js +372 -0
  42. package/dist/997.js +35 -0
  43. package/dist/HASH +1 -0
  44. package/dist/assets/img/Facebook.64607ec.png +0 -0
  45. package/dist/assets/img/Zalo.6845707.png +0 -0
  46. package/dist/assets/img/award1a.1a58e10.png +0 -0
  47. package/dist/assets/img/award3a.3b74423.png +0 -0
  48. package/dist/assets/img/award4a.ed6ccd4.png +0 -0
  49. package/dist/assets/img/award5.39eb7ae.png +0 -0
  50. package/dist/assets/img/bubble_default.7d5e4ab.svg +20 -0
  51. package/dist/assets/img/caret.998eb8f.svg +1 -0
  52. package/dist/assets/img/convos_empty.2353007.svg +72 -0
  53. package/dist/assets/img/default.06e6ee0.jpg +0 -0
  54. package/dist/assets/img/default_avatar.5b74dc1.png +0 -0
  55. package/dist/assets/img/done_gray.d7e8203.svg +4 -0
  56. package/dist/assets/img/done_green.b817fd4.svg +4 -0
  57. package/dist/assets/img/emoji/messenger/sub_23.529_58.824.d5faf14.png +0 -0
  58. package/dist/assets/img/emoji/messenger/sub_23.529_60.784.1420241.png +0 -0
  59. package/dist/assets/img/emoji/messenger/sub_25.490_0.000.1e6b2c0.png +0 -0
  60. package/dist/assets/img/emoji/messenger/sub_25.490_45.098.3dfbc08.png +0 -0
  61. package/dist/assets/img/emoji/messenger/sub_25.490_5.882.4b49e94.png +0 -0
  62. package/dist/assets/img/emoji/messenger/sub_27.451_72.549.7114f58.png +0 -0
  63. package/dist/assets/img/emoji/messenger/sub_27.451_84.314.231e2b7.png +0 -0
  64. package/dist/assets/img/emoji/messenger/sub_27.451_96.078.ddbb008.png +0 -0
  65. package/dist/assets/img/emoji/messenger/sub_29.412_5.882.61f3359.png +0 -0
  66. package/dist/assets/img/emoji/messenger/sub_58.824_100.000.6df72bc.png +0 -0
  67. package/dist/assets/img/emoji/messenger/sub_58.824_45.098.3d7a7ad.png +0 -0
  68. package/dist/assets/img/emoji/messenger/sub_58.824_47.059.0f4d6e2.png +0 -0
  69. package/dist/assets/img/emoji/messenger/sub_58.824_54.902.65724c1.png +0 -0
  70. package/dist/assets/img/emoji/messenger/sub_58.824_64.706.9da3cba.png +0 -0
  71. package/dist/assets/img/emoji/messenger/sub_58.824_72.549.edca5cf.png +0 -0
  72. package/dist/assets/img/emoji/messenger/sub_58.824_78.431.f910f2a.png +0 -0
  73. package/dist/assets/img/emoji/messenger/sub_58.824_88.235.def4e71.png +0 -0
  74. package/dist/assets/img/emoji/messenger/sub_60.784_11.765.49c03d0.png +0 -0
  75. package/dist/assets/img/emoji/messenger/sub_60.784_27.451.46b54e5.png +0 -0
  76. package/dist/assets/img/emoji/messenger/sub_60.784_29.412.e36d284.png +0 -0
  77. package/dist/assets/img/emoji/messenger/sub_60.784_3.922.bb191b0.png +0 -0
  78. package/dist/assets/img/emoji/messenger/sub_60.784_35.294.af7b927.png +0 -0
  79. package/dist/assets/img/emoji/messenger/sub_60.784_7.843.98f2a99.png +0 -0
  80. package/dist/assets/img/empty.29bed2d.svg +23 -0
  81. package/dist/assets/img/empty_1.0b98af0.svg +11 -0
  82. package/dist/assets/img/examplte_logo.f86535f.svg +1 -0
  83. package/dist/assets/img/external-link.90577cc.svg +1 -0
  84. package/dist/assets/img/fb_inverse_circle.36c2cfc.png +0 -0
  85. package/dist/assets/img/ghn.070f8e3.svg +17 -0
  86. package/dist/assets/img/ghtk.326eac1.svg +18 -0
  87. package/dist/assets/img/image_default.be0698b.png +0 -0
  88. package/dist/assets/img/info.ebce7ea.svg +1 -0
  89. package/dist/assets/img/loader.37000a7.svg +1 -0
  90. package/dist/assets/img/loading_static.a27f0ac.svg +10 -0
  91. package/dist/assets/img/main-service1.d536cc2.png +0 -0
  92. package/dist/assets/img/main-service2.9b99549.png +0 -0
  93. package/dist/assets/img/not_found_search.2ddb8d1.png +0 -0
  94. package/dist/assets/img/package.8d37d0f.svg +5 -0
  95. package/dist/assets/img/package_fade.763c001.svg +1 -0
  96. package/dist/assets/img/rating--average.1ea492c.svg +12 -0
  97. package/dist/assets/img/rating--great.e3d712e.svg +16 -0
  98. package/dist/assets/img/rating--poor.2938a25.svg +12 -0
  99. package/dist/assets/img/search.f9705ff.svg +1 -0
  100. package/dist/assets/img/send.6db52a9.svg +3 -0
  101. package/dist/assets/img/shop.f7dc7d8.svg +1 -0
  102. package/dist/assets/img/shop_faded.df7845a.svg +1 -0
  103. package/dist/assets/img/stars_v2.79cf728.png +0 -0
  104. package/dist/assets/img/subiz-mono.8fbc067.svg +3 -0
  105. package/dist/assets/img/x.26fdfa3.svg +5 -0
  106. package/dist/malicious-app.js +17 -0
  107. package/dist/screenshoot.html +7 -0
  108. package/dist/screenshoot.js +13026 -0
  109. package/package.json +8 -8
  110. package/src/chatbox/Event.js +48 -7
  111. package/src/chatbox/Events.js +21 -1
  112. package/src/chatbox/FileAttachments.js +31 -4
  113. package/src/chatbox/ImageAttachments.js +26 -3
  114. package/src/chatbox/QuoteMessage.js +94 -0
  115. package/src/chatbox/Rating.js +5 -0
  116. package/src/chatbox/TextMessage.js +50 -2
  117. package/src/chatbox/Widget.js +52 -0
  118. package/src/store/api.js +3 -0
  119. package/src/store/store.js +51 -2
  120. package/src/util/common.js +19 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.1.191",
3
+ "version": "4.1.196",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -9,14 +9,14 @@
9
9
  "lib": "rm -rf compiled_src && npx babel --out-dir=compiled_src src"
10
10
  },
11
11
  "dependencies": {
12
- "@subiz/ajax": "^3.0.20",
12
+ "@subiz/ajax": "^3.0.22",
13
13
  "@subiz/flow": "^1.1.0",
14
14
  "@subiz/wsclient": "^4.0.34",
15
15
  "cookie_js": "1.4.2",
16
- "core-js": "^3.46.0",
16
+ "core-js": "^3.47.0",
17
17
  "css-loader": "5.2.7",
18
18
  "file-loader": "^6.2.0",
19
- "less": "^4.4.2",
19
+ "less": "^4.5.1",
20
20
  "less-loader": "^12.3.0",
21
21
  "preact": "10.19.3"
22
22
  },
@@ -28,10 +28,10 @@
28
28
  "@babel/preset-env": "^7.28.5",
29
29
  "babel-loader": "^10.0.0",
30
30
  "babel-plugin-syntax-jsx": "^6.18.0",
31
- "html-webpack-plugin": "^5.6.4",
32
- "puppeteer": "^24.27.0",
33
- "webpack": "^5.102.1",
34
- "webpack-bundle-analyzer": "^4.10.2",
31
+ "html-webpack-plugin": "^5.6.5",
32
+ "puppeteer": "^24.34.0",
33
+ "webpack": "^5.104.1",
34
+ "webpack-bundle-analyzer": "^5.1.0",
35
35
  "webpack-cli": "^6.0.1",
36
36
  "webpack-dev-server": "^5.2.2"
37
37
  }
@@ -28,6 +28,26 @@ 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
+ this.textMessageRef = createRef()
32
+ this.fileAttachmentRef = createRef()
33
+ this.imageAttachmentRef = createRef()
34
+ }
35
+
36
+ focusSourceEventFromQuote = (ev) => {
37
+ let $textMessage = this.textMessageRef.current
38
+ if ($textMessage && typeof $textMessage.focusSourceEventFromQuote === 'function') {
39
+ $textMessage.focusSourceEventFromQuote(ev)
40
+ }
41
+
42
+ let $fileAttachment = this.fileAttachmentRef.current
43
+ if ($fileAttachment && typeof $fileAttachment.focusSourceEventFromQuote === 'function') {
44
+ $fileAttachment.focusSourceEventFromQuote(ev)
45
+ }
46
+
47
+ let $imageAttachment = this.imageAttachmentRef.current
48
+ if ($imageAttachment && typeof $imageAttachment.focusSourceEventFromQuote === 'function') {
49
+ $imageAttachment.focusSourceEventFromQuote(ev)
50
+ }
31
51
  }
32
52
 
33
53
  render() {
@@ -248,21 +268,34 @@ export default class Event extends Com {
248
268
  let wrappercls = 'agent-convo-event-wrapper'
249
269
  if (hasForm) wrappercls += ' full'
250
270
  return (
251
- <div class='convo-event agent-message'>
271
+ <div class='convo-event agent-message' data-ev-id={ev.id}>
252
272
  {$sender}
253
273
  <div class={wrappercls}>
254
274
  <div class={innercls}>
255
275
  <TextMessage
276
+ ref={this.textMessageRef}
277
+ onFocusSourceEventFromQuote={this.props.onFocusSourceEventFromQuote}
256
278
  showTime={this.props.event.show_time}
257
279
  isLast={this.props.isLast}
258
280
  store={this.props.store}
281
+ t={this.props.t}
259
282
  ev={ev}
260
283
  onLink={this.props.onLink}
261
284
  />
262
- <ImageAttachments store={this.props.store} ev={ev} />
285
+ <ImageAttachments
286
+ ref={this.imageAttachmentRef}
287
+ store={this.props.store}
288
+ ev={ev}
289
+ focusSourceEventFromQuote={this.props.focusSourceEventFromQuote}
290
+ />
263
291
  <AudioAttachments store={this.props.store} ev={ev} />
264
292
  <VideoAttachments store={this.props.store} ev={ev} />
265
- <FileAttachments store={this.props.store} ev={ev} />
293
+ <FileAttachments
294
+ ref={this.fileAttachmentRef}
295
+ store={this.props.store}
296
+ ev={ev}
297
+ focusSourceEventFromQuote={this.props.focusSourceEventFromQuote}
298
+ />
266
299
  <ButtonAttachments store={this.props.store} ev={ev} t={this.props.t} />
267
300
  <FormAttachment store={this.props.store} ev={ev} t={this.props.t} />
268
301
  <FormSubmitAttachments store={this.props.store} ev={ev} t={this.props.t} />
@@ -317,15 +350,21 @@ export default class Event extends Com {
317
350
  }
318
351
 
319
352
  return (
320
- <div class='convo-event user-message'>
353
+ <div class='convo-event user-message' data-ev-id={ev.id}>
321
354
  {$sender}
322
355
  <div class='message-user__container'>
323
356
  {$dot}
324
- <TextMessage store={this.props.store} ev={ev} refresh={this.state.refresh} onLink={this.props.onLink} />
325
- <ImageAttachments store={this.props.store} ev={ev} />
357
+ <TextMessage
358
+ ref={this.textMessageRef}
359
+ store={this.props.store}
360
+ ev={ev}
361
+ refresh={this.state.refresh}
362
+ onLink={this.props.onLink}
363
+ />
364
+ <ImageAttachments store={this.props.store} ev={ev} ref={this.imageAttachmentRef} />
326
365
  <AudioAttachments store={this.props.store} ev={ev} />
327
366
  <VideoAttachments store={this.props.store} ev={ev} />
328
- <FileAttachments store={this.props.store} ev={ev} />
367
+ <FileAttachments store={this.props.store} ev={ev} ref={this.fileAttachmentRef} />
329
368
  <ButtonAttachments store={this.props.store} ev={ev} t={this.props.t} />
330
369
  <FormAttachment store={this.props.store} t={this.props.t} ev={ev} />
331
370
  <ProductAttachments t={this.props.t} store={this.props.store} ev={ev} />
@@ -388,6 +427,8 @@ export default class Event extends Com {
388
427
  let cid = sb.get(this.props.event.ev, 'data.message.conversation_id', '')
389
428
  if (cid.startsWith('cslight')) return null
390
429
  let ev = sb.get(this.props.event, 'ev') || {}
430
+ let isDeleted = sb.get(ev, 'data.message.deleted')
431
+ if (isDeleted) return
391
432
  let pongs = sb.get(ev, 'data.message.pongs') || []
392
433
  let hasLike = this.hasLike()
393
434
  let hasDislike = this.hasDislike()
@@ -5,7 +5,7 @@ import sb from '../util/index.js'
5
5
 
6
6
  import Spinner from './spinner.js'
7
7
  import {Com} from '../common.js'
8
- import {h, Fragment} from 'preact'
8
+ import {h, Fragment, createRef} from 'preact'
9
9
 
10
10
  // ['cid', 'creating', 'isShow'],
11
11
  export default class Events extends Com {
@@ -15,6 +15,7 @@ export default class Events extends Com {
15
15
  this.props.store.onConvoEvent(this, (ev) => this.onEvent(ev))
16
16
  this.loadConvo()
17
17
  this.refBody = null
18
+ this.evRefs = {}
18
19
 
19
20
  this.checkAndSeen = sb.throttle(() => {
20
21
  if (
@@ -64,12 +65,29 @@ export default class Events extends Com {
64
65
  this.setState({isShowTyping: false})
65
66
  }, 5000)
66
67
  })
68
+
69
+ this.props.store.onMessageUpdated(this, async (ev) => {
70
+ if (sb.convoOfEv(ev) !== this.props.cid) return
71
+ await this.props.store.reloadConvoEvents(this.props.cid)
72
+ this.forceUpdate()
73
+ })
67
74
  }
68
75
 
69
76
  componentDidUpdate(prevProps) {
70
77
  if (prevProps.cid !== this.props.cid) this.loadConvo()
71
78
  }
72
79
 
80
+ setEventChildRef(ele, id) {
81
+ this.evRefs[id] = ele
82
+ }
83
+
84
+ onFocusSourceEventFromQuote = (ev) => {
85
+ let ref = this.evRefs[ev.id]
86
+ if (ref) {
87
+ ref.focusSourceEventFromQuote(ev)
88
+ }
89
+ }
90
+
73
91
  ShowTyping() {
74
92
  this.setState({
75
93
  isShowTyping: true,
@@ -140,6 +158,8 @@ export default class Events extends Com {
140
158
  onFormSubmit={this.props.onFormSubmit}
141
159
  onSend={this.props.onSend}
142
160
  onPostback={this.props.onPostback}
161
+ onFocusSourceEventFromQuote={this.onFocusSourceEventFromQuote}
162
+ ref={(ele) => this.setEventChildRef(ele, ev.ev.id)}
143
163
  event={ev}
144
164
  t={this.props.t}
145
165
  key={ev.ev.id}
@@ -3,12 +3,17 @@ import {Com} from '../common.js'
3
3
  import {h} from 'preact'
4
4
 
5
5
  export default class FileAttachments extends Com {
6
+ constructor(props) {
7
+ super(props)
8
+ this.state = {focusedIdx: -1}
9
+ }
10
+
6
11
  render() {
7
12
  var byType = sb.get(this.props.ev, 'by.type', 'agent')
8
13
  if (byType !== 'user') byType = 'agent'
9
14
  var attachments = sb.get(this.props.ev, 'data.message.attachments', [])
10
15
  var files = []
11
- sb.each(attachments, (att) => {
16
+ sb.each(attachments, (att, idx) => {
12
17
  if (!att) return
13
18
  if (!att.url && !att._file) return
14
19
  if (
@@ -20,15 +25,18 @@ export default class FileAttachments extends Com {
20
25
  att.type !== 'form' &&
21
26
  att.type !== 'form_submit'
22
27
  ) {
23
- files.push(att)
28
+ files.push({...att, _true_idx: idx})
24
29
  }
25
30
  })
26
31
 
27
32
  if (files.length === 0) return null
28
33
 
29
- var $files = sb.map(files, (file, index) => {
34
+ let $files = sb.map(files, (file, index) => {
35
+ let cls = `file-attachment file-attachment--${byType}`
36
+ if (file._true_idx === this.state.focusedIdx) cls += ` just-focused`
37
+
30
38
  return (
31
- <div key={index} class={`file-attachment file-attachment--${byType}`}>
39
+ <div key={index} class={cls}>
32
40
  <a class='file-attachment--link' href={this.props.store.replaceFileUrl(file.url)} target='_blank'>
33
41
  <span class='file-attachment--download mr-2'></span>
34
42
  <span class='file-attachment--filename'>{file.name}</span>
@@ -39,4 +47,23 @@ export default class FileAttachments extends Com {
39
47
 
40
48
  return <div class={`message-${byType}`}>{$files}</div>
41
49
  }
50
+
51
+ focusSourceEventFromQuote(ev) {
52
+ if (ev.id !== this.props.ev.id) return
53
+ let fields = sb.get(ev, 'data.message.fields') || []
54
+ let attachmentIndexField = sb.find(fields, (f) => f.key === 'attachment_index')
55
+ let attachmentIndex = sb.get(attachmentIndexField, 'value')
56
+ try {
57
+ attachmentIndex = JSON.parse(attachmentIndex)
58
+ } catch (err) {
59
+ attachmentIndex = undefined
60
+ }
61
+
62
+ if (attachmentIndex === undefined) return // this is a quote for text
63
+ this.setState({focusedIdx: attachmentIndex})
64
+ if (this.focusTimeout) clearTimeout(this.focusTimeout)
65
+ this.focusTimeout = setTimeout(() => {
66
+ this.setState({focusedIdx: -1})
67
+ }, 3000)
68
+ }
42
69
  }
@@ -8,6 +8,7 @@ export default class ImageAttachment extends Com {
8
8
  this.setState({
9
9
  ready: false,
10
10
  errorIndecies: [],
11
+ focusedIdx: -1,
11
12
  })
12
13
  }
13
14
 
@@ -16,6 +17,25 @@ export default class ImageAttachment extends Com {
16
17
  this.setImageFrameDimension()
17
18
  }
18
19
 
20
+ focusSourceEventFromQuote(ev) {
21
+ if (ev.id !== this.props.ev.id) return
22
+ let fields = sb.get(ev, 'data.message.fields') || []
23
+ let attachmentIndexField = sb.find(fields, (f) => f.key === 'attachment_index')
24
+ let attachmentIndex = sb.get(attachmentIndexField, 'value')
25
+ try {
26
+ attachmentIndex = JSON.parse(attachmentIndex)
27
+ } catch (err) {
28
+ attachmentIndex = undefined
29
+ }
30
+
31
+ if (attachmentIndex === undefined) return // this is a quote for text
32
+ this.setState({focusedIdx: attachmentIndex})
33
+ if (this.focusTimeout) clearTimeout(this.focusTimeout)
34
+ this.focusTimeout = setTimeout(() => {
35
+ this.setState({focusedIdx: -1})
36
+ }, 3000)
37
+ }
38
+
19
39
  setImageFrameDimension() {
20
40
  const MAX_HEIGHT = 240
21
41
  const DEFAULT_MAX_WIDTH = 267
@@ -60,9 +80,12 @@ export default class ImageAttachment extends Com {
60
80
  let byType = sb.get(this.props.ev, 'by.type')
61
81
  if (byType !== 'user') byType = 'agent'
62
82
 
83
+ let cls = `image-attachment image-attachment--${byType}`
84
+ if (image._true_idx === this.state.focusedIdx) cls += ' just-focused'
85
+
63
86
  if (!image.width || !image.height) {
64
87
  return (
65
- <div class={`image-attachment image-attachment--${byType}`}>
88
+ <div class={cls}>
66
89
  <a href={this.props.store.replaceFileUrl(image.url)} target='_blank'>
67
90
  <img
68
91
  alt
@@ -84,7 +107,7 @@ export default class ImageAttachment extends Com {
84
107
  }
85
108
 
86
109
  return (
87
- <div class={`image-attachment image-attachment--${byType}`}>
110
+ <div class={cls}>
88
111
  <a href={trueUrl} target='_blank'>
89
112
  <img
90
113
  alt
@@ -125,7 +148,7 @@ export default class ImageAttachment extends Com {
125
148
  ) {
126
149
  att.url = this.props.store.FileURL + uri
127
150
  }
128
- images.push(att)
151
+ images.push({...att, _true_idx: i})
129
152
  })
130
153
 
131
154
  if (images.length === 0) return null
@@ -0,0 +1,94 @@
1
+ import sb from '../util/index.js'
2
+ import Block from './block.js'
3
+ import {Com} from '../common.js'
4
+ import {h} from 'preact'
5
+ import Emoji from './Emoji.js'
6
+
7
+ export default class QuoteMessage extends Com {
8
+ render() {
9
+ let sendername = ''
10
+ let byType = sb.get(this.props.ev, 'by.type')
11
+ let byId = sb.get(this.props.ev, 'by.id')
12
+
13
+ if (byType === 'user') {
14
+ sendername = this.props.t('you')
15
+ } else {
16
+ sendername = this.getSenderName(this.props.ev)
17
+
18
+ let acc = this.props.store.matchAccount() || {}
19
+ if (sb.get(this.props, 'chatbox.hide_agent_profile')) sendername = acc.name
20
+ }
21
+
22
+ let att = sb.get(this.props.ev, 'data.message.attachments.0')
23
+ if (att) {
24
+ let isImg = sb.includes(att.mimetype, 'image')
25
+ if (isImg) {
26
+ let uri = att.url
27
+ if (
28
+ !uri.startsWith('https://') &&
29
+ !uri.startsWith('http://') &&
30
+ !uri.startsWith('content://') &&
31
+ !uri.startsWith('data:')
32
+ ) {
33
+ uri = this.props.store.FileURL + uri
34
+ }
35
+ return (
36
+ <div class='message-quote d-flex' onClick={() => this.scrollToSourceEvent(this.props.ev)}>
37
+ <img src={uri} class='message-quote-inner-img' />
38
+ <div class='message-quote-inner'>
39
+ <div class='message-quote-title'>{sendername}</div>
40
+ <div class='message-quote-content'>[{this.props.t('image')}]</div>
41
+ </div>
42
+ </div>
43
+ )
44
+ } else {
45
+ return (
46
+ <div class='message-quote' onClick={() => this.scrollToSourceEvent(this.props.ev)}>
47
+ <div class='message-quote-title'>{sendername}</div>
48
+ <div class='message-quote-content'>
49
+ {`[File] `}
50
+ {att.name}
51
+ </div>
52
+ </div>
53
+ )
54
+ }
55
+ }
56
+
57
+ let format = sb.get(this.props.ev, 'data.message.format', 'plaintext')
58
+ let block = sb.get(this.props.ev, 'data.message.block')
59
+
60
+ let text = sb.get(this.props.ev, 'data.message.text')
61
+ if (block) text = sb.blockToText(block)
62
+
63
+ return (
64
+ <div class='message-quote' onClick={() => this.scrollToSourceEvent(this.props.ev)}>
65
+ <div class='message-quote-title'>{sendername}</div>
66
+ <div class='message-quote-content'>{text}</div>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ scrollToSourceEvent(ev) {
72
+ let $ele = document.querySelector(`.convo-event[data-ev-id=${ev.id}]`)
73
+ if ($ele && typeof $ele.scrollIntoViewIfNeeded === 'function') {
74
+ $ele.scrollIntoViewIfNeeded()
75
+ }
76
+ this.props.onFocusSourceEventFromQuote(ev)
77
+ }
78
+
79
+ getSenderName = (ev) => {
80
+ let bytype = sb.get(ev, 'by.type')
81
+ let byid = sb.get(ev, 'by.display_id', sb.get(ev, 'by.id')) || ''
82
+ if (bytype === 'user') return sb.getUserDisplayName(this.props.store.matchUser())
83
+ let acc = this.props.store.matchAccount()
84
+ if (bytype === 'agent') return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
85
+ if (byid.startsWith('at')) return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
86
+ if (bytype === 'bot') {
87
+ let bot = this.props.store.matchBot()[byid]
88
+ if (!bot) return 'Bot'
89
+ return bot.fullname || 'Bot'
90
+ }
91
+ if (byid === 'system' || bytype === 'dummy' || bytype === 'workflow') return acc.name || 'system'
92
+ return acc.name
93
+ }
94
+ }
@@ -34,6 +34,11 @@ export default class Rating extends Com {
34
34
  this.props.store.onConvoEvent(this, (ev) => {
35
35
  var cid = sb.convoOfEv(ev)
36
36
  if (cid !== this.props.cid) return
37
+ if (!this.state.hide && ev.type === 'conversation_rated') {
38
+ // close if already rated in another tab
39
+ this.close()
40
+ return
41
+ }
37
42
  if (this.shoudDisplay()) this.open()
38
43
  })
39
44
 
@@ -4,8 +4,14 @@ import {Com} from '../common.js'
4
4
  import {h} from 'preact'
5
5
  import Emoji from './Emoji.js'
6
6
  import Time from './time.js'
7
+ import QuoteMessage from './QuoteMessage.js'
7
8
 
8
9
  export default class TextMessage extends Com {
10
+ constructor(props) {
11
+ super(props)
12
+ this.state = {focused: false}
13
+ }
14
+
9
15
  renderTime() {
10
16
  return null
11
17
  if (!this.props.showTime && !this.props.isLast && !this.props.isFirst) return null
@@ -17,6 +23,25 @@ export default class TextMessage extends Com {
17
23
  )
18
24
  }
19
25
 
26
+ focusSourceEventFromQuote(ev) {
27
+ if (ev.id !== this.props.ev.id) return
28
+ let fields = sb.get(ev, 'data.message.fields') || []
29
+ let attachmentIndexField = sb.find(fields, (f) => f.key === 'attachment_index')
30
+ let attachmentIndex = sb.get(attachmentIndexField, 'value')
31
+ try {
32
+ attachmentIndex = JSON.parse(attachmentIndex)
33
+ } catch (err) {
34
+ attachmentIndex = undefined
35
+ }
36
+
37
+ if (attachmentIndex !== undefined) return // this is a quote for image or video, not for text
38
+ this.setState({focused: true})
39
+ if (this.focusTimeout) clearTimeout(this.focusTimeout)
40
+ this.focusTimeout = setTimeout(() => {
41
+ this.setState({focused: false})
42
+ }, 3000)
43
+ }
44
+
20
45
  render() {
21
46
  var format = sb.get(this.props.ev, 'data.message.format', 'plaintext')
22
47
  let block = sb.get(this.props.ev, 'data.message.block')
@@ -66,15 +91,38 @@ export default class TextMessage extends Com {
66
91
  })
67
92
 
68
93
  if (sb.size(tags) === 0) return null
69
- if (byType === 'agent')
94
+ let isDeleted = sb.get(this.props.ev, 'data.message.deleted')
95
+
96
+ let attachments = sb.get(this.props.ev, 'data.message.attachments', [])
97
+ let quote = sb.find(attachments, (att) => att.type === 'quote')
98
+
99
+ if (byType === 'agent') {
100
+ let cls = 'message-agent message-text--agent'
101
+ if (isDeleted) {
102
+ tags = this.props.t('recalled_message')
103
+ cls += ' message-text--deleted'
104
+ }
105
+ if (this.state.focused) cls += ' just-focused'
106
+
70
107
  return (
71
- <div class='message-agent message-text--agent' title={t}>
108
+ <div class={cls} title={t}>
109
+ {quote && (
110
+ <QuoteMessage
111
+ ev={sb.get(quote, 'quote')}
112
+ t={this.props.t}
113
+ store={this.props.store}
114
+ onFocusSourceEventFromQuote={this.props.onFocusSourceEventFromQuote}
115
+ />
116
+ )}
72
117
  {tags}
73
118
  {this.renderTime()}
74
119
  </div>
75
120
  )
121
+ }
76
122
  let msgcls = 'message-user message-text--user'
77
123
  if (this.props.ev._error) msgcls += ' message-user--error'
124
+ if (this.state.focused) msgcls += ' just-focused'
125
+
78
126
  return (
79
127
  <div class={msgcls} title={t}>
80
128
  {tags}
@@ -48,6 +48,54 @@ export default class Widget extends Com {
48
48
  if (!this.props.store.isMobile()) this.focusInput()
49
49
 
50
50
  this.updateFormDataFields()
51
+ this.debugRTAgentMessageSent()
52
+ }
53
+
54
+ findMissingRTMessage = async (started) => {
55
+ let messageSentDict = this.props.store.listAgentRealtimeMessageSent()
56
+
57
+ let res1 = await this.props.store.sbzListConversations()
58
+ let conversions = sb.get(res1, 'body.conversations') || []
59
+ let cids = sb.map(conversions, (convo) => convo.id)
60
+
61
+ let promises = sb.map(cids, (cid) => this.props.store.sbzListMessages(cid))
62
+ let msgReses = await Promise.all(promises)
63
+ sb.map(msgReses, (msgRes) => {
64
+ let messages = sb.get(msgRes, 'body') || []
65
+ sb.map(messages, (ev) => {
66
+ if (ev.created < started) return
67
+ //if (ev.created > Date.now() - 30_000) return
68
+ if (ev.type !== 'message_sent') return
69
+ if (sb.get(ev, 'by.type') === 'user') return
70
+
71
+ console.log('check ev pass condition', ev)
72
+ if (!messageSentDict[ev.id]) {
73
+ this.props.store.sendBugReport(
74
+ '[WIDGET ' + accid + '] Missing agent message sent evid ' + ev.id + ', cid ' + sb.convoOfEv(ev),
75
+ )
76
+ }
77
+ })
78
+ })
79
+ }
80
+
81
+ debugRTAgentMessageSent = async () => {
82
+ try {
83
+ let debugAccountIds = this.props.store.matchDebugAccountIds()
84
+ let accid = sb.get(this.props.store.matchAccount(), 'id')
85
+ if (!debugAccountIds.includes(accid)) return
86
+
87
+ console.log('START check RT agent message with BE source')
88
+ let started = Date.now()
89
+ this.findMissingRTMessage(started)
90
+
91
+ for (let i = 0; i < 100_000; i++) {
92
+ await sb.sleep(120_000)
93
+ console.log('LOOP check RT agent message with BE source', i)
94
+ this.findMissingRTMessage(started)
95
+ }
96
+ } catch (err) {
97
+ this.props.store.sendBugReport(`debugRTAgentMessageSent ERROR ${err.stack}`)
98
+ }
51
99
  }
52
100
 
53
101
  applyMessage(text) {
@@ -799,6 +847,9 @@ export default class Widget extends Com {
799
847
  this.setState({creating: true})
800
848
  let botid = cid.split('_')[0].slice(7) // 6 = len(cslight)
801
849
  let oldconvo = this.props.store.matchConvo(this.props.cid) || {}
850
+ let greeting_msg = sb.find(oldconvo.events, (ev) => {
851
+ return ev.type == 'message_sent' && ev.by && ev.by.type == 'bot'
852
+ })
802
853
  const {convo, error} = await this.props.store.startConvo({
803
854
  state: 'active', // so it would not run through rule
804
855
  locale: this.props.t.locale(),
@@ -806,6 +857,7 @@ export default class Widget extends Com {
806
857
  members: [{type: 'bot', id: botid, bot_version: oldconvo.bot_version}],
807
858
  initial_by: this.props.store.matchUser().id,
808
859
  is_testing: oldconvo.is_testing,
860
+ greeting_message: greeting_msg,
809
861
  })
810
862
  if (_creating != this._creating) return // outdated
811
863
  if (error) {
package/src/store/api.js CHANGED
@@ -39,6 +39,7 @@ export default class {
39
39
 
40
40
  this.FileURL = FileURL
41
41
  this.ApiURL = ApiURL
42
+ this.bugReq = ajax.setBaseUrl('https://api5.subiz.com.vn/4.1').setContentType('text/plain')
42
43
  this.api41 = ajax
43
44
  .setBaseUrl('https://api.sbz.vn/4.1')
44
45
  .withCredentials(true)
@@ -229,6 +230,8 @@ export default class {
229
230
  return {error: 'invalid_auth'}
230
231
  }
231
232
 
233
+ sendBugReport = (str) => this.bugReq.post('/bugs', str)
234
+
232
235
  listConversations = (_) =>
233
236
  this.apireq
234
237
  .addQuery('user_id', this.uid)