@subiz/widget-v4 4.1.178 → 4.1.179

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.
@@ -16,6 +16,8 @@
16
16
  @red: #dc3545;
17
17
  @yellow: #ffc107;
18
18
  @green: #28a745;
19
+ @green-light-bg: #28a74526;
20
+ @border: @gray-300;
19
21
 
20
22
  @link: #0d6efd;
21
23
  @link-hover: #024dbc;
@@ -65,3 +67,29 @@
65
67
  -moz-mask-image: url(@url);
66
68
  mask-image: url(@url);
67
69
  }
70
+
71
+ .badge {
72
+ padding: 2px 8px;
73
+ font-size: 12px;
74
+ font-weight: 600;
75
+ border-radius: 6px;
76
+ display: inline-flex;
77
+ align-items: center;
78
+
79
+ &.badge--success {
80
+ .color(@green);
81
+ background: @green-light-bg;
82
+ }
83
+ }
84
+
85
+ .dot {
86
+ width: 8px;
87
+ height: 8px;
88
+ border-radius: 4px;
89
+ flex-shrink: 0;
90
+ background: @black;
91
+
92
+ &.dot--success {
93
+ background: @green;
94
+ }
95
+ }
@@ -92,6 +92,7 @@ body div#sbz92 {
92
92
 
93
93
  font-family: @font-family-sans-serif;
94
94
  font-size: @font-size-base;
95
+ line-height: 1.25;
95
96
 
96
97
  -webkit-box-sizing: border-box; // Safari/Chrome, other WebKit
97
98
  -moz-box-sizing: border-box; // Firefox, other Gecko
@@ -209,6 +210,9 @@ body div#sbz92 {
209
210
  .ml-2 {
210
211
  margin-left: @spacer2;
211
212
  }
213
+ .mr-1 {
214
+ margin-right: @spacer1;
215
+ }
212
216
  .mr-2 {
213
217
  margin-right: @spacer2;
214
218
  }
@@ -507,7 +507,6 @@ a.message_error {
507
507
  }
508
508
 
