@miso.ai/client-sdk-ui 1.10.0-beta.9 → 1.10.1-beta.0
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/index.js +0 -7
- package/dist/cjs/actor/tracker.js +47 -102
- package/dist/cjs/actor/utils.js +1 -1
- package/dist/cjs/actor/view.js +12 -1
- package/dist/cjs/actor/views.js +24 -2
- package/dist/cjs/asset/svgs.js +14 -4
- package/dist/cjs/constants.js +3 -2
- package/dist/cjs/layout/affiliation/affiliation.js +152 -58
- package/dist/cjs/layout/input/option-list.js +34 -49
- package/dist/cjs/layout/list/collection.js +22 -43
- package/dist/cjs/layout/templates.js +54 -32
- package/dist/cjs/layout/text/typewriter/index.js +12 -7
- package/dist/cjs/layout/trackable.js +124 -0
- 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 +1 -5
- package/dist/cjs/workflow/base.js +2 -7
- package/dist/cjs/workflow/recommendation.js +2 -0
- package/package.json +2 -2
- package/src/actor/index.js +0 -1
- package/src/actor/tracker.js +37 -94
- package/src/actor/utils.js +1 -1
- package/src/actor/view.js +14 -1
- package/src/actor/views.js +30 -1
- package/src/asset/svgs.js +13 -2
- package/src/constants.js +7 -1
- package/src/layout/affiliation/affiliation.js +140 -55
- package/src/layout/input/option-list.js +35 -43
- package/src/layout/list/collection.js +20 -38
- package/src/layout/templates.js +25 -19
- package/src/layout/text/typewriter/index.js +8 -6
- package/src/layout/trackable.js +106 -0
- 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 +1 -4
- package/src/workflow/base.js +3 -5
- package/src/workflow/recommendation.js +1 -0
- package/dist/cjs/actor/trackers.js +0 -51
- package/dist/cjs/layout/affiliation/viewable.js +0 -65
- package/dist/cjs/layout/text/typewriter/progress.js +0 -45
- package/dist/cjs/util/tracker.js +0 -348
- package/src/actor/trackers.js +0 -45
- package/src/layout/affiliation/viewable.js +0 -61
- package/src/layout/text/typewriter/progress.js +0 -33
- package/src/util/tracker.js +0 -318
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { trimObj, defineValues, Resolution } from '@miso.ai/commons';
|
|
1
|
+
import { trimObj, defineValues, Resolution, pacer } from '@miso.ai/commons';
|
|
2
2
|
import { LAYOUT_CATEGORY, STATUS } from '../../../constants.js';
|
|
3
3
|
import ProgressiveLayout from '../../progressive.js';
|
|
4
|
-
import ProgressController from './progress.js';
|
|
5
4
|
import PlaintextRenderer from './plaintext.js';
|
|
6
5
|
import { containerElement, cursorClassName, fromSameSession } from './utils.js';
|
|
7
6
|
|
|
@@ -27,6 +26,9 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
27
26
|
tag = 'auto',
|
|
28
27
|
format = 'markdown',
|
|
29
28
|
tooltip = false,
|
|
29
|
+
speed,
|
|
30
|
+
acceleration,
|
|
31
|
+
nextCursorFn,
|
|
30
32
|
...options
|
|
31
33
|
} = {}) {
|
|
32
34
|
super({
|
|
@@ -39,7 +41,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
39
41
|
className,
|
|
40
42
|
});
|
|
41
43
|
this._prevState = undefined;
|
|
42
|
-
this.
|
|
44
|
+
this._getNextCursor = typeof nextCursorFn === 'function' ? nextCursorFn : pacer({ speed, acceleration });
|
|
43
45
|
this._readiness = new Resolution();
|
|
44
46
|
|
|
45
47
|
// kick off sooner
|
|
@@ -81,7 +83,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
81
83
|
onDebug,
|
|
82
84
|
});
|
|
83
85
|
// capture citation link click if necessary
|
|
84
|
-
this._unsubscribes.push(this._view.proxyElement.on('click', (e) => this.
|
|
86
|
+
this._unsubscribes.push(this._view.proxyElement.on('click', (e) => this._onClick(e)));
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
_getSource(index) {
|
|
@@ -134,7 +136,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
134
136
|
// new typing streak
|
|
135
137
|
result = this._renderer.clear(container, rendered);
|
|
136
138
|
} else {
|
|
137
|
-
const cursor = this.
|
|
139
|
+
const cursor = this._getNextCursor(rendered.cursor, state.doneAt, rendered.timestamp, state.timestamp);
|
|
138
140
|
result = this._renderer.update(container, rendered, { ...state, cursor });
|
|
139
141
|
}
|
|
140
142
|
writeToState(result);
|
|
@@ -148,7 +150,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
// event //
|
|
151
|
-
|
|
153
|
+
_onClick({ target }) {
|
|
152
154
|
const element = target.closest(`[data-role="citation-link"]`);
|
|
153
155
|
if (!element) {
|
|
154
156
|
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.tracker.options || {};
|
|
69
|
+
if (!options) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this._view.tracker.impression(entries.map(({ value }) => value));
|
|
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.tracker.options || {};
|
|
86
|
+
if (!options) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const { value, element } of entries) {
|
|
90
|
+
(async () => (await this._viewables.track(element, options)) && this._view.tracker.viewable([value]))();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_trackClick(event, binding) {
|
|
95
|
+
const { click: options } = this._view.tracker.options || {};
|
|
96
|
+
if (validateClick(options, event, binding)) {
|
|
97
|
+
this._view.tracker.click([binding.value]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_destroyTrackable() {
|
|
102
|
+
const { unbounds } = this._bindings.clear();
|
|
103
|
+
this._untrackViewables(unbounds);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
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.id || value;
|
|
65
71
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { findInAncestors, trimObj } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TRACKER_OPTIONS = Object.freeze({
|
|
4
|
+
impression: Object.freeze({
|
|
5
|
+
}),
|
|
6
|
+
viewable: Object.freeze({
|
|
7
|
+
area: 0.5,
|
|
8
|
+
duration: 1000,
|
|
9
|
+
}),
|
|
10
|
+
click: Object.freeze({
|
|
11
|
+
lenient: false,
|
|
12
|
+
}),
|
|
13
|
+
watch: false,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function mergeOptions(def, opt) {
|
|
17
|
+
// if opt is falsy then return false, merge otherwise
|
|
18
|
+
return !!opt && { ...def, ...opt };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeTrackerOptions(options) {
|
|
22
|
+
if (options === false) {
|
|
23
|
+
return false; // turn off all tracking
|
|
24
|
+
}
|
|
25
|
+
if (options === true) {
|
|
26
|
+
options = {};
|
|
27
|
+
}
|
|
28
|
+
if (typeof options !== 'object') {
|
|
29
|
+
throw new Error(`Invalid options: ${options}`);
|
|
30
|
+
}
|
|
31
|
+
const { impression = {}, viewable = {}, click = {}, ...rest } = options;
|
|
32
|
+
return trimObj({
|
|
33
|
+
...DEFAULT_TRACKER_OPTIONS,
|
|
34
|
+
...rest,
|
|
35
|
+
impression: mergeOptions(DEFAULT_TRACKER_OPTIONS.impression, impression),
|
|
36
|
+
viewable: mergeOptions(DEFAULT_TRACKER_OPTIONS.viewable, viewable),
|
|
37
|
+
click: mergeOptions(DEFAULT_TRACKER_OPTIONS.click, click),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function validateClick(options = {}, event) {
|
|
42
|
+
if (!options) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof options.validate === 'function') {
|
|
47
|
+
return options.validate(event);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (options.lenient) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// standard criteria
|
|
55
|
+
// 1. it must be a left click
|
|
56
|
+
if (event.button !== 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// 2. event default must not be prevented
|
|
60
|
+
if (event.defaultPrevented) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
// 3. must go through a real link
|
|
64
|
+
if (!findInAncestors(event.target, element => (element.tagName === 'A' && element.href) || undefined)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { viewable as whenViewable } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
export default class Viewables {
|
|
4
|
+
|
|
5
|
+
constructor() {
|
|
6
|
+
this._entries = new WeakMap();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
isTracked(element) {
|
|
10
|
+
return this._entries.has(element);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async track(element, options = {}) {
|
|
14
|
+
if (!options) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const entry = this._entries.get(element);
|
|
18
|
+
if (entry) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const ac = new AbortController();
|
|
22
|
+
const { signal } = ac;
|
|
23
|
+
this._entries.set(element, { ac });
|
|
24
|
+
let result = false;
|
|
25
|
+
try {
|
|
26
|
+
await whenViewable(element, { ...options, signal });
|
|
27
|
+
result = true;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (e.name !== 'AbortError') {
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
} finally {
|
|
33
|
+
this._entries.delete(element);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
untrack(element) {
|
|
39
|
+
const entry = this._entries.get(element);
|
|
40
|
+
if (!entry) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
entry.ac.abort();
|
|
44
|
+
this._entries.delete(element);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.10.
|
|
1
|
+
export default '1.10.1-beta.0';
|
package/src/workflow/ask.js
CHANGED
|
@@ -42,11 +42,8 @@ const DEFAULT_LAYOUTS = Object.freeze({
|
|
|
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
|
+
[ROLE.AFFILIATION]: {},
|
|
48
46
|
[ROLE.QUERY_SUGGESTIONS]: {
|
|
49
|
-
active: true, // we are tracking events at initial stage, when session is not active yet
|
|
50
47
|
click: {
|
|
51
48
|
validate: event => event.button === 0, // left click only
|
|
52
49
|
},
|
package/src/workflow/base.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component } from '@miso.ai/commons';
|
|
2
|
-
import { Hub, SessionMaker, DataActor, ViewsActor, InteractionsActor,
|
|
2
|
+
import { Hub, SessionMaker, DataActor, ViewsActor, InteractionsActor, fields } from '../actor/index.js';
|
|
3
3
|
import * as sources from '../source/index.js';
|
|
4
4
|
import { STATUS, ROLE } from '../constants.js';
|
|
5
5
|
import { ContainerLayout, ErrorLayout } from '../layout/index.js';
|
|
@@ -46,9 +46,8 @@ export default class Workflow extends Component {
|
|
|
46
46
|
|
|
47
47
|
this._sessions = new SessionMaker(hub);
|
|
48
48
|
this._data = new DataActor(hub, { source: sources.api(client), options });
|
|
49
|
-
|
|
49
|
+
this._views = new ViewsActor(hub, { extensions, layouts, roles, options, workflow: name });
|
|
50
50
|
this._interactions = new InteractionsActor(hub, { client, options });
|
|
51
|
-
this._trackers = new Trackers(hub, { views, options });
|
|
52
51
|
|
|
53
52
|
this._unsubscribes = [
|
|
54
53
|
this._hub.on(fields.response(), data => this.updateData(data)),
|
|
@@ -151,7 +150,7 @@ export default class Workflow extends Component {
|
|
|
151
150
|
|
|
152
151
|
// trackers //
|
|
153
152
|
get trackers() {
|
|
154
|
-
return this.
|
|
153
|
+
return this._views.trackers;
|
|
155
154
|
}
|
|
156
155
|
|
|
157
156
|
_preprocessInteraction({
|
|
@@ -190,7 +189,6 @@ export default class Workflow extends Component {
|
|
|
190
189
|
}
|
|
191
190
|
this._unsubscribes = [];
|
|
192
191
|
|
|
193
|
-
this._trackers._destroy();
|
|
194
192
|
this._views._destroy({ dom });
|
|
195
193
|
this._data._destroy();
|
|
196
194
|
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _tracker = _interopRequireDefault(require("./tracker.js"));
|
|
8
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
-
class Trackers {
|
|
10
|
-
constructor(hub, _ref) {
|
|
11
|
-
let {
|
|
12
|
-
views,
|
|
13
|
-
options
|
|
14
|
-
} = _ref;
|
|
15
|
-
this._hub = hub;
|
|
16
|
-
this._views = views;
|
|
17
|
-
this._options = options;
|
|
18
|
-
this._trackers = {};
|
|
19
|
-
options.on('trackers', () => this._syncConfig());
|
|
20
|
-
this._syncConfig();
|
|
21
|
-
}
|
|
22
|
-
_syncConfig() {
|
|
23
|
-
for (const [role, options] of Object.entries(this._options.resolved.trackers)) {
|
|
24
|
-
this._createOrConfigTracker(role, options);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
_createOrConfigTracker(role, options) {
|
|
28
|
-
if (options === undefined) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
let tracker = this._trackers[role];
|
|
32
|
-
if (!tracker) {
|
|
33
|
-
tracker = this._trackers[role] = new _tracker.default(this._hub, this._views.get(role), options);
|
|
34
|
-
Object.defineProperty(this, role, {
|
|
35
|
-
get: () => this._trackers[role]
|
|
36
|
-
});
|
|
37
|
-
} else {
|
|
38
|
-
tracker.config(options);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
_destroy() {
|
|
42
|
-
for (const tracker of Object.values(this._trackers)) {
|
|
43
|
-
try {
|
|
44
|
-
tracker._destroy();
|
|
45
|
-
} catch (e) {
|
|
46
|
-
console.error(e);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
exports.default = Trackers;
|