@miso.ai/client-sdk-ui 1.10.0-beta.0 → 1.10.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/actor/autocomplete.js +157 -0
- package/dist/cjs/actor/data.js +4 -12
- package/dist/cjs/actor/fields.js +8 -0
- package/dist/cjs/actor/index.js +7 -0
- package/dist/cjs/actor/tracker.js +46 -70
- package/dist/cjs/actor/trackers.js +5 -1
- package/dist/cjs/actor/utils.js +16 -2
- package/dist/cjs/actor/view.js +14 -3
- package/dist/cjs/actor/views.js +9 -1
- package/dist/cjs/affiliation/index.js +126 -0
- package/dist/cjs/combo/ask/options.js +1 -1
- package/dist/cjs/combo/base.js +1 -1
- package/dist/cjs/constants.js +6 -3
- package/dist/cjs/defaults/ask/controls.js +1 -1
- package/dist/cjs/defaults/ask/phrases.js +1 -1
- package/dist/cjs/defaults/ask/templates.js +35 -4
- package/dist/cjs/element/role/index.js +7 -0
- package/dist/cjs/element/role/miso-affiliation.js +20 -0
- package/dist/cjs/layout/affiliation/affiliation.js +330 -0
- package/dist/cjs/layout/banner/banner.js +1 -1
- package/dist/cjs/layout/container/container.js +1 -1
- package/dist/cjs/layout/error/error.js +1 -1
- package/dist/cjs/layout/index.js +7 -0
- package/dist/cjs/layout/input/option-list.js +35 -50
- package/dist/cjs/layout/input/search-box.js +227 -129
- package/dist/cjs/layout/list/cards.js +1 -1
- package/dist/cjs/layout/list/carousel.js +5 -3
- package/dist/cjs/layout/list/collection.js +44 -49
- package/dist/cjs/layout/list/list.js +1 -1
- package/dist/cjs/layout/radio/feedback.js +1 -1
- package/dist/cjs/layout/radio/radio.js +1 -1
- package/dist/cjs/layout/raf.js +1 -1
- package/dist/cjs/layout/template.js +1 -1
- package/dist/cjs/layout/templates.js +172 -66
- package/dist/cjs/layout/text/text.js +1 -1
- package/dist/cjs/layout/text/typewriter/index.js +3 -3
- package/dist/cjs/layout/trackable.js +124 -0
- package/dist/cjs/plugin.js +1 -1
- package/dist/cjs/source/index.js +20 -8
- package/dist/cjs/util/bindings.js +58 -110
- package/dist/cjs/util/states.js +31 -24
- package/dist/cjs/util/trackers.js +75 -0
- package/dist/cjs/util/viewables.js +55 -0
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/ask.js +60 -19
- package/dist/cjs/workflow/asks.js +1 -1
- package/dist/cjs/workflow/base.js +6 -7
- package/dist/cjs/workflow/explore.js +5 -2
- package/dist/cjs/workflow/options.js +60 -14
- package/dist/cjs/workflow/recommendation.js +2 -0
- package/dist/cjs/workflow/search.js +35 -7
- package/package.json +2 -2
- package/src/actor/autocomplete.js +131 -0
- package/src/actor/data.js +3 -10
- package/src/actor/fields.js +8 -0
- package/src/actor/index.js +1 -0
- package/src/actor/tracker.js +39 -62
- package/src/actor/trackers.js +3 -1
- package/src/actor/utils.js +14 -2
- package/src/actor/view.js +12 -1
- package/src/actor/views.js +11 -0
- package/src/affiliation/index.js +108 -0
- package/src/constants.js +10 -1
- package/src/defaults/ask/phrases.js +1 -1
- package/src/defaults/ask/templates.js +34 -2
- package/src/element/role/index.js +1 -0
- package/src/element/role/miso-affiliation.js +16 -0
- package/src/layout/affiliation/affiliation.js +295 -0
- package/src/layout/index.js +1 -0
- package/src/layout/input/option-list.js +35 -43
- package/src/layout/input/search-box.js +192 -95
- package/src/layout/list/carousel.js +4 -2
- package/src/layout/list/collection.js +42 -43
- package/src/layout/templates.js +127 -46
- package/src/layout/text/typewriter/index.js +2 -2
- package/src/layout/trackable.js +106 -0
- package/src/source/index.js +14 -1
- package/src/util/bindings.js +47 -103
- package/src/util/states.js +26 -20
- package/src/util/trackers.js +69 -0
- package/src/util/viewables.js +47 -0
- package/src/version.js +1 -1
- package/src/workflow/ask.js +37 -3
- package/src/workflow/base.js +2 -3
- package/src/workflow/explore.js +1 -1
- package/src/workflow/options.js +54 -13
- package/src/workflow/recommendation.js +1 -0
- package/src/workflow/search.js +33 -1
- package/dist/cjs/util/tracker.js +0 -348
- package/src/util/tracker.js +0 -318
package/src/layout/templates.js
CHANGED
|
@@ -16,77 +16,124 @@ export function unimplemented(name) {
|
|
|
16
16
|
|
|
17
17
|
export function product(layout, state, data, meta) {
|
|
18
18
|
const { templates } = layout;
|
|
19
|
-
const [openTag, closeTag] =
|
|
19
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
20
20
|
return [
|
|
21
21
|
openTag,
|
|
22
|
-
(templates.imageBlock ||
|
|
23
|
-
(templates.infoBlock || productInfoBlock)(layout, data, meta),
|
|
24
|
-
(templates.indexBlock || renderIndexBlock)(layout, data, meta),
|
|
22
|
+
(templates.imageBlock || imageBlock)(layout, data, meta),
|
|
23
|
+
(templates.infoBlock || templates.productInfoBlock || productInfoBlock)(layout, data, meta),
|
|
25
24
|
closeTag,
|
|
26
25
|
].join('');
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
export function question(layout, state, data) {
|
|
30
|
-
const [openTag, closeTag] =
|
|
29
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
31
30
|
return `${openTag}${data.value || data.text || data}${closeTag}`;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export function article(layout, state, data, meta) {
|
|
35
34
|
const { templates } = layout;
|
|
36
|
-
const [openTag, closeTag] =
|
|
35
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
37
36
|
return [
|
|
38
37
|
openTag,
|
|
39
|
-
(templates.imageBlock ||
|
|
40
|
-
(templates.infoBlock || articleInfoBlock)(layout, data, meta),
|
|
41
|
-
(templates.indexBlock ||
|
|
38
|
+
(templates.imageBlock || imageBlock)(layout, data, meta),
|
|
39
|
+
(templates.infoBlock || templates.articleInfoBlock || articleInfoBlock)(layout, data, meta),
|
|
40
|
+
(templates.indexBlock || indexBlock)(layout, data, meta),
|
|
42
41
|
closeTag,
|
|
43
42
|
].join('');
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
export function affiliation(layout, state, data, meta) {
|
|
46
|
+
const { templates } = layout;
|
|
47
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
48
|
+
return [
|
|
49
|
+
openTag,
|
|
50
|
+
(templates.infoBlock || templates.affiliationInfoBlock || affiliationInfoBlock)(layout, data, meta),
|
|
51
|
+
(templates.imageBlock || imageBlock)(layout, data, meta),
|
|
52
|
+
(templates.brandBlock || brandBlock)(layout, data, meta),
|
|
53
|
+
closeTag,
|
|
54
|
+
].join('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function affiliationInfoBlock(layout, data, meta) {
|
|
58
|
+
const { className, templates } = layout;
|
|
59
|
+
return `<div class="${className}__item-info-container">${[
|
|
60
|
+
(templates.titleBlock || titleBlock)(layout, data, meta),
|
|
61
|
+
(templates.descriptionBlock || descriptionBlock)(layout, data, meta),
|
|
62
|
+
(templates.priceBlock || priceBlock)(layout, data, meta),
|
|
63
|
+
(templates.ctaBlock || ctaBlock)(layout, data, meta),
|
|
64
|
+
].join('')}</div>`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function productInfoBlock(layout, data, meta) {
|
|
68
|
+
const { className, templates } = layout;
|
|
69
|
+
return `<div class="${className}__item-info-container">${[
|
|
70
|
+
(templates.titleBlock || titleBlock)(layout, data, meta),
|
|
71
|
+
(templates.descriptionBlock || descriptionBlock)(layout, data, meta),
|
|
72
|
+
(templates.priceBlock || priceBlock)(layout, data, meta),
|
|
73
|
+
].join('')}</div>`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function articleInfoBlock(layout, data, meta) {
|
|
77
|
+
const { className, templates } = layout;
|
|
78
|
+
return `<div class="${className}__item-info-container">${[
|
|
79
|
+
(templates.titleBlock || titleBlock)(layout, data, meta),
|
|
80
|
+
(templates.dateBlock || dateBlock)(layout, data, meta),
|
|
81
|
+
(templates.descriptionBlock || descriptionBlock)(layout, data, meta),
|
|
82
|
+
(templates.ctaBlock || ctaBlock)(layout, data, meta),
|
|
83
|
+
].join('')}</div>`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function titleBlock({ className }, { title }) {
|
|
87
|
+
return title ? `<div class="${className}__item-title">${escapeHtml(title)}</div>` : '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function brandBlock({ className }, { brand, brand_logo }) {
|
|
91
|
+
if (!brand && !brand_logo) {
|
|
92
|
+
return '';
|
|
58
93
|
}
|
|
59
|
-
|
|
94
|
+
const content = brand_logo ? `<img class="${className}__item-brand-logo" src="${brand_logo}"${brand ? ` alt="${brand}"` : ''}>` : `<div class="${className}__item-brand">${brand}</div>`;
|
|
95
|
+
return `<div class="${className}__item-brand-container">${content}</div>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function descriptionBlock({ className }, { snippet, description }) {
|
|
99
|
+
return snippet ? `<div class="${className}__item-snippet">${snippet}</div>` : description ? `<div class="${className}__item-desc">${escapeHtml(description)}</div>` : '';
|
|
60
100
|
}
|
|
61
101
|
|
|
62
|
-
function
|
|
102
|
+
export function dateBlock({ className, templates }, { created_at, updated_at, published_at }) {
|
|
63
103
|
const date = published_at || created_at || updated_at;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
104
|
+
return date ? `<div class="${className}__item-date">${formatDate(date, templates.date)}</div>` : '';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function priceBlock(layout, data) {
|
|
108
|
+
const { className, templates } = layout;
|
|
109
|
+
const { sale_price, original_price, currency = 'USD' } = data;
|
|
110
|
+
const price = sale_price || original_price;
|
|
111
|
+
if (!price) {
|
|
112
|
+
return '';
|
|
67
113
|
}
|
|
68
|
-
|
|
69
|
-
|
|
114
|
+
const has_price_difference = original_price !== undefined && sale_price !== undefined && sale_price < original_price;
|
|
115
|
+
const discount_rate_percent = data.discount_rate_percent || (has_price_difference ? Math.floor((1 - sale_price / original_price) * 100) : undefined);
|
|
116
|
+
|
|
117
|
+
let content = '';
|
|
118
|
+
// original price
|
|
119
|
+
if (has_price_difference) {
|
|
120
|
+
content += `<span class="${className}__item-original-price miso-price" data-currency="${currency}">${original_price}</span><br>`;
|
|
70
121
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
122
|
+
// current price
|
|
123
|
+
content += `<span class="${className}__item-price miso-price" data-currency="${currency}">${price}</span>`;
|
|
124
|
+
// discount rate
|
|
125
|
+
if (discount_rate_percent) {
|
|
126
|
+
const text = (templates.discountRateText || discountRateText)(layout, { ...data, discount_rate_percent });
|
|
127
|
+
content += ` <span class="${className}__item-discount-rate">${text}</span>`;
|
|
75
128
|
}
|
|
76
|
-
return `<div class="${className}__item-
|
|
129
|
+
return `<div class="${className}__item-price-container">${content}</div>`;
|
|
77
130
|
}
|
|
78
131
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const { link = {} } = options;
|
|
82
|
-
const { target = '_blank', rel = 'noopener' } = link; // TODO: other properties
|
|
83
|
-
const tag = url ? 'a' : 'div';
|
|
84
|
-
const urlAttrs = url ? `href="${url}" target="${target}" rel="${rel}"` : '';
|
|
85
|
-
const productAttrs = product_id ? `${ATTR_DATA_MISO_PRODUCT_ID}="${product_id}"` : '';
|
|
86
|
-
return [`<${tag} class="${className}__item-body" data-role="item" ${productAttrs} ${urlAttrs}>`, `</${tag}>`];
|
|
132
|
+
export function discountRateText(layout, { discount_rate_percent }) {
|
|
133
|
+
return `(${discount_rate_percent}% off)`;
|
|
87
134
|
}
|
|
88
135
|
|
|
89
|
-
function
|
|
136
|
+
export function imageBlock({ className }, { cover_image }) {
|
|
90
137
|
if (!cover_image) {
|
|
91
138
|
return '';
|
|
92
139
|
}
|
|
@@ -94,14 +141,42 @@ function renderImageBlock({ className }, { cover_image }) {
|
|
|
94
141
|
return `<div class="${className}__item-cover-image-container">${img}</div>`;
|
|
95
142
|
}
|
|
96
143
|
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
144
|
+
export function indexBlock({ className }, data, { index }) {
|
|
145
|
+
return `<div class="${className}__item-index-container"><span class="${className}__item-index miso-citation-index" data-index="${index + 1}"></span></div>`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function ctaBlock(layout, data, meta) {
|
|
149
|
+
const { className, templates } = layout;
|
|
150
|
+
let _cta = templates.cta || cta;
|
|
151
|
+
if (typeof _cta === 'function') {
|
|
152
|
+
_cta = _cta(layout, data, meta);
|
|
153
|
+
}
|
|
154
|
+
return _cta ? `<div class="${className}__item-cta-container">${_cta}</div>` : '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function cta(layout, data, meta) {
|
|
158
|
+
const { className, templates } = layout;
|
|
159
|
+
let text = templates.ctaText;
|
|
160
|
+
if (typeof text === 'function') {
|
|
161
|
+
text = text(layout, data, meta);
|
|
162
|
+
}
|
|
163
|
+
return text ? `<div class="${className}__item-cta">${text}</div>` : '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// helpers //
|
|
167
|
+
function tagPair({ className, options = {} }, { product_id, url }, { classSuffix = 'item-body', role = 'item' } = {}) {
|
|
168
|
+
const { link = {} } = options;
|
|
169
|
+
const { target = '_blank', rel = 'noopener' } = link; // TODO: other properties
|
|
170
|
+
const tag = url ? 'a' : 'div';
|
|
171
|
+
const urlAttrs = url ? `href="${url}" target="${target}" rel="${rel}"` : '';
|
|
172
|
+
const productAttrs = product_id ? `${ATTR_DATA_MISO_PRODUCT_ID}="${product_id}"` : '';
|
|
173
|
+
const roleAttrs = role ? `data-role="${role}"` : '';
|
|
174
|
+
return [`<${tag} class="${className}__${classSuffix}" ${roleAttrs} ${productAttrs} ${urlAttrs}>`, `</${tag}>`];
|
|
100
175
|
}
|
|
101
176
|
|
|
102
177
|
const DEFAULT_DATE_OPTIONS = Object.freeze({ locale: 'en-US', year: 'numeric', month: 'short', day: 'numeric' });
|
|
103
178
|
|
|
104
|
-
function
|
|
179
|
+
function formatDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
105
180
|
switch (typeof fn) {
|
|
106
181
|
case 'function':
|
|
107
182
|
return fn(date);
|
|
@@ -113,3 +188,9 @@ function renderDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
|
113
188
|
throw new Error(`Invalid date format: ${fn}`);
|
|
114
189
|
}
|
|
115
190
|
}
|
|
191
|
+
|
|
192
|
+
export const helpers = Object.freeze({
|
|
193
|
+
tagPair,
|
|
194
|
+
formatDate,
|
|
195
|
+
escapeHtml,
|
|
196
|
+
});
|
|
@@ -81,7 +81,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
81
81
|
onDebug,
|
|
82
82
|
});
|
|
83
83
|
// capture citation link click if necessary
|
|
84
|
-
this._unsubscribes.push(this._view.proxyElement.on('click', (e) => this.
|
|
84
|
+
this._unsubscribes.push(this._view.proxyElement.on('click', (e) => this._onClick(e)));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
_getSource(index) {
|
|
@@ -148,7 +148,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// event //
|
|
151
|
-
|
|
151
|
+
_onClick({ target }) {
|
|
152
152
|
const element = target.closest(`[data-role="citation-link"]`);
|
|
153
153
|
if (!element) {
|
|
154
154
|
return;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { EVENT_TYPE } from '../constants.js';
|
|
2
|
+
import Bindings from '../util/bindings.js';
|
|
3
|
+
import Viewables from '../util/viewables.js';
|
|
4
|
+
import { validateClick } from '../util/trackers.js';
|
|
5
|
+
|
|
6
|
+
export function makeTrackable(prototype) {
|
|
7
|
+
_mixin(prototype, TrackableMixin.prototype);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function _mixin(target, source) {
|
|
11
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
12
|
+
for (const key in descriptors) {
|
|
13
|
+
if (key === 'constructor') {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const { value } = descriptors[key];
|
|
17
|
+
if (target[key] === undefined && typeof value === 'function') {
|
|
18
|
+
target[key] = value;
|
|
19
|
+
}
|
|
20
|
+
// TODO: getter, setter
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TrackableMixin {
|
|
25
|
+
|
|
26
|
+
_initTrackable() {
|
|
27
|
+
this._bindings = new Bindings();
|
|
28
|
+
this._viewables = new Viewables();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_syncBindings(element, state) {
|
|
32
|
+
const values = this._getItems(state) || [];
|
|
33
|
+
const keys = values.map(value => this._getItemKey(value));
|
|
34
|
+
const elements = this._getItemElements(element);
|
|
35
|
+
|
|
36
|
+
const { bounds, unbounds } = this._bindings.update(keys, values, elements);
|
|
37
|
+
this._onBindingsUpdate(bounds, unbounds);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_clearBindings() {
|
|
41
|
+
const { bounds, unbounds } = this._bindings.clear();
|
|
42
|
+
this._onBindingsUpdate(bounds, unbounds);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_getItemKey(value) {
|
|
46
|
+
// TODO: ad-hoc!
|
|
47
|
+
return value.product_id || value.text || value.id || value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_getItems(state) {
|
|
51
|
+
throw new Error(`Not implemented.`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_getItemElements(element) {
|
|
55
|
+
throw new Error(`Not implemented.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_onBindingsUpdate(bounds, unbounds) {
|
|
59
|
+
this._trackImpressions(bounds);
|
|
60
|
+
this._untrackViewables(unbounds);
|
|
61
|
+
this._trackViewables(bounds);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_trackImpressions(entries) {
|
|
65
|
+
if (entries.length === 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const { impression: options } = this._view.trackerOptions || {};
|
|
69
|
+
if (!options) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setTimeout(() => this._view._track(EVENT_TYPE.IMPRESSION, entries.map(({ value }) => value)), 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_untrackViewables(entries) {
|
|
76
|
+
for (const { element } of entries) {
|
|
77
|
+
this._viewables.untrack(element);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_trackViewables(entries) {
|
|
82
|
+
if (entries.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const { viewable: options } = this._view.trackerOptions || {};
|
|
86
|
+
if (!options) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const { value, element } of entries) {
|
|
90
|
+
(async () => (await this._viewables.track(element, options)) && this._view._track(EVENT_TYPE.VIEWABLE, [value]))();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_trackClick(event, binding) {
|
|
95
|
+
const { click: options } = this._view.trackerOptions || {};
|
|
96
|
+
if (validateClick(options, event, binding)) {
|
|
97
|
+
this._view._track(EVENT_TYPE.CLICK, [binding.value]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_destroyTrackable() {
|
|
102
|
+
const { unbounds } = this._bindings.clear();
|
|
103
|
+
this._untrackViewables(unbounds);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
package/src/source/index.js
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { API } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
//export { default as api } from './api.js';
|
|
4
|
+
|
|
5
|
+
export function api(client) {
|
|
6
|
+
// TODO: send uuid & unit id
|
|
7
|
+
return async ({ group, name, payload, options }) => {
|
|
8
|
+
if (group === API.GROUP.ASK && name === API.NAME.QUESTIONS) {
|
|
9
|
+
return client.api.ask.questions(payload, options);
|
|
10
|
+
}
|
|
11
|
+
// because name is in snake case
|
|
12
|
+
return client.api[group]._run(name, payload, options);
|
|
13
|
+
};
|
|
14
|
+
}
|
package/src/util/bindings.js
CHANGED
|
@@ -1,122 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
function tuple(keys, values, elements) {
|
|
2
|
+
const k2b = new Map();
|
|
3
|
+
const e2b = new Map();
|
|
4
|
+
const entries = [];
|
|
5
|
+
for (let index = 0; index < keys.length; index++) {
|
|
6
|
+
const key = keys[index];
|
|
7
|
+
const element = elements[index];
|
|
8
|
+
const value = values[index];
|
|
9
|
+
const entry = Object.freeze({ index, key, value, element });
|
|
10
|
+
k2b.set(key, entry);
|
|
11
|
+
e2b.set(element, entry);
|
|
12
|
+
entries.push(entry);
|
|
13
|
+
}
|
|
14
|
+
return { k2b, e2b, entries };
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
this._bindings = new Map();
|
|
20
|
-
this._e2b = new WeakMap();
|
|
17
|
+
function diff(from, to) {
|
|
18
|
+
const entries = [];
|
|
19
|
+
for (const entry of to.entries) {
|
|
20
|
+
const f = from.e2b.get(entry.element);
|
|
21
|
+
if (!f || f.key !== entry.key) {
|
|
22
|
+
entries.push(entry);
|
|
23
|
+
}
|
|
21
24
|
}
|
|
25
|
+
return entries;
|
|
26
|
+
}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
return [...this._bindings.values()];
|
|
25
|
-
}
|
|
28
|
+
export default class Bindings {
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!this._root) {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
if (!isElement(ref)) {
|
|
35
|
-
return this._bindings.get(ref);
|
|
36
|
-
}
|
|
37
|
-
return findInAncestors(ref, element => this._e2b.get(element) || undefined, { root: this._root });
|
|
30
|
+
constructor() {
|
|
31
|
+
this._k2b = new Map();
|
|
32
|
+
this._e2b = new Map();
|
|
33
|
+
this._entries = [];
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return this.unbindAll();
|
|
43
|
-
}
|
|
44
|
-
this._root = rootElement;
|
|
45
|
-
const unbounds = this._purge();
|
|
46
|
-
const bounds = [];
|
|
47
|
-
// warn for conflict productIds
|
|
48
|
-
// TODO
|
|
49
|
-
for (const pair of this._list(rootElement)) {
|
|
50
|
-
const [binding, ...ubs] = this._bind(pair);
|
|
51
|
-
if (binding) {
|
|
52
|
-
bounds.push(binding);
|
|
53
|
-
}
|
|
54
|
-
for (const u of ubs) {
|
|
55
|
-
unbounds.push(u);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return { bounds, unbounds };
|
|
36
|
+
get entries() {
|
|
37
|
+
return [...this._entries];
|
|
59
38
|
}
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this._bindings.clear();
|
|
64
|
-
this._root = undefined;
|
|
65
|
-
return { bounds: [], unbounds };
|
|
40
|
+
get(element) {
|
|
41
|
+
return this._e2b.get(element) || undefined;
|
|
66
42
|
}
|
|
67
43
|
|
|
44
|
+
update(keys, values, elements) {
|
|
45
|
+
const newTuple = tuple(keys, values, elements);
|
|
46
|
+
const oldTuple = { k2b: this._k2b, e2b: this._e2b, entries: this._entries };
|
|
68
47
|
|
|
48
|
+
const bounds = diff(oldTuple, newTuple);
|
|
49
|
+
const unbounds = diff(newTuple, oldTuple);
|
|
69
50
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
throw new Error(`Key or value is required.`);
|
|
74
|
-
}
|
|
75
|
-
if (!element) {
|
|
76
|
-
throw new Error(`Element is required.`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// check if binding already exist
|
|
80
|
-
const oldBinding = this._bindings.get(key);
|
|
81
|
-
if (oldBinding && oldBinding.element === element) {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
const unbound0 = this._unbind(this.get(key));
|
|
85
|
-
const unbound1 = this._unbind(this.get(element));
|
|
86
|
-
|
|
87
|
-
const binding = Object.freeze({ key, value, element });
|
|
88
|
-
this._bindings.set(key, binding);
|
|
89
|
-
this._e2b.set(element, binding);
|
|
90
|
-
|
|
91
|
-
const results = [binding];
|
|
92
|
-
unbound0 && results.push(unbound0);
|
|
93
|
-
unbound1 && results.push(unbound1);
|
|
94
|
-
|
|
95
|
-
return results;
|
|
96
|
-
}
|
|
51
|
+
this._k2b = newTuple.k2b;
|
|
52
|
+
this._e2b = newTuple.e2b;
|
|
53
|
+
this._entries = newTuple.entries;
|
|
97
54
|
|
|
98
|
-
|
|
99
|
-
const purged = [];
|
|
100
|
-
for (const binding of this._bindings.values()) {
|
|
101
|
-
const { element } = binding;
|
|
102
|
-
if (isInDocument(element)) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
purged.push(this._unbind(binding));
|
|
106
|
-
}
|
|
107
|
-
return purged;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
_unbind(binding) {
|
|
111
|
-
if (binding) {
|
|
112
|
-
this._bindings.delete(binding.key);
|
|
113
|
-
this._e2b.delete(binding.element);
|
|
114
|
-
}
|
|
115
|
-
return binding;
|
|
55
|
+
return { bounds, unbounds };
|
|
116
56
|
}
|
|
117
57
|
|
|
118
|
-
|
|
119
|
-
this.
|
|
58
|
+
clear() {
|
|
59
|
+
const unbounds = this._entries;
|
|
60
|
+
this._k2b = new Map();
|
|
61
|
+
this._e2b = new Map();
|
|
62
|
+
this._entries = [];
|
|
63
|
+
return { bounds: [], unbounds };
|
|
120
64
|
}
|
|
121
65
|
|
|
122
66
|
}
|
package/src/util/states.js
CHANGED
|
@@ -4,6 +4,7 @@ import { EVENT_TYPE, TRACKING_STATUS, validateEventType, validateTrackingStatus
|
|
|
4
4
|
const { IMPRESSION, VIEWABLE, CLICK } = EVENT_TYPE;
|
|
5
5
|
const { UNTRACKED, TRIGGERED } = TRACKING_STATUS;
|
|
6
6
|
|
|
7
|
+
/*
|
|
7
8
|
const STATES = {
|
|
8
9
|
NEW: Object.freeze({
|
|
9
10
|
[IMPRESSION]: UNTRACKED,
|
|
@@ -15,51 +16,56 @@ const STATES = {
|
|
|
15
16
|
[CLICK]: UNTRACKED,
|
|
16
17
|
}),
|
|
17
18
|
};
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const UNTRACKED_STATES = Object.freeze({
|
|
22
|
+
[IMPRESSION]: UNTRACKED,
|
|
23
|
+
[VIEWABLE]: UNTRACKED,
|
|
24
|
+
[CLICK]: UNTRACKED,
|
|
25
|
+
});
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
28
|
* Tracking states for a session to keep track of the tracking status of each product.
|
|
21
|
-
* For each entry there are status of impression, viewable, and click events.
|
|
22
|
-
* The actual click tracking status depends on the presence of root element, which is not respected in this object.
|
|
29
|
+
* For each entry there are status of impression, viewable, and click events.
|
|
23
30
|
*/
|
|
24
31
|
export default class States {
|
|
25
32
|
|
|
26
33
|
constructor(sessionId) {
|
|
27
34
|
this.sessionId = sessionId;
|
|
28
|
-
this._states = new Map(); //
|
|
35
|
+
this._states = new Map(); // item key -> {impression, viewable, click}
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
getFullState(
|
|
38
|
+
getFullState(item) {
|
|
32
39
|
// when we receive a bind event, impression is trigger, making an entry here
|
|
33
40
|
// so if the state is not found, the product is not present yet => all untracked
|
|
34
|
-
const state = this._states.get(
|
|
35
|
-
return state ? { ...state } :
|
|
41
|
+
const state = this._states.get(this._getKey(item));
|
|
42
|
+
return state ? { ...state } : UNTRACKED_STATES;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
get(
|
|
39
|
-
const state = this._states.get(
|
|
45
|
+
get(item, type) {
|
|
46
|
+
const state = this._states.get(this._getKey(item));
|
|
40
47
|
return state && state[type] || UNTRACKED;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
set(
|
|
50
|
+
set(items, type, status) {
|
|
44
51
|
validateEventType(type);
|
|
45
52
|
validateTrackingStatus(status);
|
|
46
|
-
for (const
|
|
47
|
-
this._setOne(
|
|
53
|
+
for (const item of asArray(items)) {
|
|
54
|
+
this._setOne(item, type, status);
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
computeIfAbsent(this._states, productId, () => ({ ...STATES.NEW }))[type] = status;
|
|
58
|
+
untriggered(items, type) {
|
|
59
|
+
return items.filter(item => this.get(item, type) !== TRIGGERED);
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
_getKey(item) {
|
|
63
|
+
// TODO: ad-hoc! should move away to higher level
|
|
64
|
+
return item.product_id || item.text || item.id || item;
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
_setOne(item, type, status) {
|
|
68
|
+
computeIfAbsent(this._states, this._getKey(item), () => ({ ...UNTRACKED_STATES }))[type] = status;
|
|
69
|
+
}
|
|
61
70
|
|
|
62
|
-
function getKey(value) {
|
|
63
|
-
// TODO: ad-hoc!
|
|
64
|
-
return value.product_id || value.text || value;
|
|
65
71
|
}
|