509
509
  .url-button-attachment {
510
- text-decoration: none;
511
510
  padding: 10px;
512
511
  display: block;
513
512
  padding-top: 9px;
@@ -542,13 +541,6 @@ a.message_error {
542
541
 
543
542
  .generic-attachment--element.clickable {
544
543
  cursor: pointer;
545
- .generic-attachment--element-title {
546
- .color(@link);
547
- }
548
-
549
- &:hover .generic-attachment--element-title {
550
- .color(@link-hover);
551
- }
552
544
  }
553
545
 
554
546
  .widget_body__events__error_msg {
@@ -574,3 +574,164 @@
574
574
  color: #fff;
575
575
  }
576
576
  }
577
+
578
+ .widget-preview-text-message--header {
579
+ display: flex;
580
+ overflow: hidden;
581
+ align-items: center;
582
+ width: 100%;
583
+
584
+ .widget-preview--avatar {
585
+ width: 24px;
586
+ height: 24px;
587
+ flex-shrink: 0;
588
+ margin-right: 10px;
589
+ }
590
+ }
591
+
592
+ .widget-preview-text-message--author {
593
+ flex: 1;
594
+ overflow: hidden;
595
+ display: flex;
596
+ align-items: center;
597
+ }
598
+
599
+ .widget-preview-text-message--author-text {
600
+ text-overflow: ellipsis;
601
+ white-space: nowrap;
602
+ overflow: hidden;
603
+ margin-right: 10px;
604
+ line-height: 1.5;
605
+ .color(@secondary-color);
606
+ }
607
+
608
+ .widget-preview-text-message--content {
609
+ margin-top: 10px;
610
+ white-space: pre-wrap;
611
+ }
612
+
613
+ .widget-preview-quick-reply-wrapper {
614
+ z-index: @z-index-3;
615
+ width: 100%;
616
+ margin-bottom: 15px;
617
+ display: flex;
618
+ flex-direction: column;
619
+ align-items: flex-start;
620
+
621
+ &.right {
622
+ align-items: flex-end;
623
+ }
624
+ }
625
+
626
+ .widget-preview-quick-reply-item {
627
+ border-radius: 20px;
628
+ padding: 8px 20px;
629
+ background: @white;
630
+ border: 1.5px solid;
631
+ border-color: @primary-color;
632
+ .color(@primary-color);
633
+ margin-bottom: 15px;
634
+ cursor: pointer;
635
+ transition: 0.3s ease;
636
+ position: relative;
637
+
638
+ &:last-child {
639
+ margin-bottom: 0;
640
+ }
641
+
642
+ &:hover {
643
+ .color(@white);
644
+ background-color: @primary-color;
645
+ }
646
+ }
647
+
648
+ .widget-preview-image-wrapper {
649
+ z-index: @z-index-3;
650
+ width: 100%;
651
+ margin-bottom: 15px;
652
+ display: flex;
653
+ flex-direction: column;
654
+ align-items: flex-start;
655
+
656
+ &.right {
657
+ align-items: flex-end;
658
+ }
659
+ }
660
+
661
+ .widget-preview-image-item {
662
+ max-width: 300px;
663
+ border-radius: 15px;
664
+ box-shadow: 0px 5px 30px rgba(0, 0, 0, 0.15);
665
+ border: 1px solid @border;
666
+ overflow: hidden;
667
+ cursor: pointer;
668
+
669
+ img {
670
+ width: 100%;
671
+ max-height: 300px;
672
+ object-fit: cover;
673
+ }
674
+ }
675
+
676
+ .widget-preview-text-message {
677
+ z-index: @z-index-3;
678
+ background: @white;
679
+ box-shadow: 0px 5px 30px rgba(0, 0, 0, 0.15);
680
+ border: 1px solid @border;
681
+ border-radius: 15px;
682
+ max-width: 100%;
683
+ margin-bottom: 15px;
684
+ padding: 15px 20px;
685
+ positive: relative;
686
+ cursor: pointer;
687
+
688
+ &.right {
689
+ border-bottom-right-radius: 8px;
690
+ }
691
+ &.left {
692
+ border-bottom-left-radius: 8px;
693
+ }
694
+ }
695
+
696
+ .widget-preview-new-wrapper {
697
+ max-width: 340px;
698
+ position: relative;
699
+
700
+ &.hidden {
701
+ display: none;
702
+ }
703
+
704
+ &:hover {
705
+ .widget-preview-x-wrapper {
706
+ display: flex;
707
+ }
708
+ }
709
+
710
+ .widget-preview--btn-close {
711
+ width: 32px;
712
+ height: 32px;
713
+ background-color: #00000040;
714
+ background-size: 16px 16px;
715
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXIteCI+PGxpbmUgeDE9IjE4IiB5MT0iNiIgeDI9IjYiIHkyPSIxOCI+PC9saW5lPjxsaW5lIHgxPSI2IiB5MT0iNiIgeDI9IjE4IiB5Mj0iMTgiPjwvbGluZT48L3N2Zz4K');
716
+
717
+ &:hover {
718
+ background-color: #00000066;
719
+ }
720
+ }
721
+ }
722
+
723
+ .widget-preview-x-wrapper {
724
+ display: none;
725
+ justify-content: flex-start;
726
+ width: calc(100% + 40px);
727
+ position: absolute;
728
+ padding: 15px 20px;
729
+ left: -20px;
730
+ bottom: 100%;
731
+
732
+ &.right {
733
+ justify-content: flex-end;
734
+ left: unset;
735
+ right: -20px;
736
+ }
737
+ }
@@ -16,9 +16,7 @@ class ChatButton extends _common.Com {
16
16
  constructor(props) {
17
17
  super(props);
18
18
  this.setState({
19
- opacity: 0,
20
- previewEvent: {},
21
- displayPreview: false
19
+ opacity: 0
22
20
  });
23
21
  this.loadGreeting();
24
22
  this.props.store.onUpdate(this, _ => this.forceUpdate());
@@ -41,10 +39,22 @@ class ChatButton extends _common.Com {
41
39
  let message = _index.default.get(ev, 'data.message.text') || '';
42
40
  if (!message && isquickreplies) return;
43
41
  }
44
- this.setState({
45
- displayPreview: true,
42
+ let screen = _index.default.get(this.props.store.matchLastview(), 'screen');
43
+ let minimized = _index.default.get(this.props.store.matchLastview(), 'minimized');
44
+ if (screen === 'widget') return;
45
+ if (minimized && Date.now() - minimized < Ms30Mins) return;
46
+ if (this.props.store.isMobile()) {
47
+ this.props.store.updateLastview({
48
+ previewEvent: ev
49
+ });
50
+ this.forceUpdate();
51
+ return;
52
+ }
53
+ this.props.store.updateLastview({
54
+ screen: 'preview',
46
55
  previewEvent: ev
47
56
  });
57
+ this.forceUpdate();
48
58
  });
49
59
  }
50
60
  fixMissingSetting(chatbox) {
@@ -84,16 +94,6 @@ class ChatButton extends _common.Com {
84
94
  style: 'opacity: ' + opacity + ' !important'
85
95
  }, this.renderPreview(), this.renderButton());
86
96
  }
87
- ClearPreview() {
88
- this.setState({
89
- previewEvent: {}
90
- });
91
- }
92
- ClosePreview() {
93
- this.setState({
94
- displayPreview: false
95
- });
96
- }
97
97
  pickBestGreeting() {
98
98
  let convoM = this.props.store.matchConvo('*');
99
99
  let unseenConvo = _index.default.filter(convoM, convo => {
@@ -126,31 +126,47 @@ class ChatButton extends _common.Com {
126
126
  return unseenConvo[0].last_message_sent;
127
127
  }
128
128
  ShowGreeting() {
129
- this.setState({
130
- previewEvent: this.pickBestGreeting(),
131
- displayPreview: true
129
+ this.props.store.updateLastview({
130
+ screen: 'preview',
131
+ previewEvent: this.pickBestGreeting()
132
132
  });
133
+ this.forceUpdate();
133
134
  }
134
135
  HideGreeting() {
135
- this.setState({
136
+ this.props.store.updateLastview({
137
+ screen: 'button',
138
+ minimized: Date.now(),
136
139
  previewEvent: {}
137
140
  });
141
+ this.forceUpdate();
138
142
  }
139
143
  loadGreeting() {
144
+ let minimized = _index.default.get(this.props.store.matchLastview(), 'minimized');
145
+ let screen = _index.default.get(this.props.store.matchLastview(), 'screen');
146
+ console.log('loadGreeting matchLastview', this.props.store.matchLastview());
147
+ if (screen === 'widget') return;
148
+ if (minimized && Date.now() - minimized < Ms30Mins) return;
140
149
  let accountSetting = this.props.plugin.chatbox;
141
- if (new _index.default.Date().getTime() - this.props.store.matchClosePreviewAt() < Ms30Mins) return;
142
150
  let greeting = _index.default.get(accountSetting, 'greeting', {});
143
151
  let text = this.props.t.i18n(_index.default.get(greeting, 'greeting', {})) || greeting.message;
144
152
  let enabled = greeting.enabled;
145
153
  if (this.props.store.isMobile()) {
146
154
  enabled = enabled && !_index.default.get(greeting, 'mobile_disabled');
155
+ this.props.store.updateLastview({
156
+ screen: 'button',
157
+ previewEvent: {}
158
+ });
159
+ return;
147
160
  }
148
161
  if (!text || !enabled) {
149
- this.setState({
162
+ this.props.store.updateLastview({
163
+ screen: 'button',
150
164
  previewEvent: {}
151
165
  });
166
+ this.forceUpdate();
152
167
  return;
153
168
  }
169
+ console.log('loadGreeting pickBestGreeting', this.pickBestGreeting());
154
170
  this.ShowGreeting();
155
171
  }
156
172
  onClick() {
@@ -168,14 +184,14 @@ class ChatButton extends _common.Com {
168
184
  return this.onPreviewClick();
169
185
  }
170
186
  onPreviewClick(e) {
171
- let cid = _index.default.convoOfEv(this.state.previewEvent);
187
+ let previewEvent = _index.default.get(this.props.store.matchLastview(), 'previewEvent') || {};
188
+ let cid = _index.default.convoOfEv(previewEvent);
172
189
  if (!cid) {
173
190
  let convos = _index.default.map(this.props.store.matchConvo('*'));
174
191
  let convo = _index.default.find(convos, convo => !_index.default.isConvoSeen(convo, this.props.store.matchUser().id));
175
192
  if (!convo) convo = convos[0] || {};
176
193
  cid = convo.id;
177
194
  }
178
- this.closePreview(e);
179
195
  this.props.onClick && this.props.onClick({
180
196
  view: 'chat',
181
197
  cid
@@ -192,7 +208,8 @@ class ChatButton extends _common.Com {
192
208
  let account = this.props.store.matchAccount();
193
209
  if (!account) return null;
194
210
  let accountSetting = this.props.plugin.chatbox;
195
- let byid = _index.default.get(this.state.previewEvent, 'by.display_id', _index.default.get(this.state.previewEvent, 'by.id'));
211
+ let previewEvent = _index.default.get(this.props.store.matchLastview(), 'previewEvent') || {};
212
+ let byid = _index.default.get(previewEvent, 'by.display_id', _index.default.get(previewEvent, 'by.id'));
196
213
  let agent = this.props.store.matchAgent(byid);
197
214
  if (!agent) agent = this.props.store.matchAgent('*')[byid] || {};
198
215
  agent = _index.default.cloneDeep(agent);
@@ -208,14 +225,28 @@ class ChatButton extends _common.Com {
208
225
  fullname: account.name || this.props.t('agent')
209
226
  });
210
227
  }
211
- let cls = 'widget-preview widget-preview--' + position;
212
- if (!this.state.displayPreview) cls += ' widget-preview--hidden';
213
- var tags = this.renderMessagePreview(this.state.previewEvent);
214
- if (_index.default.size(tags) === 0) cls += ' widget-preview--hidden';
228
+ let tags = this.renderMessagePreview(previewEvent);
229
+ let attachments = _index.default.get(previewEvent, 'data.message.attachments') || [];
230
+ let quickReplies = _index.default.find(attachments, att => att.type === 'quick_replies');
231
+ quickReplies = _index.default.get(quickReplies, 'quick_replies') || [];
232
+ let imgAtt = _index.default.find(attachments, att => _index.default.includes(att.mimetype, 'image') && att.url);
233
+ let uri = _index.default.get(imgAtt, 'url') || '';
234
+ if (uri && !uri.startsWith('https://') && !uri.startsWith('http://') && !uri.startsWith('content://') && !uri.startsWith('data:')) {
235
+ uri = this.props.store.FileURL + uri;
236
+ }
237
+ let isShowPreview = _index.default.get(this.props.store.matchLastview(), 'screen') === 'preview';
215
238
  return (0, _preact.h)("div", {
216
- class: cls
239
+ class: `widget-preview-new-wrapper ${isShowPreview ? '' : 'hidden'}`
217
240
  }, (0, _preact.h)("div", {
218
- class: "widget-preview--body"
241
+ class: `widget-preview-x-wrapper ${position}`
242
+ }, (0, _preact.h)("div", {
243
+ class: "widget-preview--btn-close",
244
+ onClick: e => this.closePreview(e)
245
+ })), (0, _preact.h)("div", {
246
+ class: `widget-preview-text-message ${position}`,
247
+ onClick: e => this.onPreviewClick(e)
248
+ }, (0, _preact.h)("div", {
249
+ class: "widget-preview-text-message--header"
219
250
  }, (0, _preact.h)("img", {
220
251
  alt: "widget avatar",
221
252
  class: "widget-preview--avatar",
@@ -223,21 +254,43 @@ class ChatButton extends _common.Com {
223
254
  onError: ev => this.onProfileError(ev),
224
255
  onload: _ => this.onProfileReady()
225
256
  }), (0, _preact.h)("div", {
226
- class: "widget-preview--content",
227
- onClick: e => this.onPreviewClick(e)
257
+ class: "widget-preview-text-message--author"
228
258
  }, (0, _preact.h)("div", {
229
- class: "widget-preview--title"
230
- }, agent.fullname), (0, _preact.h)("p", {
231
- class: "widget-preview--message"
232
- }, tags)), (0, _preact.h)("div", {
233
- class: "widget-preview--btn-close",
234
- onClick: e => this.closePreview(e)
235
- })), (0, _preact.h)("div", {
236
- class: "widget-preview--action",
237
- onClick: e => this.onPreviewClick(e)
259
+ class: "widget-preview-text-message--author-text"
260
+ }, agent.fullname), (0, _preact.h)("div", {
261
+ class: "badge badge--success"
238
262
  }, (0, _preact.h)("div", {
239
- class: "widget-preview--action-text"
240
- }, this.props.t('chat_now'))));
263
+ class: "dot dot--success mr-1"
264
+ }), 'ONLINE'))), (0, _preact.h)("div", {
265
+ class: "widget-preview-text-message--content"
266
+ }, tags)), uri && (0, _preact.h)("div", {
267
+ class: `widget-preview-image-wrapper ${position}`
268
+ }, (0, _preact.h)("div", {
269
+ class: "widget-preview-image-item",
270
+ onClick: () => this.onPreviewClick()
271
+ }, (0, _preact.h)("img", {
272
+ src: uri
273
+ }))), _index.default.size(quickReplies) > 0 && (0, _preact.h)("div", {
274
+ class: `widget-preview-quick-reply-wrapper ${position}`
275
+ }, _index.default.map(quickReplies, reply => {
276
+ return (0, _preact.h)("div", {
277
+ class: "widget-preview-quick-reply-item",
278
+ onClick: () => {
279
+ this.props.onSelectQuickReply({
280
+ text: reply.title,
281
+ format: 'plaintext',
282
+ conversation_id: _index.default.convoOfEv(previewEvent),
283
+ attachments: [{
284
+ type: 'postback',
285
+ postback: {
286
+ event_id: previewEvent.id,
287
+ payload: reply.payload
288
+ }
289
+ }]
290
+ });
291
+ }
292
+ }, reply.title);
293
+ })));
241
294
  }
242
295
  onProfileReady() {
243
296
  if (this._buttonLoaded) this.setState({
@@ -402,11 +455,7 @@ class ChatButton extends _common.Com {
402
455
  e.preventDefault();
403
456
  e.stopPropagation();
404
457
  }
405
- this.setState({
406
- displayPreview: false
407
- });
408
- this.props.store.closePreview();
409
- this.forceUpdate();
458
+ this.HideGreeting();
410
459
  }
411
460
  }
412
461
  exports.default = ChatButton;
@@ -40,10 +40,7 @@ class Main extends _common.Com {
40
40
  this.$cssStyleDiv.id = 'subiz-style-for-' + this.props.plugin.id;
41
41
  }
42
42
  this.setState({
43
- cssReady: false,
44
- isShowWidget: false,
45
- widgetView: '',
46
- widgetConvoId: ''
43
+ cssReady: false
47
44
  });
48
45
  this.checkExpand();
49
46
  this.props.store.onResetCSS(this, () => this.refreshCSS());
@@ -59,15 +56,6 @@ class Main extends _common.Com {
59
56
  this.setState({
60
57
  cssReady: true
61
58
  });
62
- if (this.props.store.matchIsOpen() && this.props.store.matchLastview().view) {
63
- if (this.props.store.isMobile()) return;
64
- this.showWidget(Object.assign({}, this.props.store.matchLastview().opts, {
65
- view: this.props.store.matchLastview().view
66
- }));
67
- } else {
68
- restoreViewport();
69
- if (!this.state.isShowWidget) this.hideWidget();
70
- }
71
59
  }
72
60
  render() {
73
61
  if (!this.state.cssReady) {
@@ -83,6 +71,10 @@ class Main extends _common.Com {
83
71
  return null;
84
72
  }
85
73
  if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_main = _index.default.Date.now();
74
+ let lastview = this.props.store.matchLastview() || {};
75
+ let isShowWidget = lastview.screen === 'widget';
76
+ let widgetView = lastview.view;
77
+ let widgetConvoId = _index.default.get(lastview, 'opts.cid');
86
78
  return (0, _preact.h)("div", {
87
79
  class: this.props.store.isMobile() ? 'mobile' : 'desktop'
88
80
  }, (0, _preact.h)(_ChatButton.default, {
@@ -90,15 +82,16 @@ class Main extends _common.Com {
90
82
  store: this.props.store,
91
83
  key: "chat_button",
92
84
  ref: this.refButton,
93
- isShow: !this.state.isShowWidget,
85
+ isShow: !isShowWidget,
94
86
  onClick: e => this.showWidget(e),
87
+ onSelectQuickReply: msg => this.onSelectQuickReply(msg),
95
88
  t: this.props.t
96
89
  }), (0, _preact.h)(_Widget.default, {
97
90
  plugin: this.props.plugin,
98
91
  store: this.props.store,
99
- view: this.state.widgetView,
100
- cid: this.state.widgetConvoId,
101
- isShow: this.state.isShowWidget,
92
+ view: widgetView,
93
+ cid: widgetConvoId,
94
+ isShow: isShowWidget,
102
95
  onClose: a => this.onWidgetClose(a),
103
96
  onGo: a => this.showWidget(a),
104
97
  t: this.props.t,
@@ -114,7 +107,8 @@ class Main extends _common.Com {
114
107
  if (!$widget) return;
115
108
  if ($widget.wasFormInputed) return;
116
109
  if (ev.type !== 'message_sent') return;
117
- if (!this.state.isShowWidget) return;
110
+ let isShowWidget = _index.default.get(this.props.store.matchLastview(), 'screen') === 'widget';
111
+ if (!isShowWidget) return;
118
112
  let byType = _index.default.get(ev, 'by.type');
119
113
  let byid = _index.default.get(ev, 'by.id') || '';
120
114
  if (!byid.startsWith('bb') || byType !== 'agent') return;
@@ -128,49 +122,44 @@ class Main extends _common.Com {
128
122
  checkExpand() {
129
123
  if (this.props.store.isMobile()) return;
130
124
  if (this.props.plugin._no_auto_expand) return;
131
- if (this.props.store.matchHasManuallyClosed()) return;
125
+ return;
132
126
  let convoM = this.props.store.matchConvo('*');
133
127
  let redconvo = _index.default.find(convoM, convo => !_index.default.isConvoSeen(convo, this.props.store.matchUser().id));
134
128
  if (!redconvo || !redconvo.id) return;
135
- if (!this.state.isShowWidget) this.showWidget({
129
+ let isShowWidget = _index.default.get(this.props.store.matchLastview(), 'screen') === 'widget';
130
+ if (!isShowWidget) this.showWidget({
136
131
  view: 'chat',
137
132
  cid: redconvo.id
138
133
  });
139
134
  }
140
- getCurrentView() {
141
- return {
142
- is_show_widget: this.state.isShowWidget,
143
- conversation_id: this.state.widgetConvoId,
144
- view: this.state.widgetView
145
- };
135
+ onSelectQuickReply(message) {
136
+ this.showWidget({
137
+ view: 'chat',
138
+ cid: message.conversation_id
139
+ });
140
+ this.forceUpdate(() => {
141
+ this.refWidget.current && this.refWidget.current.sendMessage(message);
142
+ });
146
143
  }
147
144
  async showWidget(p) {
148
- this.refButton.current && this.refButton.current.ClearPreview();
149
- if (!p.view) {
150
- let lastview = this.props.store.matchLastview() || {};
151
- p.view = _index.default.get(lastview, 'view');
152
- p.cid = _index.default.get(lastview, 'opts.cid');
153
- }
154
- p = p || {};
145
+ let cid = p.cid;
146
+ let newView = {
147
+ screen: 'widget',
148
+ minimized: 0,
149
+ view: p.view
150
+ };
155
151
  if (!p.view && !p.cid) {
156
152
  let convoM = this.props.store.matchConvo('*');
157
153
  let redconvo = _index.default.find(convoM, convo => !_index.default.isConvoSeen(convo, this.props.store.matchUser().id));
158
- p.cid = _index.default.get(redconvo, 'id', _index.default.get(convoM, '0.id'));
159
- p.view = 'chat';
154
+ cid = _index.default.get(redconvo, 'id', _index.default.get(convoM, '0.id'));
155
+ newView.view = 'chat';
160
156
  }
157
+ if (cid) newView.opts = {
158
+ cid
159
+ };
161
160
  if (this.props.store.isMobile()) modifyViewport();
162
- this.setState({
163
- widgetConvoId: p.cid,
164
- widgetView: p.view
165
- });
166
- this.props.store.setIsOpen(true);
161
+ this.props.store.updateLastview(newView);
167
162
  this.forceUpdate();
168
- this.setState({
169
- isShowWidget: true
170
- });
171
- this.props.store.setLastview(p.view, {
172
- cid: p.cid
173
- });
174
163
  }
175
164
  applyMessage(text) {
176
165
  this.refWidget.current && this.refWidget.current.applyMessage(text);
@@ -186,15 +175,13 @@ class Main extends _common.Com {
186
175
  }
187
176
  hideWidget() {
188
177
  restoreViewport();
189
- this.props.store.setIsOpen(false);
190
- this.setState({
191
- isShowWidget: false
178
+ this.props.store.updateLastview({
179
+ screen: 'button',
180
+ previewEvent: {}
192
181
  });
182
+ this.forceUpdate();
193
183
  }
194
184
  onWidgetClose() {
195
- this.refButton.current && this.refButton.current.ClearPreview();
196
- this.refButton.current && this.refButton.current.ClosePreview();
197
- this.props.store.setHasManuallyClosed();
198
185
  this.hideWidget();
199
186
  this.props.store.emitCloseWidgetEvent();
200
187
  }
@@ -139,10 +139,12 @@ class Store {
139
139
  let user = this.cache.match('user') || {};
140
140
  if (!user.id) return {};
141
141
  if (lastView.user_id != user.id) return {};
142
+ if (lastView.view && !lastView.screen) {
143
+ lastView.screen = 'widget';
144
+ }
142
145
  return lastView;
143
146
  });
144
147
  _defineProperty(this, "matchIsOpen", () => !!this.kv.match('isopen'));
145
- _defineProperty(this, "matchHasManuallyClosed", () => !!this.kv.match('hasManuallyClosed'));
146
148
  _defineProperty(this, "matchClosePreviewAt", () => this.kv.match('close_preview_at') || 0);
147
149
  _defineProperty(this, "checkFormToken", (accid, token) => this.api.checkFormToken(accid, token));
148
150
  _defineProperty(this, "submitForm2", (accid, token, data) => this.api.submitForm2(accid, token, data));
@@ -176,23 +178,17 @@ class Store {
176
178
  _defineProperty(this, "onConnection", (o, cb) => this.pubsub.on2(o, 'connection', cb));
177
179
  _defineProperty(this, "onResetCSS", (o, cb) => this.pubsub.on2(o, 'resetCSS', cb));
178
180
  _defineProperty(this, "onHighlight", (o, cb) => this.pubsub.on2(o, 'highlight', cb));
179
- _defineProperty(this, "setHasManuallyClosed", () => this.kv.put('hasManuallyClosed', true));
180
- _defineProperty(this, "setIsOpen", isopen => {
181
- let lastisopen = this.kv.match('isopen');
182
- this.kv.put('isopen', isopen);
183
- if (!lastisopen && isopen) {
184
- sendEventToThirdParty('SUBIZ Chat', 'Opened', 'Subiz Windows');
185
- }
186
- });
187
181
  _defineProperty(this, "closePreview", () => this.kv.put('close_preview_at', new _index.default.Date().getTime()));
188
- _defineProperty(this, "setLastview", (view, opts) => {
182
+ _defineProperty(this, "updateLastview", obj => {
189
183
  let user = this.cache.match('user') || {};
190
184
  if (!user.id) return;
191
- this.kv.put('lastview', {
192
- user_id: user.id,
193
- view,
194
- opts
195
- });
185
+ let lastview = this.matchLastview() || {};
186
+ lastview = {
187
+ ...lastview,
188
+ ...obj,
189
+ user_id: user.id
190
+ };
191
+ this.kv.put('lastview', lastview);
196
192
  });
197
193
  _defineProperty(this, "timeDiff", 0);
198
194
  this.connectionStatus = 'active';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.1.178",
3
+ "version": "4.1.179",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -13,10 +13,10 @@
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.45.1",
16
+ "core-js": "^3.46.0",
17
17
  "css-loader": "5.2.7",
18
18
  "file-loader": "^6.2.0",
19
- "less": "^4.4.1",
19
+ "less": "^4.4.2",
20
20
  "less-loader": "^12.3.0",
21
21
  "preact": "10.19.3"
22
22
  },
@@ -29,8 +29,8 @@
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.22.2",
33
- "webpack": "^5.101.3",
32
+ "puppeteer": "^24.24.0",
33
+ "webpack": "^5.102.1",
34
34
  "webpack-bundle-analyzer": "^4.10.2",
35
35
  "webpack-cli": "^6.0.1",
36
36
  "webpack-dev-server": "^5.2.2"
@@ -11,8 +11,6 @@ export default class ChatButton extends Com {
11
11
  super(props)
12
12
  this.setState({
13
13
  opacity: 0, // wait for image to load first before setting opacity to 1
14
- previewEvent: {},
15
- displayPreview: false,
16
14
  })
17
15
  this.loadGreeting()
18
16
 
@@ -39,8 +37,21 @@ export default class ChatButton extends Com {
39
37
  let message = sb.get(ev, 'data.message.text') || ''
40
38
  if (!message && isquickreplies) return
41
39
  }
40
+ let screen = sb.get(this.props.store.matchLastview(), 'screen')
41
+ let minimized = sb.get(this.props.store.matchLastview(), 'minimized')
42
+ if (screen === 'widget') return
43
+ if (minimized && Date.now() - minimized < Ms30Mins) return
42
44
 
43
- this.setState({displayPreview: true, previewEvent: ev})
45
+ if (this.props.store.isMobile()) {
46
+ // dont show greeting fro mobile anymore
47
+ // need setState previewEvent for display 1 unread in mobile
48
+ this.props.store.updateLastview({previewEvent: ev})
49
+ this.forceUpdate()
50
+ return
51
+ }
52
+
53
+ this.props.store.updateLastview({screen: 'preview', previewEvent: ev})
54
+ this.forceUpdate()
44
55
  })
45
56
  }
46
57
 
@@ -97,15 +108,6 @@ export default class ChatButton extends Com {
97
108
  )
98
109
  }
99
110
 
100
- // this method could be call from the parent
101
- ClearPreview() {
102
- this.setState({previewEvent: {}})
103
- }
104
-
105
- ClosePreview() {
106
- this.setState({displayPreview: false})
107
- }
108
-
109
111
  pickBestGreeting() {
110
112
  let convoM = this.props.store.matchConvo('*')
111
113
  let unseenConvo = sb
@@ -136,20 +138,27 @@ export default class ChatButton extends Com {
136
138
 
137
139
  // this method could be call from the parent
138
140
  ShowGreeting() {
139
- this.setState({
140
- previewEvent: this.pickBestGreeting(),
141
- displayPreview: true,
142
- })
141
+ this.props.store.updateLastview({screen: 'preview', previewEvent: this.pickBestGreeting()})
142
+ this.forceUpdate()
143
+ //this.setState({
144
+ //previewEvent: this.pickBestGreeting(),
145
+ //})
143
146
  }
144
147
 
145
148
  HideGreeting() {
146
- this.setState({previewEvent: {}})
149
+ this.props.store.updateLastview({screen: 'button', minimized: Date.now(), previewEvent: {}})
150
+ this.forceUpdate()
151
+ //this.setState({previewEvent: {}})
147
152
  }
148
153
 
149
154
  loadGreeting() {
155
+ let minimized = sb.get(this.props.store.matchLastview(), 'minimized')
156
+ let screen = sb.get(this.props.store.matchLastview(), 'screen')
157
+ console.log('loadGreeting matchLastview', this.props.store.matchLastview())
158
+ if (screen !== 'preview') return
159
+ if (minimized && Date.now() - minimized < Ms30Mins) return
150
160
  let accountSetting = this.props.plugin.chatbox
151
161
  // dont show preview if the user has previously closed it
152
- if (new sb.Date().getTime() - this.props.store.matchClosePreviewAt() < Ms30Mins) return
153
162
 
154
163
  let greeting = sb.get(accountSetting, 'greeting', {})
155
164
  let text = this.props.t.i18n(sb.get(greeting, 'greeting', {})) || greeting.message
@@ -157,11 +166,16 @@ export default class ChatButton extends Com {
157
166
  let enabled = greeting.enabled
158
167
  if (this.props.store.isMobile()) {
159
168
  enabled = enabled && !sb.get(greeting, 'mobile_disabled')
169
+ // dont show greeting fro mobile anymore
170
+ this.props.store.updateLastview({screen: 'button', previewEvent: {}})
171
+ return
160
172
  }
161
173
  if (!text || !enabled) {
162
- this.setState({previewEvent: {}})
174
+ this.props.store.updateLastview({screen: 'button', previewEvent: {}})
175
+ this.forceUpdate()
163
176
  return
164
177
  }
178
+ console.log('loadGreeting pickBestGreeting', this.pickBestGreeting())
165
179
  this.ShowGreeting()
166
180
  }
167
181
 
@@ -184,14 +198,15 @@ export default class ChatButton extends Com {
184
198
  }
185
199
 
186
200
  onPreviewClick(e) {
187
- let cid = sb.convoOfEv(this.state.previewEvent)
201
+ let previewEvent = sb.get(this.props.store.matchLastview(), 'previewEvent') || {}
202
+ let cid = sb.convoOfEv(previewEvent)
188
203
  if (!cid) {
189
204
  let convos = sb.map(this.props.store.matchConvo('*'))
190
205
  let convo = sb.find(convos, (convo) => !sb.isConvoSeen(convo, this.props.store.matchUser().id))
191
206
  if (!convo) convo = convos[0] || {}
192
207
  cid = convo.id
193
208
  }
194
- this.closePreview(e)
209
+ //this.HideGreeting()
195
210
  this.props.onClick && this.props.onClick({view: 'chat', cid})
196
211
  }
197
212
 
@@ -205,7 +220,8 @@ export default class ChatButton extends Com {
205
220
  let account = this.props.store.matchAccount()
206
221
  if (!account) return null
207
222
  let accountSetting = this.props.plugin.chatbox
208
- let byid = sb.get(this.state.previewEvent, 'by.display_id', sb.get(this.state.previewEvent, 'by.id'))
223
+ let previewEvent = sb.get(this.props.store.matchLastview(), 'previewEvent') || {}
224
+ let byid = sb.get(previewEvent, 'by.display_id', sb.get(previewEvent, 'by.id'))
209
225
  // let agent = sb.cloneDeep(this.props.store.matchAgent(byid)) || {}
210
226
  // somehow store.matchAgent(id) return undefined so we try to backup with store.matchAgent('*')[id]
211
227
  let agent = this.props.store.matchAgent(byid)
@@ -225,32 +241,85 @@ export default class ChatButton extends Com {
225
241
  })
226
242
  }
227
243
 
228
- let cls = 'widget-preview widget-preview--' + position
229
- if (!this.state.displayPreview) cls += ' widget-preview--hidden'
244
+ let tags = this.renderMessagePreview(previewEvent)
245
+ let attachments = sb.get(previewEvent, 'data.message.attachments') || []
246
+ let quickReplies = sb.find(attachments, (att) => att.type === 'quick_replies')
247
+ quickReplies = sb.get(quickReplies, 'quick_replies') || []
248
+ let imgAtt = sb.find(attachments, (att) => sb.includes(att.mimetype, 'image') && att.url)
249
+ let uri = sb.get(imgAtt, 'url') || ''
250
+ if (
251
+ uri &&
252
+ !uri.startsWith('https://') &&
253
+ !uri.startsWith('http://') &&
254
+ !uri.startsWith('content://') &&
255
+ !uri.startsWith('data:')
256
+ ) {
257
+ uri = this.props.store.FileURL + uri
258
+ }
230
259
 
231
- var tags = this.renderMessagePreview(this.state.previewEvent)
232
- if (sb.size(tags) === 0) cls += ' widget-preview--hidden'
260
+ let isShowPreview = sb.get(this.props.store.matchLastview(), 'screen') === 'preview'
233
261
 
234
262
  return (
235
- <div class={cls}>
236
- <div class='widget-preview--body'>
237
- <img
238
- alt='widget avatar'
239
- class='widget-preview--avatar'
240
- src={this.props.store.replaceFileUrl(agent.avatar_url)}
241
- onError={(ev) => this.onProfileError(ev)}
242
- onload={(_) => this.onProfileReady()}
243
- />
244
- <div class='widget-preview--content' onClick={(e) => this.onPreviewClick(e)}>
245
- <div class='widget-preview--title'>{agent.fullname}</div>
246
- <p class='widget-preview--message'>{tags}</p>
247
- </div>
263
+ <div class={`widget-preview-new-wrapper ${isShowPreview ? '' : 'hidden'}`}>
264
+ <div class={`widget-preview-x-wrapper ${position}`}>
248
265
  <div class='widget-preview--btn-close' onClick={(e) => this.closePreview(e)} />
249
266
  </div>
250
-
251
- <div class='widget-preview--action' onClick={(e) => this.onPreviewClick(e)}>
252
- <div class='widget-preview--action-text'>{this.props.t('chat_now')}</div>
267
+ <div class={`widget-preview-text-message ${position}`} onClick={(e) => this.onPreviewClick(e)}>
268
+ <div class='widget-preview-text-message--header'>
269
+ <img
270
+ alt='widget avatar'
271
+ class='widget-preview--avatar'
272
+ src={this.props.store.replaceFileUrl(agent.avatar_url)}
273
+ onError={(ev) => this.onProfileError(ev)}
274
+ onload={(_) => this.onProfileReady()}
275
+ />
276
+ <div class='widget-preview-text-message--author'>
277
+ <div class='widget-preview-text-message--author-text'>{agent.fullname}</div>
278
+ <div class='badge badge--success'>
279
+ <div class='dot dot--success mr-1' />
280
+ {'ONLINE'}
281
+ </div>
282
+ </div>
283
+ </div>
284
+ <div class='widget-preview-text-message--content'>{tags}</div>
253
285
  </div>
286
+ {uri && (
287
+ <div class={`widget-preview-image-wrapper ${position}`}>
288
+ <div class='widget-preview-image-item' onClick={() => this.onPreviewClick()}>
289
+ <img src={uri} />
290
+ </div>
291
+ </div>
292
+ )}
293
+ {sb.size(quickReplies) > 0 && (
294
+ <div class={`widget-preview-quick-reply-wrapper ${position}`}>
295
+ {sb.map(quickReplies, (reply) => {
296
+ return (
297
+ <div
298
+ class='widget-preview-quick-reply-item'
299
+ onClick={() => {
300
+ // copy chooseQuickReply function from Event.js
301
+ this.props.onSelectQuickReply({
302
+ text: reply.title,
303
+ format: 'plaintext',
304
+ conversation_id: sb.convoOfEv(previewEvent),
305
+ attachments: [
306
+ {
307
+ type: 'postback',
308
+ postback: {
309
+ event_id: previewEvent.id,
310
+ payload: reply.payload,
311
+ },
312
+ },
313
+ ],
314
+ })
315
+ }}
316
+ >
317
+ {reply.title}
318
+ </div>
319
+ )
320
+ })}
321
+ </div>
322
+ )}
254
323
  </div>
255
324
  )
256
325
  }
@@ -440,9 +509,7 @@ export default class ChatButton extends Com {
440
509
  e.preventDefault()
441
510
  e.stopPropagation()
442
511
  }
443
- this.setState({displayPreview: false})
444
- this.props.store.closePreview()
445
- this.forceUpdate()
512
+ this.HideGreeting()
446
513
  }
447
514
  }
448
515
 
@@ -63,7 +63,6 @@ export default class Widget extends Com {
63
63
  if (prevProp.cid !== this.props.cid) {
64
64
  this.closeOrder()
65
65
  this.setState({clickedJoin: false, quickReplyMessage: {}})
66
- // this.props.store.setLastview('chat', { cid: this.props.cid })
67
66
  sb.setTimeout(() => {
68
67
  // focus Events then focus MessageInput
69
68
  // if there is any pending form, MessageInput will be disabled
@@ -45,9 +45,6 @@ export default class Main extends Com {
45
45
 
46
46
  this.setState({
47
47
  cssReady: false,
48
- isShowWidget: false,
49
- widgetView: '',
50
- widgetConvoId: '',
51
48
  })
52
49
 
53
50
  this.checkExpand()
@@ -62,16 +59,6 @@ export default class Main extends Com {
62
59
  })
63
60
  this.refreshCSS()
64
61
  this.setState({cssReady: true})
65
-
66
- if (this.props.store.matchIsOpen() && this.props.store.matchLastview().view) {
67
- if (this.props.store.isMobile()) return // dont auto expand widget on mobile
68
- this.showWidget(
69
- Object.assign({}, this.props.store.matchLastview().opts, {view: this.props.store.matchLastview().view}),
70
- )
71
- } else {
72
- restoreViewport()
73
- if (!this.state.isShowWidget) this.hideWidget()
74
- }
75
62
  }
76
63
 
77
64
  render() {
@@ -88,6 +75,11 @@ export default class Main extends Com {
88
75
  return null
89
76
  }
90
77
  if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_main = sb.Date.now()
78
+ let lastview = this.props.store.matchLastview() || {}
79
+ let isShowWidget = lastview.screen === 'widget'
80
+ let widgetView = lastview.view
81
+ let widgetConvoId = sb.get(lastview, 'opts.cid')
82
+
91
83
  return (
92
84
  <div class={this.props.store.isMobile() ? 'mobile' : 'desktop'}>
93
85
  <ChatButton
@@ -95,16 +87,17 @@ export default class Main extends Com {
95
87
  store={this.props.store}
96
88
  key='chat_button'
97
89
  ref={this.refButton}
98
- isShow={!this.state.isShowWidget}
90
+ isShow={!isShowWidget}
99
91
  onClick={(e) => this.showWidget(e)}
92
+ onSelectQuickReply={(msg) => this.onSelectQuickReply(msg)}
100
93
  t={this.props.t}
101
94
  />
102
95
  <Widget
103
96
  plugin={this.props.plugin}
104
97
  store={this.props.store}
105
- view={this.state.widgetView}
106
- cid={this.state.widgetConvoId}
107
- isShow={this.state.isShowWidget}
98
+ view={widgetView}
99
+ cid={widgetConvoId}
100
+ isShow={isShowWidget}
108
101
  onClose={(a) => this.onWidgetClose(a)}
109
102
  onGo={(a) => this.showWidget(a)}
110
103
  t={this.props.t}
@@ -124,7 +117,8 @@ export default class Main extends Com {
124
117
  if (!$widget) return
125
118
  if ($widget.wasFormInputed) return
126
119
  if (ev.type !== 'message_sent') return
127
- if (!this.state.isShowWidget) return
120
+ let isShowWidget = sb.get(this.props.store.matchLastview(), 'screen') === 'widget'
121
+ if (!isShowWidget) return
128
122
 
129
123
  let byType = sb.get(ev, 'by.type')
130
124
  let byid = sb.get(ev, 'by.id') || ''
@@ -140,50 +134,39 @@ export default class Main extends Com {
140
134
  if (this.props.store.isMobile()) return
141
135
 
142
136
  if (this.props.plugin._no_auto_expand) return
143
- // dont try to expand widget if user has already closed it
144
- if (this.props.store.matchHasManuallyClosed()) return
137
+ return
145
138
 
146
139
  let convoM = this.props.store.matchConvo('*')
147
140
  let redconvo = sb.find(convoM, (convo) => !sb.isConvoSeen(convo, this.props.store.matchUser().id))
148
141
  if (!redconvo || !redconvo.id) return
149
- if (!this.state.isShowWidget) this.showWidget({view: 'chat', cid: redconvo.id})
142
+ let isShowWidget = sb.get(this.props.store.matchLastview(), 'screen') === 'widget'
143
+ if (!isShowWidget) this.showWidget({view: 'chat', cid: redconvo.id})
150
144
  }
151
145
 
152
- getCurrentView() {
153
- return {
154
- is_show_widget: this.state.isShowWidget,
155
- conversation_id: this.state.widgetConvoId,
156
- view: this.state.widgetView,
157
- }
146
+ onSelectQuickReply(message) {
147
+ this.showWidget({view: 'chat', cid: message.conversation_id})
148
+ this.forceUpdate(() => {
149
+ this.refWidget.current && this.refWidget.current.sendMessage(message)
150
+ })
158
151
  }
159
152
 
160
153
  async showWidget(p) {
161
- this.refButton.current && this.refButton.current.ClearPreview()
162
- if (!p.view) {
163
- let lastview = this.props.store.matchLastview() || {}
164
- p.view = sb.get(lastview, 'view')
165
- p.cid = sb.get(lastview, 'opts.cid')
166
- }
167
- p = p || {}
154
+ //this.refButton.current && this.refButton.current.ClearPreview()
168
155
 
156
+ let cid = p.cid
157
+ let newView = {screen: 'widget', minimized: 0, view: p.view}
169
158
  if (!p.view && !p.cid) {
170
159
  let convoM = this.props.store.matchConvo('*')
171
160
  let redconvo = sb.find(convoM, (convo) => !sb.isConvoSeen(convo, this.props.store.matchUser().id))
172
161
  // try to show first convo
173
- p.cid = sb.get(redconvo, 'id', sb.get(convoM, '0.id'))
174
- p.view = 'chat'
162
+ cid = sb.get(redconvo, 'id', sb.get(convoM, '0.id'))
163
+ newView.view = 'chat'
175
164
  }
165
+ if (cid) newView.opts = {cid}
176
166
 
177
167
  if (this.props.store.isMobile()) modifyViewport()
178
- this.setState({widgetConvoId: p.cid, widgetView: p.view})
179
- this.props.store.setIsOpen(true)
180
-
181
- // lazy load widget
182
- // if (!Widget) Widget = (await loadWidget()).default
183
-
168
+ this.props.store.updateLastview(newView)
184
169
  this.forceUpdate()
185
- this.setState({isShowWidget: true})
186
- this.props.store.setLastview(p.view, {cid: p.cid})
187
170
  }
188
171
 
189
172
  applyMessage(text) {
@@ -204,17 +187,12 @@ export default class Main extends Com {
204
187
 
205
188
  hideWidget() {
206
189
  restoreViewport()
207
- this.props.store.setIsOpen(false)
208
- this.setState({isShowWidget: false})
190
+ this.props.store.updateLastview({screen: 'button', previewEvent: {}})
191
+ this.forceUpdate()
209
192
  }
210
193
 
211
194
  onWidgetClose() {
212
- this.refButton.current && this.refButton.current.ClearPreview()
213
- // need call closePreview if not this will display default agent avatar and account name
214
- this.refButton.current && this.refButton.current.ClosePreview()
215
- this.props.store.setHasManuallyClosed()
216
195
  this.hideWidget()
217
-
218
196
  // emit event close widget
219
197
  this.props.store.emitCloseWidgetEvent()
220
198
  }
@@ -466,13 +466,21 @@ export default class Store {
466
466
 
467
467
  // make sure last view of correct user
468
468
  let user = this.cache.match('user') || {}
469
- if (!user.id) return {}
470
- if (lastView.user_id != user.id) return {}
469
+ if (!user.id) return { screen: 'preview' }
470
+ if (lastView.user_id != user.id) return { screen: 'preview' }
471
+
472
+ if (!lastView.screen) {
473
+ lastView.screen = 'preview' // mode preview allow load default greeting
474
+
475
+ // backward compatible for user currently view chat
476
+ if (lastView.view === 'list' || lastView.view === 'chat') {
477
+ lastView.screen = 'widget'
478
+ }
479
+ }
471
480
  return lastView
472
481
  }
473
482
 
474
483
  matchIsOpen = () => !!this.kv.match('isopen') // is widget currently open
475
- matchHasManuallyClosed = () => !!this.kv.match('hasManuallyClosed') // true if user has ever closed the widget
476
484
  matchClosePreviewAt = () => this.kv.match('close_preview_at') || 0
477
485
 
478
486
  checkFormToken = (accid, token) => this.api.checkFormToken(accid, token)
@@ -1212,19 +1220,18 @@ export default class Store {
1212
1220
  onResetCSS = (o, cb) => this.pubsub.on2(o, 'resetCSS', cb)
1213
1221
  onHighlight = (o, cb) => this.pubsub.on2(o, 'highlight', cb)
1214
1222
 
1215
- setHasManuallyClosed = () => this.kv.put('hasManuallyClosed', true)
1216
- setIsOpen = (isopen) => {
1217
- let lastisopen = this.kv.match('isopen')
1218
- this.kv.put('isopen', isopen)
1219
- if (!lastisopen && isopen) {
1220
- sendEventToThirdParty('SUBIZ Chat', 'Opened', 'Subiz Windows')
1221
- }
1222
- }
1223
1223
  closePreview = () => this.kv.put('close_preview_at', new sb.Date().getTime())
1224
- setLastview = (view, opts) => {
1224
+ updateLastview = (obj) => {
1225
1225
  let user = this.cache.match('user') || {}
1226
1226
  if (!user.id) return // store not init
1227
- this.kv.put('lastview', {user_id: user.id, view, opts})
1227
+ let lastview = this.matchLastview() || {}
1228
+ lastview = {
1229
+ ...lastview,
1230
+ ...obj,
1231
+ user_id: user.id,
1232
+ }
1233
+
1234
+ this.kv.put('lastview', lastview)
1228
1235
  }
1229
1236
 
1230
1237
  matchLastPluginRun(pluginid) {