@miso.ai/client-sdk-ui 1.10.0-beta.7 → 1.10.0-beta.9
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/tracker.js +6 -1
- package/dist/cjs/actor/utils.js +5 -1
- package/dist/cjs/affiliation/index.js +30 -4
- package/dist/cjs/layout/affiliation/affiliation.js +169 -13
- package/dist/cjs/layout/affiliation/viewable.js +65 -0
- package/dist/cjs/layout/list/carousel.js +4 -2
- package/dist/cjs/layout/list/collection.js +8 -5
- package/dist/cjs/util/states.js +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/ask.js +4 -0
- package/package.json +2 -2
- package/src/actor/tracker.js +7 -1
- package/src/actor/utils.js +5 -1
- package/src/affiliation/index.js +28 -4
- package/src/layout/affiliation/affiliation.js +157 -7
- package/src/layout/affiliation/viewable.js +61 -0
- package/src/layout/list/carousel.js +4 -2
- package/src/layout/list/collection.js +9 -5
- package/src/util/states.js +1 -1
- package/src/version.js +1 -1
- package/src/workflow/ask.js +4 -1
|
@@ -39,7 +39,9 @@ class Tracker {
|
|
|
39
39
|
sessionId: () => this._getSessionId(),
|
|
40
40
|
active: active || (() => this._isViewReady())
|
|
41
41
|
}, options));
|
|
42
|
-
this._unsubscribes = [this._tracker.on('*', data => this._handleEvent(data)),
|
|
42
|
+
this._unsubscribes = [this._tracker.on('*', data => this._handleEvent(data)),
|
|
43
|
+
// TODO: view actor has another click event that does not comply to this data format
|
|
44
|
+
...[_constants.EVENT_TYPE.VIEWABLE].map(type => this._view.on(type, values => this._handleViewActorEvent(type, values))), hub.on(fields.view(role), () => this.refresh())];
|
|
43
45
|
this.refresh();
|
|
44
46
|
}
|
|
45
47
|
config(options) {
|
|
@@ -115,6 +117,9 @@ class Tracker {
|
|
|
115
117
|
getState(productId) {
|
|
116
118
|
return this._tracker.getState(productId);
|
|
117
119
|
}
|
|
120
|
+
_handleViewActorEvent(type, values) {
|
|
121
|
+
this._tracker._trigger(type, values);
|
|
122
|
+
}
|
|
118
123
|
_handleEvent(data) {
|
|
119
124
|
const property = this._role === 'results' ? 'products' : this._role; // TODO: ad-hoc, see #83
|
|
120
125
|
const misoId = this._getMisoId();
|
package/dist/cjs/actor/utils.js
CHANGED
|
@@ -35,7 +35,9 @@ function toInteraction(_ref, _ref2) {
|
|
|
35
35
|
const question_source = request && request._meta && request._meta.question_source;
|
|
36
36
|
const isProductType = isProduct(property);
|
|
37
37
|
const product_ids = isProductType ? values.map(v => v.product_id).filter(v => v) : [];
|
|
38
|
-
const items = isProductType ? undefined : values.map(v => v.text || v);
|
|
38
|
+
const items = isProductType ? undefined : values.map(v => v.text || v.id || v);
|
|
39
|
+
const channel = property === _constants.ROLE.AFFILIATION ? values[0] && values[0].channel : undefined;
|
|
40
|
+
const positions = property === _constants.ROLE.AFFILIATION ? values.map(v => v.position) : undefined;
|
|
39
41
|
const payload = (0, _commons.trimObj)({
|
|
40
42
|
type: event === 'viewable' ? 'viewable_impression' : event,
|
|
41
43
|
product_ids,
|
|
@@ -44,7 +46,9 @@ function toInteraction(_ref, _ref2) {
|
|
|
44
46
|
custom_context: (0, _commons.trimObj)({
|
|
45
47
|
api_ts,
|
|
46
48
|
property,
|
|
49
|
+
channel,
|
|
47
50
|
items,
|
|
51
|
+
positions,
|
|
48
52
|
trigger: manual ? 'manual' : 'auto',
|
|
49
53
|
question_source
|
|
50
54
|
})
|
|
@@ -27,18 +27,45 @@ function processValue(value) {
|
|
|
27
27
|
sovrn_aff
|
|
28
28
|
} = value,
|
|
29
29
|
rest = _objectWithoutPropertiesLoose(value, _excluded);
|
|
30
|
-
|
|
30
|
+
value = Object.assign({}, rest, {
|
|
31
31
|
affiliation: processSovrnData(sovrn_aff)
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
return value;
|
|
34
|
+
return postProcessValue(value);
|
|
35
|
+
}
|
|
36
|
+
function postProcessValue(value) {
|
|
37
|
+
const {
|
|
38
|
+
affiliation
|
|
39
|
+
} = value;
|
|
40
|
+
if (!affiliation) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
let {
|
|
44
|
+
channel,
|
|
45
|
+
products
|
|
46
|
+
} = affiliation;
|
|
47
|
+
if (!products) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
products = products.map(product => Object.assign({}, product));
|
|
51
|
+
let i = 0;
|
|
52
|
+
for (const product of products) {
|
|
53
|
+
if (channel) {
|
|
54
|
+
product.channel = channel;
|
|
55
|
+
}
|
|
56
|
+
product.position = i++;
|
|
57
|
+
}
|
|
58
|
+
return Object.assign({}, value, {
|
|
59
|
+
affiliation: Object.assign({}, affiliation, {
|
|
60
|
+
products
|
|
61
|
+
})
|
|
62
|
+
});
|
|
35
63
|
}
|
|
36
64
|
function processSovrnData(_ref) {
|
|
37
65
|
let {
|
|
38
66
|
products
|
|
39
67
|
} = _ref,
|
|
40
68
|
sovrn_aff = _objectWithoutPropertiesLoose(_ref, _excluded2);
|
|
41
|
-
products = products.slice(0, 1);
|
|
42
69
|
return Object.assign({
|
|
43
70
|
channel: 'sovrn'
|
|
44
71
|
}, sovrn_aff, {
|
|
@@ -64,7 +91,6 @@ function processSovrnProduct(_ref2) {
|
|
|
64
91
|
const brand_logo = merchant && merchant.logo;
|
|
65
92
|
return (0, _commons.trimObj)({
|
|
66
93
|
id: `sovrn-${id}`,
|
|
67
|
-
channel: 'sovrn',
|
|
68
94
|
title,
|
|
69
95
|
description,
|
|
70
96
|
cover_image,
|
|
@@ -7,6 +7,7 @@ exports.default = void 0;
|
|
|
7
7
|
var _constants = require("../../constants.js");
|
|
8
8
|
var _collection = _interopRequireDefault(require("../list/collection.js"));
|
|
9
9
|
var _templates = require("../templates.js");
|
|
10
|
+
var _viewable = _interopRequireDefault(require("./viewable.js"));
|
|
10
11
|
const _excluded = ["className", "templates"];
|
|
11
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
13
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; }
|
|
@@ -30,28 +31,26 @@ function ready(layout, state) {
|
|
|
30
31
|
const {
|
|
31
32
|
templates
|
|
32
33
|
} = layout;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (products && products.length > 0) {
|
|
37
|
-
return templates.body(layout, state);
|
|
34
|
+
const items = layout._getItems(state);
|
|
35
|
+
if (items && items.length > 0) {
|
|
36
|
+
return templates.control(layout, state, 'previous') + templates.body(layout, state) + templates.control(layout, state, 'next');
|
|
38
37
|
} else {
|
|
39
38
|
return templates.empty(layout, state);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
41
|
+
function control(layout, state, direction) {
|
|
42
|
+
const {
|
|
43
|
+
className
|
|
44
|
+
} = layout;
|
|
45
|
+
return `<div class="${className}__control-${direction}" data-role="${direction}"></div>`;
|
|
46
|
+
}
|
|
42
47
|
function body(layout, state) {
|
|
43
48
|
const {
|
|
44
49
|
className,
|
|
45
50
|
templates
|
|
46
51
|
} = layout;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
products
|
|
50
|
-
} = state.value;
|
|
51
|
-
products = products.map(product => Object.assign({
|
|
52
|
-
channel
|
|
53
|
-
}, product));
|
|
54
|
-
return `<div class="${className}__body">${templates.header(layout, state)}${templates.list(layout, state, products)}</div>`;
|
|
52
|
+
const items = layout._getItems(state);
|
|
53
|
+
return `<div class="${className}__body">${templates.list(layout, state, items)}</div>`;
|
|
55
54
|
}
|
|
56
55
|
function header(layout, state) {
|
|
57
56
|
const {
|
|
@@ -69,11 +68,47 @@ function header(layout, state) {
|
|
|
69
68
|
}
|
|
70
69
|
return `<div class="${className}__header"><div class="${className}__header-tab">${headerText}</div></div>`;
|
|
71
70
|
}
|
|
71
|
+
function item(layout, state, value, index) {
|
|
72
|
+
const {
|
|
73
|
+
className,
|
|
74
|
+
templates,
|
|
75
|
+
options
|
|
76
|
+
} = layout;
|
|
77
|
+
const {
|
|
78
|
+
itemType
|
|
79
|
+
} = options;
|
|
80
|
+
const header = templates.itemHeader(layout, state, value, {
|
|
81
|
+
index
|
|
82
|
+
});
|
|
83
|
+
const body = templates[itemType](layout, state, value, {
|
|
84
|
+
index
|
|
85
|
+
});
|
|
86
|
+
return `<li class="${className}__item">${header}${body}</li>`;
|
|
87
|
+
}
|
|
88
|
+
function itemHeader(layout, state, value, meta) {
|
|
89
|
+
const {
|
|
90
|
+
className,
|
|
91
|
+
templates
|
|
92
|
+
} = layout;
|
|
93
|
+
let {
|
|
94
|
+
headerText
|
|
95
|
+
} = templates;
|
|
96
|
+
if (typeof headerText === 'function') {
|
|
97
|
+
headerText = headerText(layout, state, value, meta);
|
|
98
|
+
}
|
|
99
|
+
if (!headerText) {
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
return `<div class="${className}__item-header"><div class="${className}__item-header-tab">${headerText}</div></div>`;
|
|
103
|
+
}
|
|
72
104
|
const DEFAULT_TEMPLATES = Object.freeze({
|
|
73
105
|
root,
|
|
74
106
|
[_constants.STATUS.READY]: ready,
|
|
107
|
+
control,
|
|
75
108
|
body,
|
|
76
109
|
header,
|
|
110
|
+
item,
|
|
111
|
+
itemHeader,
|
|
77
112
|
affiliation: _templates.affiliation,
|
|
78
113
|
ctaText: 'View',
|
|
79
114
|
headerText: 'Featured'
|
|
@@ -104,6 +139,127 @@ class AffiliationLayout extends _collection.default {
|
|
|
104
139
|
itemType: 'affiliation',
|
|
105
140
|
templates: Object.assign({}, DEFAULT_TEMPLATES, templates)
|
|
106
141
|
}, options));
|
|
142
|
+
this._itemCount = undefined;
|
|
143
|
+
this._itemIndex = 0;
|
|
144
|
+
this._displayedIndex = undefined;
|
|
145
|
+
this._viewable = new _viewable.default(this._onViewable.bind(this));
|
|
146
|
+
}
|
|
147
|
+
get itemIndex() {
|
|
148
|
+
return this._itemIndex;
|
|
149
|
+
}
|
|
150
|
+
next() {
|
|
151
|
+
if (this._itemCount === undefined) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.go(this._itemIndex + 1);
|
|
155
|
+
}
|
|
156
|
+
previous() {
|
|
157
|
+
if (this._itemCount === undefined) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.go(this._itemIndex - 1);
|
|
161
|
+
}
|
|
162
|
+
go(index) {
|
|
163
|
+
if (this._itemCount === undefined) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (index < 0) {
|
|
167
|
+
index = this._itemCount - 1;
|
|
168
|
+
} else if (index >= this._itemCount) {
|
|
169
|
+
index = 0;
|
|
170
|
+
}
|
|
171
|
+
this._itemIndex = index;
|
|
172
|
+
this._syncItemIndex();
|
|
173
|
+
}
|
|
174
|
+
_getItems(state) {
|
|
175
|
+
const {
|
|
176
|
+
products
|
|
177
|
+
} = state.value || {};
|
|
178
|
+
return products;
|
|
179
|
+
}
|
|
180
|
+
_render(element, states, controls) {
|
|
181
|
+
super._render(element, states, controls);
|
|
182
|
+
this._syncItemCount(element, states);
|
|
183
|
+
this._syncItemIndex();
|
|
184
|
+
}
|
|
185
|
+
_syncElement(element) {
|
|
186
|
+
const oldElement = this._element;
|
|
187
|
+
super._syncElement(element);
|
|
188
|
+
this._syncViewableElement(oldElement, element);
|
|
189
|
+
}
|
|
190
|
+
_syncViewableElement(oldElement, newElement) {
|
|
191
|
+
if (oldElement === newElement) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
oldElement && this._viewable.unobserve(oldElement);
|
|
195
|
+
newElement && this._viewable.observe(newElement);
|
|
196
|
+
}
|
|
197
|
+
_syncItemCount(element, states) {
|
|
198
|
+
element = element.firstElementChild;
|
|
199
|
+
if (!element) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const {
|
|
203
|
+
state
|
|
204
|
+
} = states;
|
|
205
|
+
const items = this._getItems(state);
|
|
206
|
+
const itemCount = this._itemCount = items ? items.length : undefined;
|
|
207
|
+
if (itemCount === undefined) {
|
|
208
|
+
element.removeAttribute('data-item-count');
|
|
209
|
+
} else {
|
|
210
|
+
element.setAttribute('data-item-count', itemCount);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
_syncItemIndex() {
|
|
214
|
+
// TODO: raf
|
|
215
|
+
const {
|
|
216
|
+
element
|
|
217
|
+
} = this._view;
|
|
218
|
+
const listElement = element && this._getListElement(element);
|
|
219
|
+
if (!listElement) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const index = this._itemIndex;
|
|
223
|
+
if (index === this._displayedIndex) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this._displayedIndex = index;
|
|
227
|
+
listElement.style.left = `${-index * 100}%`;
|
|
228
|
+
this._viewable.display(index);
|
|
229
|
+
this._view._events.emit('display', {
|
|
230
|
+
index
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
_onViewable(index) {
|
|
234
|
+
const item = this._getRenderedItemData(index);
|
|
235
|
+
if (!item) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this._view._events.emit(_constants.EVENT_TYPE.VIEWABLE, [item]);
|
|
239
|
+
}
|
|
240
|
+
_getRenderedItemData(index) {
|
|
241
|
+
const {
|
|
242
|
+
value
|
|
243
|
+
} = this._rendered.get(this._element) || {};
|
|
244
|
+
if (!value) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
const {
|
|
248
|
+
products = []
|
|
249
|
+
} = value;
|
|
250
|
+
return products[index];
|
|
251
|
+
}
|
|
252
|
+
_onClick(event) {
|
|
253
|
+
super._onClick(event);
|
|
254
|
+
const element = event.target;
|
|
255
|
+
if (element.closest(`[data-role="previous"]`)) {
|
|
256
|
+
this.previous();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (element.closest(`[data-role="next"]`)) {
|
|
260
|
+
this.next();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
107
263
|
}
|
|
108
264
|
}
|
|
109
265
|
exports.default = AffiliationLayout;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _commons = require("@miso.ai/commons");
|
|
8
|
+
class CarouselItemViewableTracker {
|
|
9
|
+
constructor(callback) {
|
|
10
|
+
let {
|
|
11
|
+
area = 0.5,
|
|
12
|
+
duration = 1000
|
|
13
|
+
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
14
|
+
this._callback = callback;
|
|
15
|
+
this._intersection = new IntersectionObserver(this._handleIntersection.bind(this), {
|
|
16
|
+
threshold: area
|
|
17
|
+
});
|
|
18
|
+
this._continuity = new _commons.ContinuityObserver(this._handleContinuity.bind(this), {
|
|
19
|
+
onDuration: duration
|
|
20
|
+
});
|
|
21
|
+
this._displayed = undefined;
|
|
22
|
+
this._intersecting = false;
|
|
23
|
+
this._triggered = new Set();
|
|
24
|
+
}
|
|
25
|
+
observe(element) {
|
|
26
|
+
this._intersection.observe(element);
|
|
27
|
+
}
|
|
28
|
+
unobserve(element) {
|
|
29
|
+
this._intersection.unobserve(element);
|
|
30
|
+
}
|
|
31
|
+
display(index) {
|
|
32
|
+
if (this._triggered.has(index)) {
|
|
33
|
+
index = undefined;
|
|
34
|
+
}
|
|
35
|
+
if (this._displayed === index) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this._displayed = index;
|
|
39
|
+
this._continuity.value = false; // reset first, the old session is invalid now
|
|
40
|
+
this._syncContinuity();
|
|
41
|
+
}
|
|
42
|
+
disconnect() {
|
|
43
|
+
this._continuity.disconnect();
|
|
44
|
+
this._intersection.disconnect();
|
|
45
|
+
}
|
|
46
|
+
_syncContinuity() {
|
|
47
|
+
this._continuity.value = this._intersecting && this._displayed !== undefined;
|
|
48
|
+
}
|
|
49
|
+
_handleIntersection(entries) {
|
|
50
|
+
this._intersecting = entries[0].isIntersecting;
|
|
51
|
+
this._syncContinuity();
|
|
52
|
+
}
|
|
53
|
+
_handleContinuity(value) {
|
|
54
|
+
const displayed = this._displayed;
|
|
55
|
+
if (value && displayed !== undefined) {
|
|
56
|
+
if (this._triggered.has(displayed)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this._triggered.add(displayed);
|
|
60
|
+
this._displayed = undefined;
|
|
61
|
+
this._callback(displayed);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.default = CarouselItemViewableTracker;
|
|
@@ -21,7 +21,8 @@ function ready(layout, state) {
|
|
|
21
21
|
const {
|
|
22
22
|
templates
|
|
23
23
|
} = layout;
|
|
24
|
-
const
|
|
24
|
+
const items = layout._getItems(state);
|
|
25
|
+
const nonEmpty = items && items.length > 0;
|
|
25
26
|
const html = Object.getPrototypeOf(layout.constructor).defaultTemplates.ready(layout, state);
|
|
26
27
|
return nonEmpty ? templates.carousel(layout, state, html) : html;
|
|
27
28
|
}
|
|
@@ -71,7 +72,8 @@ class CarouselLayout extends _collection.default {
|
|
|
71
72
|
const {
|
|
72
73
|
state
|
|
73
74
|
} = states;
|
|
74
|
-
|
|
75
|
+
const items = this._getItems(state);
|
|
76
|
+
this._itemCount = items ? items.length : undefined;
|
|
75
77
|
this.syncSize();
|
|
76
78
|
}
|
|
77
79
|
get page() {
|
|
@@ -28,7 +28,7 @@ function ready(layout, state) {
|
|
|
28
28
|
const {
|
|
29
29
|
templates
|
|
30
30
|
} = layout;
|
|
31
|
-
const values = state
|
|
31
|
+
const values = layout._getItems(state);
|
|
32
32
|
|
|
33
33
|
// TODO: handle categories, attributes, etc. by introducing sublayout
|
|
34
34
|
if (values && values.length > 0 || state.ongoing) {
|
|
@@ -45,7 +45,7 @@ function list(layout, state, values) {
|
|
|
45
45
|
} = layout;
|
|
46
46
|
const listTag = templates.ordered ? 'ol' : 'ul';
|
|
47
47
|
// TODO: support separator?
|
|
48
|
-
return `<${listTag} class="${className}__list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
|
|
48
|
+
return `<${listTag} class="${className}__list" data-role="list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
|
|
49
49
|
}
|
|
50
50
|
function items(layout, state, values) {
|
|
51
51
|
let {
|
|
@@ -149,7 +149,7 @@ class CollectionLayout extends _template.default {
|
|
|
149
149
|
_html(state, rendered, incremental) {
|
|
150
150
|
if (incremental) {
|
|
151
151
|
const offset = rendered.value.length;
|
|
152
|
-
const values = state.
|
|
152
|
+
const values = (this._getItems(state) || []).slice(offset);
|
|
153
153
|
return values.length > 0 ? this.templates.items(this, state, values, {
|
|
154
154
|
offset
|
|
155
155
|
}) : '';
|
|
@@ -184,14 +184,17 @@ class CollectionLayout extends _template.default {
|
|
|
184
184
|
if (!element) {
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
|
-
const values = state
|
|
187
|
+
const values = this._getItems(state) || [];
|
|
188
188
|
let i = 0;
|
|
189
189
|
for (const itemElement of this._listItemElements(element)) {
|
|
190
190
|
itemElement[VALUE] = values[i++];
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
+
_getItems(state) {
|
|
194
|
+
return state.value;
|
|
195
|
+
}
|
|
193
196
|
_getListElement(element) {
|
|
194
|
-
return element.querySelector(
|
|
197
|
+
return element.querySelector('[data-role="list"]');
|
|
195
198
|
}
|
|
196
199
|
_listItemElements(element) {
|
|
197
200
|
return element ? Array.from(element.querySelectorAll(`[data-role="item"]`)) : [];
|
package/dist/cjs/util/states.js
CHANGED
package/dist/cjs/version.js
CHANGED
package/dist/cjs/workflow/ask.js
CHANGED
|
@@ -57,6 +57,7 @@ const DEFAULT_LAYOUTS = Object.freeze({
|
|
|
57
57
|
}],
|
|
58
58
|
[_constants.ROLE.QUERY_SUGGESTIONS]: _index3.OptionListLayout.type,
|
|
59
59
|
[_constants.ROLE.AFFILIATION]: [_index3.AffiliationLayout.type, {
|
|
60
|
+
incremental: true,
|
|
60
61
|
itemType: 'affiliation',
|
|
61
62
|
link: {
|
|
62
63
|
rel: 'noopener nofollow'
|
|
@@ -66,6 +67,9 @@ const DEFAULT_LAYOUTS = Object.freeze({
|
|
|
66
67
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
67
68
|
[_constants.ROLE.SOURCES]: {},
|
|
68
69
|
[_constants.ROLE.RELATED_RESOURCES]: {},
|
|
70
|
+
[_constants.ROLE.AFFILIATION]: {
|
|
71
|
+
viewable: false // track viewable manually
|
|
72
|
+
},
|
|
69
73
|
[_constants.ROLE.QUERY_SUGGESTIONS]: {
|
|
70
74
|
active: true,
|
|
71
75
|
// we are tracking events at initial stage, when session is not active yet
|
package/package.json
CHANGED
package/src/actor/tracker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { STATUS, ATTR_DATA_MISO_PRODUCT_ID } from '../constants.js';
|
|
1
|
+
import { STATUS, ATTR_DATA_MISO_PRODUCT_ID, EVENT_TYPE } from '../constants.js';
|
|
2
2
|
import * as fields from './fields.js';
|
|
3
3
|
import _Tracker from '../util/tracker.js';
|
|
4
4
|
import { fallbackListFunction } from '../util/bindings.js';
|
|
@@ -29,6 +29,8 @@ export default class Tracker {
|
|
|
29
29
|
|
|
30
30
|
this._unsubscribes = [
|
|
31
31
|
this._tracker.on('*', data => this._handleEvent(data)),
|
|
32
|
+
// TODO: view actor has another click event that does not comply to this data format
|
|
33
|
+
...[EVENT_TYPE.VIEWABLE].map(type => this._view.on(type, values => this._handleViewActorEvent(type, values))),
|
|
32
34
|
hub.on(fields.view(role), () => this.refresh()),
|
|
33
35
|
];
|
|
34
36
|
|
|
@@ -110,6 +112,10 @@ export default class Tracker {
|
|
|
110
112
|
return this._tracker.getState(productId);
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
_handleViewActorEvent(type, values) {
|
|
116
|
+
this._tracker._trigger(type, values);
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
_handleEvent(data) {
|
|
114
120
|
const property = this._role === 'results' ? 'products' : this._role; // TODO: ad-hoc, see #83
|
|
115
121
|
const misoId = this._getMisoId();
|
package/src/actor/utils.js
CHANGED
|
@@ -23,7 +23,9 @@ export function toInteraction({ property, misoId, request }, { event, values, ma
|
|
|
23
23
|
const question_source = request && request._meta && request._meta.question_source;
|
|
24
24
|
const isProductType = isProduct(property);
|
|
25
25
|
const product_ids = isProductType ? values.map(v => v.product_id).filter(v => v) : [];
|
|
26
|
-
const items = isProductType ? undefined : values.map(v => v.text || v);
|
|
26
|
+
const items = isProductType ? undefined : values.map(v => v.text || v.id || v);
|
|
27
|
+
const channel = property === ROLE.AFFILIATION ? values[0] && values[0].channel : undefined;
|
|
28
|
+
const positions = property === ROLE.AFFILIATION ? values.map(v => v.position) : undefined;
|
|
27
29
|
const payload = trimObj({
|
|
28
30
|
type: event === 'viewable' ? 'viewable_impression' : event,
|
|
29
31
|
product_ids,
|
|
@@ -32,7 +34,9 @@ export function toInteraction({ property, misoId, request }, { event, values, ma
|
|
|
32
34
|
custom_context: trimObj({
|
|
33
35
|
api_ts,
|
|
34
36
|
property,
|
|
37
|
+
channel,
|
|
35
38
|
items,
|
|
39
|
+
positions,
|
|
36
40
|
trigger: manual ? 'manual' : 'auto',
|
|
37
41
|
question_source,
|
|
38
42
|
}),
|
package/src/affiliation/index.js
CHANGED
|
@@ -14,16 +14,41 @@ export function processData(data) {
|
|
|
14
14
|
function processValue(value) {
|
|
15
15
|
if (value.sovrn_aff) {
|
|
16
16
|
const { sovrn_aff, ...rest } = value;
|
|
17
|
-
|
|
17
|
+
value = {
|
|
18
18
|
...rest,
|
|
19
19
|
affiliation: processSovrnData(sovrn_aff),
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
return value;
|
|
22
|
+
return postProcessValue(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function postProcessValue(value) {
|
|
26
|
+
const { affiliation } = value;
|
|
27
|
+
if (!affiliation) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
let { channel, products } = affiliation;
|
|
31
|
+
if (!products) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
products = products.map(product => ({ ...product }));
|
|
35
|
+
let i = 0;
|
|
36
|
+
for (const product of products) {
|
|
37
|
+
if (channel) {
|
|
38
|
+
product.channel = channel;
|
|
39
|
+
}
|
|
40
|
+
product.position = i++;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
...value,
|
|
44
|
+
affiliation: {
|
|
45
|
+
...affiliation,
|
|
46
|
+
products,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
function processSovrnData({ products, ...sovrn_aff }) {
|
|
26
|
-
products = products.slice(0, 1);
|
|
27
52
|
return {
|
|
28
53
|
channel: 'sovrn',
|
|
29
54
|
...sovrn_aff,
|
|
@@ -49,7 +74,6 @@ export function processSovrnProduct({
|
|
|
49
74
|
const brand_logo = merchant && merchant.logo;
|
|
50
75
|
return trimObj({
|
|
51
76
|
id: `sovrn-${id}`,
|
|
52
|
-
channel: 'sovrn',
|
|
53
77
|
title,
|
|
54
78
|
description,
|
|
55
79
|
cover_image,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
|
|
1
|
+
import { STATUS, LAYOUT_CATEGORY, EVENT_TYPE } from '../../constants.js';
|
|
2
2
|
import CollectionLayout from '../list/collection.js';
|
|
3
3
|
import { affiliation } from '../templates.js';
|
|
4
|
+
import CarouselItemViewableTracker from './viewable.js';
|
|
4
5
|
|
|
5
6
|
const TYPE = 'affiliation';
|
|
6
7
|
const DEFAULT_CLASSNAME = 'miso-affiliation';
|
|
@@ -16,20 +17,24 @@ function root(layout, state) {
|
|
|
16
17
|
|
|
17
18
|
function ready(layout, state) {
|
|
18
19
|
const { templates } = layout;
|
|
19
|
-
const
|
|
20
|
+
const items = layout._getItems(state);
|
|
20
21
|
|
|
21
|
-
if (
|
|
22
|
-
return templates.body(layout, state);
|
|
22
|
+
if (items && items.length > 0) {
|
|
23
|
+
return templates.control(layout, state, 'previous') + templates.body(layout, state) + templates.control(layout, state, 'next');
|
|
23
24
|
} else {
|
|
24
25
|
return templates.empty(layout, state);
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function control(layout, state, direction) {
|
|
30
|
+
const { className } = layout;
|
|
31
|
+
return `<div class="${className}__control-${direction}" data-role="${direction}"></div>`;
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
function body(layout, state) {
|
|
29
35
|
const { className, templates } = layout;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return `<div class="${className}__body">${templates.header(layout, state)}${templates.list(layout, state, products)}</div>`;
|
|
36
|
+
const items = layout._getItems(state);
|
|
37
|
+
return `<div class="${className}__body">${templates.list(layout, state, items)}</div>`;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
function header(layout, state) {
|
|
@@ -44,11 +49,34 @@ function header(layout, state) {
|
|
|
44
49
|
return `<div class="${className}__header"><div class="${className}__header-tab">${headerText}</div></div>`;
|
|
45
50
|
}
|
|
46
51
|
|
|
52
|
+
function item(layout, state, value, index) {
|
|
53
|
+
const { className, templates, options } = layout;
|
|
54
|
+
const { itemType } = options;
|
|
55
|
+
const header = templates.itemHeader(layout, state, value, { index });
|
|
56
|
+
const body = templates[itemType](layout, state, value, { index });
|
|
57
|
+
return `<li class="${className}__item">${header}${body}</li>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function itemHeader(layout, state, value, meta) {
|
|
61
|
+
const { className, templates } = layout;
|
|
62
|
+
let { headerText } = templates;
|
|
63
|
+
if (typeof headerText === 'function') {
|
|
64
|
+
headerText = headerText(layout, state, value, meta);
|
|
65
|
+
}
|
|
66
|
+
if (!headerText) {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
return `<div class="${className}__item-header"><div class="${className}__item-header-tab">${headerText}</div></div>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
47
72
|
const DEFAULT_TEMPLATES = Object.freeze({
|
|
48
73
|
root,
|
|
49
74
|
[STATUS.READY]: ready,
|
|
75
|
+
control,
|
|
50
76
|
body,
|
|
51
77
|
header,
|
|
78
|
+
item,
|
|
79
|
+
itemHeader,
|
|
52
80
|
affiliation,
|
|
53
81
|
ctaText: 'View',
|
|
54
82
|
headerText: 'Featured',
|
|
@@ -79,6 +107,128 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
79
107
|
|
|
80
108
|
constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
|
|
81
109
|
super({ className, itemType: 'affiliation', templates: { ...DEFAULT_TEMPLATES, ...templates }, ...options });
|
|
110
|
+
this._itemCount = undefined;
|
|
111
|
+
this._itemIndex = 0;
|
|
112
|
+
this._displayedIndex = undefined;
|
|
113
|
+
this._viewable = new CarouselItemViewableTracker(this._onViewable.bind(this));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get itemIndex() {
|
|
117
|
+
return this._itemIndex;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
next() {
|
|
121
|
+
if (this._itemCount === undefined) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.go(this._itemIndex + 1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
previous() {
|
|
128
|
+
if (this._itemCount === undefined) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.go(this._itemIndex - 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
go(index) {
|
|
135
|
+
if (this._itemCount === undefined) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (index < 0) {
|
|
139
|
+
index = this._itemCount - 1;
|
|
140
|
+
} else if (index >= this._itemCount) {
|
|
141
|
+
index = 0;
|
|
142
|
+
}
|
|
143
|
+
this._itemIndex = index;
|
|
144
|
+
this._syncItemIndex();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_getItems(state) {
|
|
148
|
+
const { products } = state.value || {};
|
|
149
|
+
return products;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_render(element, states, controls) {
|
|
153
|
+
super._render(element, states, controls);
|
|
154
|
+
this._syncItemCount(element, states);
|
|
155
|
+
this._syncItemIndex();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_syncElement(element) {
|
|
159
|
+
const oldElement = this._element;
|
|
160
|
+
super._syncElement(element);
|
|
161
|
+
this._syncViewableElement(oldElement, element);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_syncViewableElement(oldElement, newElement) {
|
|
165
|
+
if (oldElement === newElement) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
oldElement && this._viewable.unobserve(oldElement);
|
|
169
|
+
newElement && this._viewable.observe(newElement);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_syncItemCount(element, states) {
|
|
173
|
+
element = element.firstElementChild;
|
|
174
|
+
if (!element) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const { state } = states;
|
|
178
|
+
const items = this._getItems(state);
|
|
179
|
+
const itemCount = this._itemCount = items ? items.length : undefined;
|
|
180
|
+
if (itemCount === undefined) {
|
|
181
|
+
element.removeAttribute('data-item-count');
|
|
182
|
+
} else {
|
|
183
|
+
element.setAttribute('data-item-count', itemCount);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_syncItemIndex() {
|
|
188
|
+
// TODO: raf
|
|
189
|
+
const { element } = this._view;
|
|
190
|
+
const listElement = element && this._getListElement(element);
|
|
191
|
+
if (!listElement) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const index = this._itemIndex;
|
|
195
|
+
if (index === this._displayedIndex) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this._displayedIndex = index;
|
|
199
|
+
listElement.style.left = `${-index * 100}%`;
|
|
200
|
+
this._viewable.display(index);
|
|
201
|
+
this._view._events.emit('display', { index });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
_onViewable(index) {
|
|
205
|
+
const item = this._getRenderedItemData(index);
|
|
206
|
+
if (!item) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this._view._events.emit(EVENT_TYPE.VIEWABLE, [item]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
_getRenderedItemData(index) {
|
|
213
|
+
const { value } = this._rendered.get(this._element) || {};
|
|
214
|
+
if (!value) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
const { products = [] } = value;
|
|
218
|
+
return products[index];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
_onClick(event) {
|
|
222
|
+
super._onClick(event);
|
|
223
|
+
const element = event.target;
|
|
224
|
+
if (element.closest(`[data-role="previous"]`)) {
|
|
225
|
+
this.previous();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (element.closest(`[data-role="next"]`)) {
|
|
229
|
+
this.next();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
82
232
|
}
|
|
83
233
|
|
|
84
234
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ContinuityObserver } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
export default class CarouselItemViewableTracker {
|
|
4
|
+
|
|
5
|
+
constructor(callback, { area = 0.5, duration = 1000 } = {}) {
|
|
6
|
+
this._callback = callback;
|
|
7
|
+
this._intersection = new IntersectionObserver(this._handleIntersection.bind(this), { threshold: area });
|
|
8
|
+
this._continuity = new ContinuityObserver(this._handleContinuity.bind(this), { onDuration: duration });
|
|
9
|
+
|
|
10
|
+
this._displayed = undefined;
|
|
11
|
+
this._intersecting = false;
|
|
12
|
+
this._triggered = new Set();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
observe(element) {
|
|
16
|
+
this._intersection.observe(element);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
unobserve(element) {
|
|
20
|
+
this._intersection.unobserve(element);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
display(index) {
|
|
24
|
+
if (this._triggered.has(index)) {
|
|
25
|
+
index = undefined;
|
|
26
|
+
}
|
|
27
|
+
if (this._displayed === index) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this._displayed = index;
|
|
31
|
+
this._continuity.value = false; // reset first, the old session is invalid now
|
|
32
|
+
this._syncContinuity();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnect() {
|
|
36
|
+
this._continuity.disconnect();
|
|
37
|
+
this._intersection.disconnect();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_syncContinuity() {
|
|
41
|
+
this._continuity.value = this._intersecting && this._displayed !== undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_handleIntersection(entries) {
|
|
45
|
+
this._intersecting = entries[0].isIntersecting;
|
|
46
|
+
this._syncContinuity();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_handleContinuity(value) {
|
|
50
|
+
const displayed = this._displayed;
|
|
51
|
+
if (value && displayed !== undefined) {
|
|
52
|
+
if (this._triggered.has(displayed)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this._triggered.add(displayed);
|
|
56
|
+
this._displayed = undefined;
|
|
57
|
+
this._callback(displayed);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -17,7 +17,8 @@ const INHERITED_DEFAULT_TEMPLATES = Object.freeze({
|
|
|
17
17
|
|
|
18
18
|
function ready(layout, state) {
|
|
19
19
|
const { templates } = layout;
|
|
20
|
-
const
|
|
20
|
+
const items = layout._getItems(state);
|
|
21
|
+
const nonEmpty = items && items.length > 0;
|
|
21
22
|
const html = Object.getPrototypeOf(layout.constructor).defaultTemplates.ready(layout, state);
|
|
22
23
|
return nonEmpty ? templates.carousel(layout, state, html) : html;
|
|
23
24
|
}
|
|
@@ -60,7 +61,8 @@ export default class CarouselLayout extends CollectionLayout {
|
|
|
60
61
|
_render(element, states, controls) {
|
|
61
62
|
super._render(element, states, controls);
|
|
62
63
|
const { state } = states;
|
|
63
|
-
|
|
64
|
+
const items = this._getItems(state);
|
|
65
|
+
this._itemCount = items ? items.length : undefined;
|
|
64
66
|
this.syncSize();
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -14,7 +14,7 @@ function root(layout, state) {
|
|
|
14
14
|
|
|
15
15
|
function ready(layout, state) {
|
|
16
16
|
const { templates } = layout;
|
|
17
|
-
const values = state
|
|
17
|
+
const values = layout._getItems(state);
|
|
18
18
|
|
|
19
19
|
// TODO: handle categories, attributes, etc. by introducing sublayout
|
|
20
20
|
if ((values && values.length > 0) || state.ongoing) {
|
|
@@ -28,7 +28,7 @@ function list(layout, state, values) {
|
|
|
28
28
|
const { className, templates, options } = layout;
|
|
29
29
|
const listTag = templates.ordered ? 'ol' : 'ul';
|
|
30
30
|
// TODO: support separator?
|
|
31
|
-
return `<${listTag} class="${className}__list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
|
|
31
|
+
return `<${listTag} class="${className}__list" data-role="list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function items(layout, state, values, { offset = 0 } = {}) {
|
|
@@ -125,7 +125,7 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
125
125
|
_html(state, rendered, incremental) {
|
|
126
126
|
if (incremental) {
|
|
127
127
|
const offset = rendered.value.length;
|
|
128
|
-
const values = state.
|
|
128
|
+
const values = (this._getItems(state) || []).slice(offset);
|
|
129
129
|
return values.length > 0 ? this.templates.items(this, state, values, { offset }) : '';
|
|
130
130
|
} else {
|
|
131
131
|
return this.templates.root(this, state);
|
|
@@ -151,15 +151,19 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
151
151
|
if (!element) {
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
|
-
const values = state
|
|
154
|
+
const values = this._getItems(state) || [];
|
|
155
155
|
let i = 0;
|
|
156
156
|
for (const itemElement of this._listItemElements(element)) {
|
|
157
157
|
itemElement[VALUE] = values[i++];
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
_getItems(state) {
|
|
162
|
+
return state.value;
|
|
163
|
+
}
|
|
164
|
+
|
|
161
165
|
_getListElement(element) {
|
|
162
|
-
return element.querySelector(
|
|
166
|
+
return element.querySelector('[data-role="list"]');
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
_listItemElements(element) {
|
package/src/util/states.js
CHANGED
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.10.0-beta.
|
|
1
|
+
export default '1.10.0-beta.9';
|
package/src/workflow/ask.js
CHANGED
|
@@ -36,12 +36,15 @@ const DEFAULT_LAYOUTS = Object.freeze({
|
|
|
36
36
|
[ROLE.SOURCES]: [ListLayout.type, { incremental: true, itemType: 'article', templates: { ordered: true } }],
|
|
37
37
|
[ROLE.RELATED_RESOURCES]: [ListLayout.type, { incremental: true, itemType: 'article' }],
|
|
38
38
|
[ROLE.QUERY_SUGGESTIONS]: OptionListLayout.type,
|
|
39
|
-
[ROLE.AFFILIATION]: [AffiliationLayout.type, { itemType: 'affiliation', link: { rel: 'noopener nofollow' } }],
|
|
39
|
+
[ROLE.AFFILIATION]: [AffiliationLayout.type, { incremental: true, itemType: 'affiliation', link: { rel: 'noopener nofollow' } }],
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
43
43
|
[ROLE.SOURCES]: {},
|
|
44
44
|
[ROLE.RELATED_RESOURCES]: {},
|
|
45
|
+
[ROLE.AFFILIATION]: {
|
|
46
|
+
viewable: false, // track viewable manually
|
|
47
|
+
},
|
|
45
48
|
[ROLE.QUERY_SUGGESTIONS]: {
|
|
46
49
|
active: true, // we are tracking events at initial stage, when session is not active yet
|
|
47
50
|
click: {
|