@subiz/widget-v4 4.0.95 → 4.0.97

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.
@@ -205,7 +205,7 @@ textarea.form-control:focus, select.form-control:focus {
205
205
  padding-right: 20px ;
206
206
  }
207
207
  select {
208
- background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00LjQxMDU4IDYuOTExMDdDNC43MzYwMiA2LjU4NTYzIDUuMjYzNjYgNi41ODU2MyA1LjU4OTA5IDYuOTExMDdMOS45OTk4NCAxMS4zMjE4TDE0LjQxMDYgNi45MTEwN0MxNC43MzYgNi41ODU2MyAxNS4yNjM3IDYuNTg1NjMgMTUuNTg5MSA2LjkxMTA3QzE1LjkxNDUgNy4yMzY1MSAxNS45MTQ1IDcuNzY0MTQgMTUuNTg5MSA4LjA4OTU4TDEwLjU4OTEgMTMuMDg5NkMxMC4yNjM3IDEzLjQxNSA5LjczNjAyIDEzLjQxNSA5LjQxMDU4IDEzLjA4OTZMNC40MTA1OCA4LjA4OTU4QzQuMDg1MTQgNy43NjQxNCA0LjA4NTE0IDcuMjM2NTEgNC40MTA1OCA2LjkxMTA3WiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+") no-repeat right;
208
+ /*background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00LjQxMDU4IDYuOTExMDdDNC43MzYwMiA2LjU4NTYzIDUuMjYzNjYgNi41ODU2MyA1LjU4OTA5IDYuOTExMDdMOS45OTk4NCAxMS4zMjE4TDE0LjQxMDYgNi45MTEwN0MxNC43MzYgNi41ODU2MyAxNS4yNjM3IDYuNTg1NjMgMTUuNTg5MSA2LjkxMTA3QzE1LjkxNDUgNy4yMzY1MSAxNS45MTQ1IDcuNzY0MTQgMTUuNTg5MSA4LjA4OTU4TDEwLjU4OTEgMTMuMDg5NkMxMC4yNjM3IDEzLjQxNSA5LjczNjAyIDEzLjQxNSA5LjQxMDU4IDEzLjA4OTZMNC40MTA1OCA4LjA4OTU4QzQuMDg1MTQgNy43NjQxNCA0LjA4NTE0IDcuMjM2NTEgNC40MTA1OCA2LjkxMTA3WiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+") no-repeat right;*/
209
209
  }
210
210
  }
211
211
 
@@ -330,6 +330,94 @@ class Widget extends _common.Com {
330
330
  formData
331
331
  });
332
332
  }
