@subiz/widget-v4 4.1.186 → 4.1.190

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.
@@ -412,15 +412,15 @@ a.message_error {
412
412
  padding: 10px 18px;
413
413
  display: block;
414
414
  text-align: center;
415
- .color(@link);
415
+ .color(@black);
416
416
  border: 1px solid @black;
417
417
  border-radius: 12px;
418
418
  text-overflow: ellipsis;
419
419
  overflow: hidden;
420
420
  font-weight: 500;
421
- text-decoration: underline;
422
421
  &:hover {
423
- .color(@link-hover);
422
+ .color(@primary-color);
423
+ border-color: @primary-color;
424
424
  }
425
425
  white-space: nowrap;
426
426
  }
@@ -515,6 +515,11 @@ a.message_error {
515
515
  border: none;
516
516
  border-top: 1px solid #d8d8d8;
517
517
  width: 100%;
518
+ .color(@link);
519
+ &:hover {
520
+ .color(@link-hover);
521
+ border-color: #d8d8d8;
522
+ }
518
523
  }
519
524
  .generic-attachment--element-title {
520
525
  padding: 0px 13px;
@@ -789,8 +794,7 @@ a.product_attachment__title {
789
794
 
790
795
  .message-event-actions {
791
796
  position: absolute;
792
- bottom: -18px;
793
- margin-top: 4px;
797
+ bottom: -10px;
794
798
  opacity: 0.7;
795
799
  left: calc(100% - 25px);
796
800
  display: none;
@@ -798,6 +802,10 @@ a.product_attachment__title {
798
802
  background-color: white;
799
803
  border-radius: 4px;
800
804
  z-index: 10;
805
+
806
+ &.ended {
807
+ bottom: 14px;
808
+ }
801
809
  }
802
810
 
803
811
  .message-event-actions:hover {
@@ -808,13 +816,22 @@ a.product_attachment__title {
808
816
  display: flex;
809
817
  }
810
818
 
819
+ .message-event-actions-static {
820
+ visibility: hidden;
821
+ display: flex;
822
+ margin-top: 2px;
823
+ border: none;
824
+ padding: 0;
825
+ gap: 4px;
826
+ }
827
+
811
828
  .agent-convo-event-inner.last {
812
829
  .message-event-actions {
813
- display: flex;
814
- position: unset;
815
- margin-top: 4px;
816
- border: none;
817
- padding: 0;
830
+ display: none;
831
+ }
832
+
833
+ .message-event-actions-static {
834
+ visibility: visible;
818
835
  }
819
836
 
820
837
  .message-event-action-btn {
@@ -857,7 +874,6 @@ a.product_attachment__title {
857
874
  }
858
875
 
859
876
  .message-event-action-btn {
860
- height: 24px;
861
877
  display: flex;
862
878
  align-items: center;
863
879
  justify-content: center;
@@ -963,3 +979,23 @@ a.product_attachment__title {
963
979
  .text-message-li {
964
980
  display: list-item;
965
981
  }
982
+
983
+ .agent-message .text-message-link {
984
+ .color(@link);
985
+ font-weight: 400;
986
+
987
+ &:hover {
988
+ .color(@link-hover);
989
+ }
990
+ }
991
+
992
+ .user-message .text-message-link {
993
+ text-decoration: underline;
994
+ text-decoration-color: rgba(255, 255, 255, 0.6);
995
+ text-underline-offset: 4px;
996
+
997
+ &:hover {
998
+ text-decoration-color: #fff;
999
+ background-color: rgba(255, 255, 255, 0.08);
1000
+ }
1001
+ }
@@ -247,7 +247,7 @@
247
247
  justify-content: center;
248
248
  padding-left: 32px;
249
249
  padding-right: 32px;
250
- height: 40px;
250
+ height: 42px;
251
251
  .color(@text-color);
252
252
  font-size: 16px;
253
253
  font-weight: 500;
@@ -779,9 +779,9 @@
779
779
  left: -20px;
780
780
  bottom: 100%;
781
781
 
782
- &.mobile {
783
- display: flex;
784
- }
782
+ &.mobile {
783
+ display: flex;
784
+ }
785
785
 
786
786
  &.right {
787
787
  justify-content: flex-end;
@@ -790,14 +790,19 @@
790
790
  }
791
791
 
792
792
  .widget-preview--btn-close {
793
+ opacity: 1;
793
794
  width: 32px;
794
795
  height: 32px;
796
+ border: 1px solid #ececec;
797
+ box-shadow: 0px 5px 30px rgba(0, 0, 0, 0.15);
795
798
  background-color: #00000040;
796
799
  background-size: 16px 16px;
797
- background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXIteCI+PGxpbmUgeDE9IjE4IiB5MT0iNiIgeDI9IjYiIHkyPSIxOCI+PC9saW5lPjxsaW5lIHgxPSI2IiB5MT0iNiIgeDI9IjE4IiB5Mj0iMTgiPjwvbGluZT48L3N2Zz4K');
800
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNlY2VjZWMiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXIteCI+PGxpbmUgeDE9IjE4IiB5MT0iNiIgeDI9IjYiIHkyPSIxOCI+PC9saW5lPjxsaW5lIHgxPSI2IiB5MT0iNiIgeDI9IjE4IiB5Mj0iMTgiPjwvbGluZT48L3N2Zz4K');
798
801
 
799
802
  &:hover {
803
+ border-color: #fff;
800
804
  background-color: #00000066;
805
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXIteCI+PGxpbmUgeDE9IjE4IiB5MT0iNiIgeDI9IjYiIHkyPSIxOCI+PC9saW5lPjxsaW5lIHgxPSI2IiB5MT0iNiIgeDI9IjE4IiB5Mj0iMTgiPjwvbGluZT48L3N2Zz4K');
801
806
  }
802
807
  }
803
808
  }
@@ -21,7 +21,7 @@ function _default(store) {
21
21
  if (!bot.welcome_message) return;
22
22
  let {
23
23
  error
24
- } = await store.startLightConvo(bot.id, bot.welcome_message, common.getLocale(bot, _index.default.detectLang()), true);
24
+ } = await store.startLightConvo(bot.id, bot.welcome_message, common.getLocale(bot, _index.default.detectLang()), false);
25
25
  if (error) return console.error(error);
26
26
  store.logBotRun(bot.id);
27
27
  return;
@@ -16,7 +16,7 @@ function Buttons(props) {
16
16
  href: 'tel:' + button.phone_number,
17
17
  key: index,
18
18
  title: button.title
19
- }, props.t.i18n(button.i18n_title) || button.title);
19
+ }, props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || `${props.t('call')} ${button.phone_number}`);
20
20
  }
21
21
  if (button.type === 'url_button') {
22
22
  return (0, _preact.h)("a", {
@@ -25,7 +25,7 @@ function Buttons(props) {
25
25
  target: "_blank",
26
26
  key: index,
27
27
  title: button.title
28
- }, props.t.i18n(button.i18n_title) || button.title || button.url);
28
+ }, props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || button.url);
29
29
  }
30
30
  if (button.type === 'postback_button') {
31
31
  return (0, _preact.h)("a", {
@@ -51,16 +51,23 @@ class ChatButton extends _common.Com {
51
51
  let mobileDisabled = _index.default.get(this.props.plugin, 'chatbox.greeting.mobile_disabled');
52
52
  if (mobileDisabled) {
53
53
  this.props.store.updateLastview({
54
+ screen: 'button',
54
55
  previewEvent: ev
55
56
  });
56
57
  this.forceUpdate();
57
58
  return;
58
59
  }
59
60
  }
60
- this.props.store.updateLastview({
61
- screen: 'preview',
62
- previewEvent: ev
63
- });
61
+ let accid = _index.default.get(this.props.store.matchAccount(), 'id');
62
+ if (accid === 'acsbdpjwipneldivgwcr') {
63
+ let cid = _index.default.convoOfEv(ev);
64
+ if (cid) this.props.onNewMessageShowWidget && this.props.onNewMessageShowWidget(cid);
65
+ } else {
66
+ this.props.store.updateLastview({
67
+ screen: 'preview',
68
+ previewEvent: ev
69
+ });
70
+ }
64
71
  this.forceUpdate();
65
72
  });
66
73
  this.props.store.onLastview(this, () => {
@@ -125,6 +132,7 @@ class ChatButton extends _common.Com {
125
132
  let byid = greeting.agent_id;
126
133
  if (!byid) byid = randomAgent(agentM).id;
127
134
  let text = this.props.t.i18n(_index.default.get(greeting, 'greeting', {})) || greeting.message;
135
+ if (!text) return {};
128
136
  return {
129
137
  by: {
130
138
  id: byid,
@@ -287,6 +295,7 @@ class ChatButton extends _common.Com {
287
295
  let quickReplies = _index.default.find(attachments, att => att.type === 'quick_replies');
288
296
  quickReplies = _index.default.get(quickReplies, 'quick_replies') || [];
289
297
  quickReplies = _index.default.take(quickReplies, 10);
298
+ if (_index.default.get(accountSetting, 'greeting.hide_quick_replies')) quickReplies = [];
290
299
  let imgAtt = _index.default.find(attachments, att => _index.default.includes(att.mimetype, 'image') && att.url);
291
300
  let uri = _index.default.get(imgAtt, 'url') || '';
292
301
  if (uri && !uri.startsWith('https://') && !uri.startsWith('http://') && !uri.startsWith('content://') && !uri.startsWith('data:')) {
@@ -326,7 +326,7 @@ class Event extends _common.Com {
326
326
  });
327
327
  }
328
328
  let $sender = null;
329
- if (this.props.event.mode === 'lead') $sender = (0, _preact.h)("div", {
329
+ if (this.props.event.mode === 'lead' && _index.default.get(ev, 'by.type') !== 'user') $sender = (0, _preact.h)("div", {
330
330
  class: "pt-3"
331
331
  });
332
332
  let $error = null;
@@ -445,8 +445,22 @@ class Event extends _common.Com {
445
445
  if (hasLike) likecls += ' active';
446
446
  let dislikecls = 'message-event-action-btn';
447
447
  if (hasDislike) dislikecls += ' active';
448
- return (0, _preact.h)("div", {
449
- class: "message-event-actions"
448
+ let actionscls = 'message-event-actions';
449
+ if (_index.default.get(this.props.event, 'is_end')) actionscls += ' ended';
450
+ return (0, _preact.h)(_preact.Fragment, null, (0, _preact.h)("div", {
451
+ class: actionscls
452
+ }, (0, _preact.h)("div", {
453
+ class: likecls,
454
+ onClick: () => this.onSendReaction(hasLike ? 'remove_like' : 'like')
455
+ }, (0, _preact.h)("div", {
456
+ class: "message-event-action-btn-svg like"
457
+ })), (0, _preact.h)("div", {
458
+ class: dislikecls,
459
+ onClick: () => this.onSendReaction(hasDislike ? 'remove_dislike' : 'dislike')
460
+ }, (0, _preact.h)("div", {
461
+ class: "message-event-action-btn-svg dislike"
462
+ }))), _index.default.get(this.props.event, 'is_end') && (0, _preact.h)("div", {
463
+ class: "message-event-actions-static"
450
464
  }, (0, _preact.h)("div", {
451
465
  class: likecls,
452
466
  onClick: () => this.onSendReaction(hasLike ? 'remove_like' : 'like')
@@ -457,7 +471,7 @@ class Event extends _common.Com {
457
471
  onClick: () => this.onSendReaction(hasDislike ? 'remove_dislike' : 'dislike')
458
472
  }, (0, _preact.h)("div", {
459
473
  class: "message-event-action-btn-svg dislike"
460
- })));
474
+ }))));
461
475
  }
462
476
  renderAIDevNote() {
463
477
  if (!this.props.isLast) return null;
@@ -130,10 +130,10 @@ class Events extends _common.Com {
130
130
  }
131
131
  }
132
132
  };
133
- events.push(fakeev);
134
133
  processEvents(convo, events);
134
+ events.push(fakeev);
135
135
  let $events = _index.default.map(events, ev => {
136
- let isLast = ev.ev.id === _index.default.get(convo, 'last_message_sent.id');
136
+ let isLast = ev.is_last_message_send;
137
137
  return (0, _preact.h)(_Event.default, {
138
138
  isLast: isLast,
139
139
  chatbox: this.props.chatbox,
@@ -312,17 +312,26 @@ function processEvents(convo, events) {
312
312
  break;
313
313
  }
314
314
  }
315
+ let lastMsgSentIdx = -1;
315
316
  for (var i = 0; i < events.length; i++) {
316
317
  let curEvent = events[i];
317
318
  let prevEvent = events[i - 1];
319
+ let nextEvent = events[i + 1];
318
320
  if (curEvent.ev.type === 'message_sent') {
321
+ lastMsgSentIdx = i;
319
322
  curEvent.mode = 'lead';
320
323
  if (_index.default.get(prevEvent, 'ev.type') === 'message_sent' && _index.default.get(prevEvent, 'ev.by.id') === _index.default.get(curEvent, 'ev.by.id')) {
321
324
  curEvent.mode = 'follow';
322
325
  }
326
+ if (_index.default.get(nextEvent, 'ev.type') !== 'message_sent' || _index.default.get(nextEvent, 'ev.by.id') !== _index.default.get(curEvent, 'ev.by.id')) {
327
+ curEvent.is_end = true;
328
+ }
323
329
  continue;
324
330
  }
325
331
  }
332
+ if (lastMsgSentIdx > -1) {
333
+ events[lastMsgSentIdx].is_last_message_send = true;
334
+ }
326
335
  }
327
336
  function visibleEvent(ev) {
328
337
  let type = _index.default.get(ev, 'type');
@@ -45,15 +45,18 @@ class TextMessage extends _common.Com {
45
45
  url = '//' + url;
46
46
  }
47
47
  return (0, _preact.h)("a", {
48
+ class: "text-message-link",
48
49
  href: url,
49
50
  target: "_blank",
50
51
  onClick: e => this.props.onLink(e, url)
51
52
  }, tag.text);
52
53
  }
53
54
  if (tag.type === 'phone') return (0, _preact.h)("a", {
55
+ class: "text-message-link",
54
56
  href: 'tel:' + tag.text
55
57
  }, tag.text);
56
58
  if (tag.type === 'email') return (0, _preact.h)("a", {
59
+ class: "text-message-link",
57
60
  href: 'mailto:' + tag.text
58
61
  }, tag.text);
59
62
  return tag.text;
@@ -724,7 +724,9 @@ class Widget extends _common.Com {
724
724
  class: "widget-body--content flex-grow-1 pl-3 pr-3 pb-2 d-flex flex-column"
725
725
  }, (0, _preact.h)("div", {
726
726
  class: "mt-2 d-flex flex-column"
727
- }, (0, _preact.h)("div", null, title), this.renderFields(form, formName)), (0, _preact.h)("div", {
727
+ }, (0, _preact.h)("div", null, title), this.renderFields(form, formName)), this.props.store.isMobile() ? (0, _preact.h)("div", {
728
+ style: "height: 20px !important; flex-shrink: 0 !important"
729
+ }) : (0, _preact.h)("div", {
728
730
  style: "min-height: 15px !important; flex: 1 !important; flex-shrink: 0 !important"
729
731
  }), (0, _preact.h)("button", {
730
732
  class: btncls,
@@ -179,6 +179,7 @@ class Block extends _common.Com {
179
179
  return (0, _preact.h)("hr", null);
180
180
  case 'a':
181
181
  return (0, _preact.h)("a", {
182
+ class: this.props.textMessage ? 'message-text-link' : '',
182
183
  href: ele.attrs['href'],
183
184
  target: this.props.textMessage ? '_blank' : ele.attrs['target'],
184
185
  title: ele.attrs['title']
@@ -86,6 +86,7 @@ class Main extends _common.Com {
86
86
  ref: this.refButton,
87
87
  isShow: !isShowWidget,
88
88
  onClick: e => this.showWidget(e),
89
+ onNewMessageShowWidget: cid => this.onNewMessageShowWidget(cid),
89
90
  onSelectQuickReply: msg => this.onSelectQuickReply(msg),
90
91
  t: this.props.t
91
92
  }), (0, _preact.h)(_Widget.default, {
@@ -143,6 +144,18 @@ class Main extends _common.Com {
143
144
  this.refWidget.current && this.refWidget.current.sendMessage(message);
144
145
  });
145
146
  }
147
+ onNewMessageShowWidget(cid) {
148
+ if (this.props.store.isMobile()) modifyViewport();
149
+ this.props.store.updateLastview({
150
+ screen: 'widget',
151
+ previewEvent: {},
152
+ view: 'chat',
153
+ opts: {
154
+ cid
155
+ }
156
+ });
157
+ this.forceUpdate();
158
+ }
146
159
  async showWidget(p) {
147
160
  let cid = p.cid;
148
161
  let newView = {
@@ -183,7 +196,8 @@ class Main extends _common.Com {
183
196
  restoreViewport();
184
197
  this.props.store.updateLastview({
185
198
  screen: 'button',
186
- previewEvent: {}
199
+ previewEvent: {},
200
+ minimized: Date.now()
187
201
  });
188
202
  this.forceUpdate();
189
203
  }
@@ -521,7 +521,14 @@ class Store {
521
521
  lastView.screen = 'widget';
522
522
  }
523
523
  }
524
- if (this.isMobile()) lastView.screen = 'preview';
524
+ if (this.isMobile()) {
525
+ lastView.screen = 'preview';
526
+ let plugins = this.matchPlugin() || {};
527
+ let chatbox = _index.default.find(plugins, plugin => plugin.type === 'chatbox') || {};
528
+ if (_index.default.get(chatbox, 'chatbox.greeting.mobile_disabled')) {
529
+ lastView.screen = 'button';
530
+ }
531
+ }
525
532
  this.kv.put('lastview', lastView);
526
533
  }
527
534
  async _initResource(accid, auth, realtimep) {
@@ -875,7 +882,6 @@ class Store {
875
882
  this.pubsub.publish('events', oldev);
876
883
  }
877
884
  if (ev.type !== 'message_pong' && ev.type !== 'conversation_typing') {
878
- this.chain.put(cid, ev.id, _index.default.getMs(ev.created), ev);
879
885
  if (ev.type === 'message_sent') {
880
886
  let idemkey = _index.default.get(ev, 'data.message.idempotency_key');
881
887
  let found = _index.default.find(this.listEvents(cid), oldev => oldev.id === idemkey);
@@ -885,6 +891,7 @@ class Store {
885
891
  }
886
892
  convo.last_message_sent = ev;
887
893
  }
894
+ this.chain.put(cid, ev.id, _index.default.getMs(ev.created), ev);
888
895
  }
889
896
  this._setConvo(convo);
890
897
  this.pubsub.publish('account');
@@ -148,10 +148,13 @@ function extractLinks(text) {
148
148
  var out = [];
149
149
  var match;
150
150
  while (match = reg.exec(text)) {
151
- out.push({
152
- index: match.index,
153
- length: reg.lastIndex - match.index
154
- });
151
+ const MINIMUM_URL_CHARS = 5;
152
+ if (reg.lastIndex - match.index > MINIMUM_URL_CHARS + 1) {
153
+ out.push({
154
+ index: match.index,
155
+ length: reg.lastIndex - match.index
156
+ });
157
+ }
155
158
  }
156
159
  return out;
157
160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.1.186",
3
+ "version": "4.1.190",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -22,14 +22,14 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@babel/cli": "^7.28.3",
25
- "@babel/core": "^7.28.4",
25
+ "@babel/core": "^7.28.5",
26
26
  "@babel/plugin-proposal-class-properties": "^7.18.6",
27
27
  "@babel/plugin-transform-react-jsx": "^7.27.1",
28
- "@babel/preset-env": "^7.28.3",
28
+ "@babel/preset-env": "^7.28.5",
29
29
  "babel-loader": "^10.0.0",
30
30
  "babel-plugin-syntax-jsx": "^6.18.0",
31
31
  "html-webpack-plugin": "^5.6.4",
32
- "puppeteer": "^24.24.0",
32
+ "puppeteer": "^24.27.0",
33
33
  "webpack": "^5.102.1",
34
34
  "webpack-bundle-analyzer": "^4.10.2",
35
35
  "webpack-cli": "^6.0.1",
package/src/bot.js CHANGED
@@ -17,7 +17,7 @@ export default function (store) {
17
17
  bot.id,
18
18
  bot.welcome_message,
19
19
  common.getLocale(bot, sb.detectLang()),
20
- true,
20
+ false,
21
21
  )
22
22
  if (error) return console.error(error)
23
23
  store.logBotRun(bot.id)
@@ -7,7 +7,7 @@ export default function Buttons(props) {
7
7
  if (button.type === 'call_button') {
8
8
  return (
9
9
  <a class='url-button-attachment' href={'tel:' + button.phone_number} key={index} title={button.title}>
10
- {props.t.i18n(button.i18n_title) || button.title}
10
+ {props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || `${props.t('call')} ${button.phone_number}`}
11
11
  </a>
12
12
  )
13
13
  }
@@ -15,7 +15,7 @@ export default function Buttons(props) {
15
15
  if (button.type === 'url_button') {
16
16
  return (
17
17
  <a class='url-button-attachment' href={button.url} target='_blank' key={index} title={button.title}>
18
- {props.t.i18n(button.i18n_title) || button.title || button.url}
18
+ {props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || button.url}
19
19
  </a>
20
20
  )
21
21
  }
@@ -52,13 +52,21 @@ export default class ChatButton extends Com {
52
52
  let mobileDisabled = sb.get(this.props.plugin, 'chatbox.greeting.mobile_disabled')
53
53
  if (mobileDisabled) {
54
54
  // need setState previewEvent for display 1 unread in mobile
55
- this.props.store.updateLastview({previewEvent: ev})
55
+ this.props.store.updateLastview({screen: 'button', previewEvent: ev})
56
56
  this.forceUpdate()
57
57
  return
58
58
  }
59
59
  }
60
60
 
61
- this.props.store.updateLastview({screen: 'preview', previewEvent: ev})
61
+ let accid = sb.get(this.props.store.matchAccount(), 'id')
62
+
63
+ // american study request old flow
64
+ if (accid === 'acsbdpjwipneldivgwcr') {
65
+ let cid = sb.convoOfEv(ev)
66
+ if (cid) this.props.onNewMessageShowWidget && this.props.onNewMessageShowWidget(cid)
67
+ } else {
68
+ this.props.store.updateLastview({screen: 'preview', previewEvent: ev})
69
+ }
62
70
  this.forceUpdate()
63
71
  })
64
72
  this.props.store.onLastview(this, () => {
@@ -145,6 +153,7 @@ export default class ChatButton extends Com {
145
153
  if (!byid) byid = randomAgent(agentM).id
146
154
 
147
155
  let text = this.props.t.i18n(sb.get(greeting, 'greeting', {})) || greeting.message
156
+ if (!text) return {} // work for api subiz('overrideChatbox', {greeting; ''})
148
157
  return {
149
158
  by: {id: byid, type: 'agent'},
150
159
  data: {message: {text, format: 'plaintext'}},
@@ -299,6 +308,8 @@ export default class ChatButton extends Com {
299
308
  let quickReplies = sb.find(attachments, (att) => att.type === 'quick_replies')
300
309
  quickReplies = sb.get(quickReplies, 'quick_replies') || []
301
310
  quickReplies = sb.take(quickReplies, 10)
311
+ // dont show with setting greeting.hide_quick_replies
312
+ if (sb.get(accountSetting, 'greeting.hide_quick_replies')) quickReplies = []
302
313
  let imgAtt = sb.find(attachments, (att) => sb.includes(att.mimetype, 'image') && att.url)
303
314
  let uri = sb.get(imgAtt, 'url') || ''
304
315
  if (
@@ -19,7 +19,7 @@ import InfoMessage from './InfoMessage.js' // message by system like convo join,
19
19
  import sb from '../util/index.js'
20
20
  import Time from './time.js'
21
21
  import {Com} from '../common.js'
22
- import {h, createRef} from 'preact'
22
+ import {h, createRef, Fragment} from 'preact'
23
23
 
24
24
  // ['event'],
25
25
  export default class Event extends Com {
@@ -300,7 +300,7 @@ export default class Event extends Com {
300
300
  }
301
301
 
302
302
  let $sender = null
303
- if (this.props.event.mode === 'lead') $sender = <div class='pt-3'></div>
303
+ if (this.props.event.mode === 'lead' && sb.get(ev, 'by.type') !== 'user') $sender = <div class='pt-3'></div>
304
304
 
305
305
  let $error = null
306
306
  if (ev._error) $dot = null
@@ -396,15 +396,31 @@ export default class Event extends Com {
396
396
  if (hasLike) likecls += ' active'
397
397
  let dislikecls = 'message-event-action-btn'
398
398
  if (hasDislike) dislikecls += ' active'
399
+
400
+ let actionscls = 'message-event-actions'
401
+ if (sb.get(this.props.event, 'is_end')) actionscls += ' ended'
402
+
399
403
  return (
400
- <div class='message-event-actions'>
401
- <div class={likecls} onClick={() => this.onSendReaction(hasLike ? 'remove_like' : 'like')}>
402
- <div class='message-event-action-btn-svg like' />
403
- </div>
404
- <div class={dislikecls} onClick={() => this.onSendReaction(hasDislike ? 'remove_dislike' : 'dislike')}>
405
- <div class='message-event-action-btn-svg dislike' />
404
+ <Fragment>
405
+ <div class={actionscls}>
406
+ <div class={likecls} onClick={() => this.onSendReaction(hasLike ? 'remove_like' : 'like')}>
407
+ <div class='message-event-action-btn-svg like' />
408
+ </div>
409
+ <div class={dislikecls} onClick={() => this.onSendReaction(hasDislike ? 'remove_dislike' : 'dislike')}>
410
+ <div class='message-event-action-btn-svg dislike' />
411
+ </div>
406
412
  </div>
407
- </div>
413
+ {sb.get(this.props.event, 'is_end') && (
414
+ <div class='message-event-actions-static'>
415
+ <div class={likecls} onClick={() => this.onSendReaction(hasLike ? 'remove_like' : 'like')}>
416
+ <div class='message-event-action-btn-svg like' />
417
+ </div>
418
+ <div class={dislikecls} onClick={() => this.onSendReaction(hasDislike ? 'remove_dislike' : 'dislike')}>
419
+ <div class='message-event-action-btn-svg dislike' />
420
+ </div>
421
+ </div>
422
+ )}
423
+ </Fragment>
408
424
  )
409
425
  }
410
426
 
@@ -124,11 +124,13 @@ export default class Events extends Com {
124
124
  data: {message: {text: '...', conversation_id: this.props.cid}},
125
125
  },
126
126
  }
127
- events.push(fakeev)
127
+
128
128
  processEvents(convo, events)
129
+ // we dont want fakeev cause some unexpected buf when processEvents
130
+ events.push(fakeev)
129
131
 
130
132
  let $events = sb.map(events, (ev) => {
131
- let isLast = ev.ev.id === sb.get(convo, 'last_message_sent.id')
133
+ let isLast = ev.is_last_message_send
132
134
  return (
133
135
  <Event
134
136
  isLast={isLast}
@@ -320,12 +322,16 @@ function processEvents(convo, events) {
320
322
  }
321
323
  }
322
324
 
325
+ let lastMsgSentIdx = -1
323
326
  // insert lead
324
327
  for (var i = 0; i < events.length; i++) {
325
328
  let curEvent = events[i]
326
329
  let prevEvent = events[i - 1]
330
+ let nextEvent = events[i + 1]
327
331
 
328
332
  if (curEvent.ev.type === 'message_sent') {
333
+ // ignore fake typing message_sent event at last
334
+ lastMsgSentIdx = i
329
335
  curEvent.mode = 'lead'
330
336
  if (
331
337
  sb.get(prevEvent, 'ev.type') === 'message_sent' &&
@@ -333,9 +339,18 @@ function processEvents(convo, events) {
333
339
  ) {
334
340
  curEvent.mode = 'follow'
335
341
  }
342
+ if (
343
+ sb.get(nextEvent, 'ev.type') !== 'message_sent' ||
344
+ sb.get(nextEvent, 'ev.by.id') !== sb.get(curEvent, 'ev.by.id')
345
+ ) {
346
+ curEvent.is_end = true
347
+ }
336
348
  continue
337
349
  }
338
350
  }
351
+ if (lastMsgSentIdx > -1) {
352
+ events[lastMsgSentIdx].is_last_message_send = true
353
+ }
339
354
  }
340
355
 
341
356
  function visibleEvent(ev) {
@@ -45,13 +45,23 @@ export default class TextMessage extends Com {
45
45
  }
46
46
 
47
47
  return (
48
- <a href={url} target='_blank' onClick={(e) => this.props.onLink(e, url)}>
48
+ <a class='text-message-link' href={url} target='_blank' onClick={(e) => this.props.onLink(e, url)}>
49
49
  {tag.text}
50
50
  </a>
51
51
  )
52
52
  }
53
- if (tag.type === 'phone') return <a href={'tel:' + tag.text}>{tag.text}</a>
54
- if (tag.type === 'email') return <a href={'mailto:' + tag.text}>{tag.text}</a>
53
+ if (tag.type === 'phone')
54
+ return (
55
+ <a class='text-message-link' href={'tel:' + tag.text}>
56
+ {tag.text}
57
+ </a>
58
+ )
59
+ if (tag.type === 'email')
60
+ return (
61
+ <a class='text-message-link' href={'mailto:' + tag.text}>
62
+ {tag.text}
63
+ </a>
64
+ )
55
65
  return tag.text
56
66
  })
57
67
 
@@ -80,7 +90,7 @@ export default class TextMessage extends Com {
80
90
  if (byType === 'agent') {
81
91
  return (
82
92
  <div class='message-agent message-text--agent' title={t}>
83
- <Block block={block} onLink={this.props.onLink} textMessage/>
93
+ <Block block={block} onLink={this.props.onLink} textMessage />
84
94
  {this.renderTime()}
85
95
  </div>
86
96
  )
@@ -89,7 +99,7 @@ export default class TextMessage extends Com {
89
99
  if (this.props.ev._error) msgcls += ' message-user--error'
90
100
  return (
91
101
  <div class={msgcls} title={t}>
92
- <Block block={block} onLink={this.props.onLink} textMessage/>
102
+ <Block block={block} onLink={this.props.onLink} textMessage />
93
103
  </div>
94
104
  )
95
105
  }
@@ -650,7 +650,11 @@ export default class Widget extends Com {
650
650
  <div>{title}</div>
651
651
  {this.renderFields(form, formName)}
652
652
  </div>
653
- <div style='min-height: 15px !important; flex: 1 !important; flex-shrink: 0 !important'></div>
653
+ {this.props.store.isMobile() ? (
654
+ <div style='height: 20px !important; flex-shrink: 0 !important'></div>
655
+ ) : (
656
+ <div style='min-height: 15px !important; flex: 1 !important; flex-shrink: 0 !important'></div>
657
+ )}
654
658
  <button
655
659
  class={btncls}
656
660
  onClick={(_) => this.submitPrechatForm(form, formName)}
@@ -188,6 +188,7 @@ export default class Block extends Com {
188
188
  case 'a':
189
189
  return (
190
190
  <a
191
+ class={this.props.textMessage ? 'message-text-link' : ''}
191
192
  href={ele.attrs['href']}
192
193
  target={this.props.textMessage ? '_blank' : ele.attrs['target']}
193
194
  title={ele.attrs['title']}
@@ -91,6 +91,7 @@ export default class Main extends Com {
91
91
  ref={this.refButton}
92
92
  isShow={!isShowWidget}
93
93
  onClick={(e) => this.showWidget(e)}
94
+ onNewMessageShowWidget={(cid) => this.onNewMessageShowWidget(cid)}
94
95
  onSelectQuickReply={(msg) => this.onSelectQuickReply(msg)}
95
96
  t={this.props.t}
96
97
  />
@@ -152,6 +153,12 @@ export default class Main extends Com {
152
153
  })
153
154
  }
154
155
 
156
+ onNewMessageShowWidget(cid) {
157
+ if (this.props.store.isMobile()) modifyViewport()
158
+ this.props.store.updateLastview({screen: 'widget', previewEvent: {}, view: 'chat', opts: {cid}})
159
+ this.forceUpdate()
160
+ }
161
+
155
162
  async showWidget(p) {
156
163
  //this.refButton.current && this.refButton.current.ClearPreview()
157
164
 
@@ -189,7 +196,7 @@ export default class Main extends Com {
189
196
 
190
197
  hideWidget() {
191
198
  restoreViewport()
192
- this.props.store.updateLastview({screen: 'button', previewEvent: {}})
199
+ this.props.store.updateLastview({screen: 'button', previewEvent: {}, minimized: Date.now()})
193
200
  this.forceUpdate()
194
201
  }
195
202
 
@@ -594,8 +594,14 @@ export default class Store {
594
594
  }
595
595
  }
596
596
 
597
- // mobile always has screen button initially
598
- if (this.isMobile()) lastView.screen = 'preview'
597
+ if (this.isMobile()) {
598
+ lastView.screen = 'preview'
599
+ let plugins = this.matchPlugin() || {}
600
+ let chatbox = sb.find(plugins, (plugin) => plugin.type === 'chatbox') || {}
601
+ if (sb.get(chatbox, 'chatbox.greeting.mobile_disabled')) {
602
+ lastView.screen = 'button'
603
+ }
604
+ }
599
605
  this.kv.put('lastview', lastView)
600
606
  }
601
607
 
@@ -1033,7 +1039,6 @@ export default class Store {
1033
1039
 
1034
1040
  // dont store pong and typing
1035
1041
  if (ev.type !== 'message_pong' && ev.type !== 'conversation_typing') {
1036
- this.chain.put(cid, ev.id, sb.getMs(ev.created), ev)
1037
1042
  if (ev.type === 'message_sent') {
1038
1043
  let idemkey = sb.get(ev, 'data.message.idempotency_key')
1039
1044
  let found = sb.find(this.listEvents(cid), (oldev) => oldev.id === idemkey)
@@ -1045,6 +1050,7 @@ export default class Store {
1045
1050
 
1046
1051
  convo.last_message_sent = ev
1047
1052
  }
1053
+ this.chain.put(cid, ev.id, sb.getMs(ev.created), ev)
1048
1054
  }
1049
1055
  this._setConvo(convo)
1050
1056
  this.pubsub.publish('account')
@@ -102,7 +102,10 @@ export function extractLinks(text) {
102
102
  var out = []
103
103
  var match
104
104
  while ((match = reg.exec(text))) {
105
- out.push({index: match.index, length: reg.lastIndex - match.index})
105
+ const MINIMUM_URL_CHARS = 5
106
+ if (reg.lastIndex - match.index > MINIMUM_URL_CHARS + 1) {
107
+ out.push({index: match.index, length: reg.lastIndex - match.index})
108
+ }
106
109
  }
107
110
 
108
111
  return out