333
+ renderCheckboxField(field) {
334
+ let items = _index.default.get(field, 'def.items', []);
335
+ let values = [];
336
+ let value = field.value || '';
337
+ if (value) values = value.split(',');
338
+ return (0, _preact.h)("div", null, _index.default.map(items, item => {
339
+ return (0, _preact.h)("div", {
340
+ class: "checkbox"
341
+ }, (0, _preact.h)("input", {
342
+ type: "checkbox",
343
+ id: 'form-checkbox' + encode(field.key) + item.value,
344
+ onChange: e => this.toggleCheckboxField(field.key, item.value),
345
+ checked: values.includes(item.value)
346
+ }), (0, _preact.h)("label", {
347
+ class: "checkbox--label",
348
+ for: 'form-checkbox' + encode(field.key) + item.value
349
+ }, this.props.t.i18n(item.i18n_label) || item.label || item.value), (0, _preact.h)("div", {
350
+ class: "checkbox--check"
351
+ }, (0, _preact.h)("div", null)));
352
+ }));
353
+ }
354
+ toggleCheckboxField(key, v) {
355
+ let formData = _index.default.cloneDeep(this.state.formData);
356
+ let field = _index.default.find(formData, (f, fkkey) => f.key === key);
357
+ if (!field) return;
358
+ let value = field.value || '';
359
+ let values = [];
360
+ if (value) values = value.split(',');
361
+ if (values.includes(v)) {
362
+ values = _index.default.filter(values, value => value !== v);
363
+ } else {
364
+ values.push(v);
365
+ }
366
+ field.value = values.join(',');
367
+ this.setState({
368
+ formData
369
+ });
370
+ }
371
+ renderRadioField(field) {
372
+ let items = _index.default.get(field, 'def.items', []);
373
+ let value = field.value || '';
374
+ return (0, _preact.h)("div", null, _index.default.map(items, item => {
375
+ return (0, _preact.h)("div", {
376
+ class: "radio"
377
+ }, (0, _preact.h)("input", {
378
+ type: "radio",
379
+ name: 'form-radio' + encode(field.key),
380
+ id: 'form-radio' + encode(field.key) + item.value,
381
+ onChange: e => this.toggleRadioField(field.key, item.value),
382
+ checked: value === item.value
383
+ }), (0, _preact.h)("label", {
384
+ class: "radio--label",
385
+ for: 'form-radio' + encode(field.key) + item.value
386
+ }, this.props.t.i18n(item.i18n_label) || item.label || item.value), (0, _preact.h)("div", {
387
+ class: "radio--check"
388
+ }, (0, _preact.h)("div", null)));
389
+ }));
390
+ }
391
+ toggleRadioField(key, v) {
392
+ let formData = _index.default.cloneDeep(this.state.formData);
393
+ let field = _index.default.find(formData, (f, fkkey) => f.key === key);
394
+ if (!field) return;
395
+ field.value = v;
396
+ this.setState({
397
+ formData
398
+ });
399
+ }
400
+ selectDropdownField(key, v) {
401
+ let formData = _index.default.cloneDeep(this.state.formData);
402
+ let field = _index.default.find(formData, (f, fkkey) => f.key === key);
403
+ if (!field) return;
404
+ field.value = v;
405
+ this.setState({
406
+ formData
407
+ });
408
+ }
409
+ renderDropdownField(field) {
410
+ let items = _index.default.get(field, 'def.items', []);
411
+ return (0, _preact.h)("select", {
412
+ class: "form-control",
413
+ value: field.value,
414
+ onChange: e => this.selectDropdownField(field.key, e.target.value)
415
+ }, (0, _preact.h)("option", {
416
+ value: ""
417
+ }, "-- ", this.props.t('no_select'), " --"), _index.default.map(items, item => (0, _preact.h)("option", {
418
+ value: item.value
419
+ }, this.props.t.i18n(item.i18n_label) || item.label || item.value)));
420
+ }
333
421
  renderFields() {
334
422
  if (_index.default.size(this.state.formData) === 0) return null;
335
423
  let $fields = _index.default.map(this.state.formData, (field, key) => {
@@ -348,6 +436,23 @@ class Widget extends _common.Com {
348
436
  row: "4",
349
437
  class: "form-control"
350
438
  });
439
+ } else if (_index.default.get(field, 'def.select') === 'checkbox') {
440
+ $field = this.renderCheckboxField(field);
441
+ } else if (_index.default.get(field, 'def.select') === 'radio') {
442
+ $field = this.renderRadioField(field);
443
+ } else if (_index.default.get(field, 'def.select') === 'dropdown') {
444
+ $field = this.renderDropdownField(field);
445
+ } else if (_index.default.get(field, 'def.multiple_line')) {
446
+ $field = (0, _preact.h)("textarea", {
447
+ class: "form-control",
448
+ ref: ref => {
449
+ this['form_' + encode(key)] = ref;
450
+ },
451
+ value: field.value || '',
452
+ onInput: e => this.onInputChange(key, e.target.value),
453
+ row: "4",
454
+ class: "form-control"
455
+ });
351
456
  } else {
352
457
  $field = (0, _preact.h)("input", {
353
458
  value: field.value || '',
@@ -680,6 +785,13 @@ class Widget extends _common.Com {
680
785
  let convo = this.props.store.matchConvo(this.props.cid) || {};
681
786
  if (convo.state === 'ended') return true;
682
787
  if (convo.state && convo.state !== 'pending') delete formData.group;
788
+
789
+ let isAllFiledEmpty = true;
790
+ _index.default.each(formData, field => {
791
+ if (field.value) isAllFiledEmpty = false;
792
+ });
793
+ if (isAllFiledEmpty) return true;
794
+
683
795
  return !!_index.default.find(formData, field => field.is_required && (field.value == null || field.value === ''));
684
796
  }
685
797
  renderConvoSummary(convo) {
@@ -42,7 +42,7 @@ class _default {
42
42
  _defineProperty(this, "getLocaleContent", locale => this.apireq.get(`/accounts/${this.accid}/locales/${locale}`));
43
43
  _defineProperty(this, "sendEvent", data => this.apireq.post(`/users/${this.uid}/events`, data));
44
44
  _defineProperty(this, "sendTyping", cid => this.apireq.post(`/conversations/${cid}/typing`));
45
- _defineProperty(this, "sendPresence", (device, is_focused, productid, source_id, sku) => this.sendEvent({
45
+ _defineProperty(this, "sendLostFocus", (device, productid, source_id, sku) => this.sendEvent({
46
46
  by: {
47
47
  id: this.uid,
48
48
  device: device
@@ -54,7 +54,7 @@ class _default {
54
54
  viewing_product_source_id: source_id,
55
55
  viewing_product_id: productid,
56
56
  pinged: _index.default.Date.now(),
57
- is_focused
57
+ is_focused: false
58
58
  }
59
59
  }
60
60
  }));
@@ -117,7 +117,12 @@ class Store {
117
117
  await this.readyGate.entry();
118
118
  if (!_index.default.get(this.kv.match('user'), 'id')) return;
119
119
  if (_index.default.isUserBanned(this.kv.match('user'))) return;
120
- return this.api.sendPresence(this.device, this.browserVisibility === 'visible' && document.hasFocus(), this._currentViewingProduct.id, this._currentViewingProduct.source_id, this._currentViewingProduct.sku);
120
+ let hasfocus = this.browserVisibility === 'visible' && document.hasFocus();
121
+ if (!hasfocus) {
122
+ this.api.sendLostFocus(this.device, this._currentViewingProduct.id, this._currentViewingProduct.source_id, this._currentViewingProduct.sku);
123
+ return;
124
+ }
125
+ this.api.sendContentView(this.device, location.href, document.title, this._currentViewingProduct.id, this._currentViewingProduct.source_id, this._currentViewingProduct.sku);
121
126
  }, 1000,
122
127
  {
123
128
  leading: false
@@ -528,11 +533,9 @@ class Store {
528
533
  }
529
534
 
530
535
  async track() {
531
- let user = this.kv.match('user') || {};
532
536
  let out = await this.api.sendContentView(this.device, location.href, document.title, this._currentViewingProduct.id, this._currentViewingProduct.source_id, this._currentViewingProduct.sku);
533
537
  let traffic_source = _index.default.get(out, 'body.by.device.source', '');
534
538
  if (traffic_source) this.kv.put('traffic_source', traffic_source);
535
- this.sendPresence();
536
539
  }
537
540
  async onWs(ev) {
538
541
  if (!ev || !ev.type) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.0.95",
3
+ "version": "4.0.97",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -277,6 +277,117 @@ export default class Widget extends Com {
277
277
  this.setState({formData})
278
278
  }
279
279
 
280
+ renderCheckboxField(field) {
281
+ let items = sb.get(field, 'def.items', [])
282
+ let values = []
283
+ let value = field.value || ''
284
+ if (value) values = value.split(',')
285
+
286
+ return (
287
+ <div>
288
+ {sb.map(items, (item) => {
289
+ return (
290
+ <div class='checkbox'>
291
+ <input
292
+ type='checkbox'
293
+ id={'form-checkbox' + encode(field.key) + item.value}
294
+ onChange={(e) => this.toggleCheckboxField(field.key, item.value)}
295
+ checked={values.includes(item.value)}
296
+ />
297
+ <label class='checkbox--label' for={'form-checkbox' + encode(field.key) + item.value}>
298
+ {this.props.t.i18n(item.i18n_label) || item.label || item.value}
299
+ </label>
300
+ <div class='checkbox--check'>
301
+ <div></div>
302
+ </div>
303
+ </div>
304
+ )
305
+ })}
306
+ </div>
307
+ )
308
+ }
309
+
310
+ toggleCheckboxField(key, v) {
311
+ let formData = sb.cloneDeep(this.state.formData)
312
+ let field = sb.find(formData, (f, fkkey) => f.key === key)
313
+ if (!field) return
314
+
315
+ let value = field.value || ''
316
+ let values = []
317
+ if (value) values = value.split(',')
318
+ if (values.includes(v)) {
319
+ values = sb.filter(values, (value) => value !== v)
320
+ } else {
321
+ values.push(v)
322
+ }
323
+
324
+ field.value = values.join(',')
325
+ this.setState({formData})
326
+ }
327
+
328
+ renderRadioField(field) {
329
+ let items = sb.get(field, 'def.items', [])
330
+ let value = field.value || ''
331
+
332
+ return (
333
+ <div>
334
+ {sb.map(items, (item) => {
335
+ return (
336
+ <div class='radio'>
337
+ <input
338
+ type='radio'
339
+ name={'form-radio' + encode(field.key)}
340
+ id={'form-radio' + encode(field.key) + item.value}
341
+ onChange={(e) => this.toggleRadioField(field.key, item.value)}
342
+ checked={value === item.value}
343
+ />
344
+ <label class='radio--label' for={'form-radio' + encode(field.key) + item.value}>
345
+ {this.props.t.i18n(item.i18n_label) || item.label || item.value}
346
+ </label>
347
+ <div class='radio--check'>
348
+ <div></div>
349
+ </div>
350
+ </div>
351
+ )
352
+ })}
353
+ </div>
354
+ )
355
+ }
356
+
357
+ toggleRadioField(key, v) {
358
+ let formData = sb.cloneDeep(this.state.formData)
359
+ let field = sb.find(formData, (f, fkkey) => f.key === key)
360
+ if (!field) return
361
+
362
+ field.value = v
363
+ this.setState({formData})
364
+ }
365
+
366
+ selectDropdownField(key, v) {
367
+ let formData = sb.cloneDeep(this.state.formData)
368
+ let field = sb.find(formData, (f, fkkey) => f.key === key)
369
+ if (!field) return
370
+
371
+ field.value = v
372
+ this.setState({formData})
373
+ }
374
+
375
+ renderDropdownField(field) {
376
+ let items = sb.get(field, 'def.items', [])
377
+ return (
378
+ <select
379
+ class='form-control'
380
+ value={field.value}
381
+ onChange={(e) => this.selectDropdownField(field.key, e.target.value)}
382
+ >
383
+ <option value=''>-- {this.props.t('no_select')} --</option>
384
+ {sb.map(items, (item) => (
385
+ <option value={item.value}>{this.props.t.i18n(item.i18n_label) || item.label || item.value}</option>
386
+ ))}
387
+ </select>
388
+ )
389
+ }
390
+
280
391
  renderFields() {
281
392
  if (sb.size(this.state.formData) === 0) return null
282
393
 
@@ -300,6 +411,25 @@ export default class Widget extends Com {
300
411
  class='form-control'
301
412
  ></textarea>
302
413
  )
414
+ } else if (sb.get(field, 'def.select') === 'checkbox') {
415
+ $field = this.renderCheckboxField(field)
416
+ } else if (sb.get(field, 'def.select') === 'radio') {
417
+ $field = this.renderRadioField(field)
418
+ } else if (sb.get(field, 'def.select') === 'dropdown') {
419
+ $field = this.renderDropdownField(field)
420
+ } else if (sb.get(field, 'def.multiple_line')) {
421
+ $field = (
422
+ <textarea
423
+ class='form-control'
424
+ ref={(ref) => {
425
+ this['form_' + encode(key)] = ref
426
+ }}
427
+ value={field.value || ''}
428
+ onInput={(e) => this.onInputChange(key, e.target.value)}
429
+ row='4'
430
+ class='form-control'
431
+ ></textarea>
432
+ )
303
433
  } else {
304
434
  $field = (
305
435
  <input
@@ -717,6 +847,14 @@ export default class Widget extends Com {
717
847
  // allow button "Tiep tuc chat" display when sent quick_replies msg
718
848
  //if (this.userHasSent(this.props.cid)) return false
719
849
  if (convo.state && convo.state !== 'pending') delete formData.group
850
+
851
+ // Make sure to show form in the first time
852
+ let isAllFiledEmpty = true
853
+ sb.each(formData, (field) => {
854
+ if (field.value) isAllFiledEmpty = false
855
+ })
856
+ if (isAllFiledEmpty && !this.userHasSent(this.props.cid)) return true
857
+
720
858
  return !!sb.find(formData, (field) => field.is_required && (field.value == null || field.value === ''))
721
859
  }
722
860
 
package/src/store/api.js CHANGED
@@ -179,7 +179,7 @@ export default class {
179
179
  sendEvent = (data) => this.apireq.post(`/users/${this.uid}/events`, data)
180
180
  sendTyping = (cid) => this.apireq.post(`/conversations/${cid}/typing`)
181
181
 
182
- sendPresence = (device, is_focused, productid, source_id, sku) =>
182
+ sendLostFocus = (device, productid, source_id, sku) =>
183
183
  this.sendEvent({
184
184
  by: {id: this.uid, device: device},
185
185
  type: 'presence_updated',
@@ -189,7 +189,7 @@ export default class {
189
189
  viewing_product_source_id: source_id,
190
190
  viewing_product_id: productid,
191
191
  pinged: sb.Date.now(),
192
- is_focused,
192
+ is_focused: false,
193
193
  },
194
194
  },
195
195
  })
@@ -322,9 +322,22 @@ export default class Store {
322
322
  await this.readyGate.entry()
323
323
  if (!sb.get(this.kv.match('user'), 'id')) return // store not init
324
324
  if (sb.isUserBanned(this.kv.match('user'))) return
325
- return this.api.sendPresence(
325
+
326
+ let hasfocus = this.browserVisibility === 'visible' && document.hasFocus()
327
+ if (!hasfocus) {
328
+ this.api.sendLostFocus(
329
+ this.device,
330
+ this._currentViewingProduct.id,
331
+ this._currentViewingProduct.source_id,
332
+ this._currentViewingProduct.sku,
333
+ )
334
+ return
335
+ }
336
+
337
+ this.api.sendContentView(
326
338
  this.device,
327
- this.browserVisibility === 'visible' && document.hasFocus(),
339
+ location.href,
340
+ document.title,
328
341
  this._currentViewingProduct.id,
329
342
  this._currentViewingProduct.source_id,
330
343
  this._currentViewingProduct.sku,
@@ -617,7 +630,6 @@ export default class Store {
617
630
 
618
631
  // send the content view, the first presence and record domain
619
632
  async track() {
620
- let user = this.kv.match('user') || {}
621
633
  let out = await this.api.sendContentView(
622
634
  this.device,
623
635
  location.href,
@@ -629,8 +641,6 @@ export default class Store {
629
641
 
630
642
  let traffic_source = sb.get(out, 'body.by.device.source', '')
631
643
  if (traffic_source) this.kv.put('traffic_source', traffic_source)
632
-
633
- this.sendPresence()
634
644
  }
635
645
 
636
646
  matchTrafficSource = () => this.kv.match('traffic_source')