@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.
Files changed (90) hide show
  1. package/dist/cjs/actor/autocomplete.js +157 -0
  2. package/dist/cjs/actor/data.js +4 -12
  3. package/dist/cjs/actor/fields.js +8 -0
  4. package/dist/cjs/actor/index.js +7 -0
  5. package/dist/cjs/actor/tracker.js +46 -70
  6. package/dist/cjs/actor/trackers.js +5 -1
  7. package/dist/cjs/actor/utils.js +16 -2
  8. package/dist/cjs/actor/view.js +14 -3
  9. package/dist/cjs/actor/views.js +9 -1
  10. package/dist/cjs/affiliation/index.js +126 -0
  11. package/dist/cjs/combo/ask/options.js +1 -1
  12. package/dist/cjs/combo/base.js +1 -1
  13. package/dist/cjs/constants.js +6 -3
  14. package/dist/cjs/defaults/ask/controls.js +1 -1
  15. package/dist/cjs/defaults/ask/phrases.js +1 -1
  16. package/dist/cjs/defaults/ask/templates.js +35 -4
  17. package/dist/cjs/element/role/index.js +7 -0
  18. package/dist/cjs/element/role/miso-affiliation.js +20 -0
  19. package/dist/cjs/layout/affiliation/affiliation.js +330 -0
  20. package/dist/cjs/layout/banner/banner.js +1 -1
  21. package/dist/cjs/layout/container/container.js +1 -1
  22. package/dist/cjs/layout/error/error.js +1 -1
  23. package/dist/cjs/layout/index.js +7 -0
  24. package/dist/cjs/layout/input/option-list.js +35 -50
  25. package/dist/cjs/layout/input/search-box.js +227 -129
  26. package/dist/cjs/layout/list/cards.js +1 -1
  27. package/dist/cjs/layout/list/carousel.js +5 -3
  28. package/dist/cjs/layout/list/collection.js +44 -49
  29. package/dist/cjs/layout/list/list.js +1 -1
  30. package/dist/cjs/layout/radio/feedback.js +1 -1
  31. package/dist/cjs/layout/radio/radio.js +1 -1
  32. package/dist/cjs/layout/raf.js +1 -1
  33. package/dist/cjs/layout/template.js +1 -1
  34. package/dist/cjs/layout/templates.js +172 -66
  35. package/dist/cjs/layout/text/text.js +1 -1
  36. package/dist/cjs/layout/text/typewriter/index.js +3 -3
  37. package/dist/cjs/layout/trackable.js +124 -0
  38. package/dist/cjs/plugin.js +1 -1
  39. package/dist/cjs/source/index.js +20 -8
  40. package/dist/cjs/util/bindings.js +58 -110
  41. package/dist/cjs/util/states.js +31 -24
  42. package/dist/cjs/util/trackers.js +75 -0
  43. package/dist/cjs/util/viewables.js +55 -0
  44. package/dist/cjs/version.js +1 -1
  45. package/dist/cjs/workflow/ask.js +60 -19
  46. package/dist/cjs/workflow/asks.js +1 -1
  47. package/dist/cjs/workflow/base.js +6 -7
  48. package/dist/cjs/workflow/explore.js +5 -2
  49. package/dist/cjs/workflow/options.js +60 -14
  50. package/dist/cjs/workflow/recommendation.js +2 -0
  51. package/dist/cjs/workflow/search.js +35 -7
  52. package/package.json +2 -2
  53. package/src/actor/autocomplete.js +131 -0
  54. package/src/actor/data.js +3 -10
  55. package/src/actor/fields.js +8 -0
  56. package/src/actor/index.js +1 -0
  57. package/src/actor/tracker.js +39 -62
  58. package/src/actor/trackers.js +3 -1
  59. package/src/actor/utils.js +14 -2
  60. package/src/actor/view.js +12 -1
  61. package/src/actor/views.js +11 -0
  62. package/src/affiliation/index.js +108 -0
  63. package/src/constants.js +10 -1
  64. package/src/defaults/ask/phrases.js +1 -1
  65. package/src/defaults/ask/templates.js +34 -2
  66. package/src/element/role/index.js +1 -0
  67. package/src/element/role/miso-affiliation.js +16 -0
  68. package/src/layout/affiliation/affiliation.js +295 -0
  69. package/src/layout/index.js +1 -0
  70. package/src/layout/input/option-list.js +35 -43
  71. package/src/layout/input/search-box.js +192 -95
  72. package/src/layout/list/carousel.js +4 -2
  73. package/src/layout/list/collection.js +42 -43
  74. package/src/layout/templates.js +127 -46
  75. package/src/layout/text/typewriter/index.js +2 -2
  76. package/src/layout/trackable.js +106 -0
  77. package/src/source/index.js +14 -1
  78. package/src/util/bindings.js +47 -103
  79. package/src/util/states.js +26 -20
  80. package/src/util/trackers.js +69 -0
  81. package/src/util/viewables.js +47 -0
  82. package/src/version.js +1 -1
  83. package/src/workflow/ask.js +37 -3
  84. package/src/workflow/base.js +2 -3
  85. package/src/workflow/explore.js +1 -1
  86. package/src/workflow/options.js +54 -13
  87. package/src/workflow/recommendation.js +1 -0
  88. package/src/workflow/search.js +33 -1
  89. package/dist/cjs/util/tracker.js +0 -348
  90. package/src/util/tracker.js +0 -318
@@ -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.0-beta.0';
1
+ export default '1.10.0-beta.10';
@@ -1,9 +1,12 @@
1
1
  import { defineValues, trimObj, API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
3
  import { mergeApiOptions } from './options.js';
4
- import { fields, FeedbackActor } from '../actor/index.js';
4
+ import * as sources from '../source/index.js';
5
+ import { fields, FeedbackActor, AutocompleteActor } from '../actor/index.js';
5
6
  import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
6
- import { SearchBoxLayout, OptionListLayout, ListLayout, TextLayout, TypewriterLayout, FeedbackLayout } from '../layout/index.js';
7
+ import { SearchBoxLayout, OptionListLayout, ListLayout, TextLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
8
+ import { makeConfigurable } from './options.js';
9
+ import { processData as processAffiliationData } from '../affiliation/index.js';
7
10
 
8
11
  const DEFAULT_API_OPTIONS = Object.freeze({
9
12
  group: API.GROUP.ASK,
@@ -17,6 +20,14 @@ const DEFAULT_API_OPTIONS = Object.freeze({
17
20
  },
18
21
  });
19
22
 
23
+ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
24
+ actor: false,
25
+ api: {
26
+ group: API.GROUP.ASK,
27
+ name: API.NAME.AUTOCOMPLETE,
28
+ },
29
+ });
30
+
20
31
  const DEFAULT_LAYOUTS = Object.freeze({
21
32
  [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
22
33
  [ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
@@ -25,13 +36,14 @@ const DEFAULT_LAYOUTS = Object.freeze({
25
36
  [ROLE.SOURCES]: [ListLayout.type, { incremental: true, itemType: 'article', templates: { ordered: true } }],
26
37
  [ROLE.RELATED_RESOURCES]: [ListLayout.type, { incremental: true, itemType: 'article' }],
27
38
  [ROLE.QUERY_SUGGESTIONS]: OptionListLayout.type,
39
+ [ROLE.AFFILIATION]: [AffiliationLayout.type, { incremental: true, itemType: 'affiliation', link: { rel: 'noopener nofollow' } }],
28
40
  });
29
41
 
30
42
  const DEFAULT_TRACKERS = Object.freeze({
31
43
  [ROLE.SOURCES]: {},
32
44
  [ROLE.RELATED_RESOURCES]: {},
45
+ [ROLE.AFFILIATION]: {},
33
46
  [ROLE.QUERY_SUGGESTIONS]: {
34
- active: true, // we are tracking events at initial stage, when session is not active yet
35
47
  click: {
36
48
  validate: event => event.button === 0, // left click only
37
49
  },
@@ -40,6 +52,7 @@ const DEFAULT_TRACKERS = Object.freeze({
40
52
 
41
53
  const DEFAULT_OPTIONS = Object.freeze({
42
54
  api: DEFAULT_API_OPTIONS,
55
+ autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
43
56
  layouts: DEFAULT_LAYOUTS,
44
57
  trackers: DEFAULT_TRACKERS,
45
58
  });
@@ -60,6 +73,13 @@ export default class Ask extends Workflow {
60
73
  this._feedback = new FeedbackActor(this._hub);
61
74
  this._setSuggestedQuestions();
62
75
 
76
+ /*
77
+ this._autocomplete = new AutocompleteActor(this._hub, {
78
+ source: sources.api(this._client),
79
+ options: this._options,
80
+ });
81
+ */
82
+
63
83
  this._unsubscribes = [
64
84
  ...this._unsubscribes,
65
85
  this._hub.on(fields.query(), payload => this.query(payload)),
@@ -152,6 +172,17 @@ export default class Ask extends Workflow {
152
172
  super.restart();
153
173
  }
154
174
 
175
+ /*
176
+ updateCompletions(event) {
177
+ // TODO: verify
178
+ this._hub.update(fields.completions(), {
179
+ ...event,
180
+ source: 'manual',
181
+ });
182
+ return this;
183
+ }
184
+ */
185
+
155
186
  _defaultProcessData(data) {
156
187
  data = super._defaultProcessData(data);
157
188
  const { value, request } = data;
@@ -178,6 +209,9 @@ export default class Ask extends Workflow {
178
209
  }
179
210
  }
180
211
 
212
+ // affiliation
213
+ data = processAffiliationData(data);
214
+
181
215
  // 1. put answer_stage to meta
182
216
  // 2. mark ongoing flag
183
217
  return {
@@ -42,14 +42,13 @@ export default class Workflow extends Component {
42
42
  const extensions = this._extensions = plugin._getExtensions(client);
43
43
  const options = this._options = new WorkflowOptions(context && context._options, mergeDefaults(this, defaults));
44
44
  const hub = this._hub = injectLogger(new Hub(), (...args) => this._log(...args));
45
- const source = this._source = sources.api(client);
46
45
  const layouts = plugin.layouts;
47
46
 
48
47
  this._sessions = new SessionMaker(hub);
49
- this._data = new DataActor(hub, { source, options });
48
+ this._data = new DataActor(hub, { source: sources.api(client), options });
50
49
  const views = this._views = new ViewsActor(hub, { extensions, layouts, roles, options, workflow: name });
51
- this._interactions = new InteractionsActor(hub, { client, options });
52
50
  this._trackers = new Trackers(hub, { views, options });
51
+ this._interactions = new InteractionsActor(hub, { client, options });
53
52
 
54
53
  this._unsubscribes = [
55
54
  this._hub.on(fields.response(), data => this.updateData(data)),
@@ -11,7 +11,7 @@ const DEFAULT_API_OPTIONS = Object.freeze({
11
11
  });
12
12
 
13
13
  const DEFAULT_LAYOUTS = Object.freeze({
14
- [ROLE.RELATED_QUESTIONS]: [ListLayout.type, { itemType: 'question' }],
14
+ [ROLE.RELATED_QUESTIONS]: [ListLayout.type, { itemType: 'question', link: { rel: 'noopener nofollow' } }],
15
15
  [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
16
16
  });
17
17
 
@@ -3,6 +3,7 @@ import { ROLE } from '../constants.js';
3
3
 
4
4
  // normalize //
5
5
  export function normalizeApiOptions([name, payload] = []) {
6
+ // TODO: take object form as well
6
7
  if (name === false) {
7
8
  return { actor: false };
8
9
  }
@@ -10,10 +11,14 @@ export function normalizeApiOptions([name, payload] = []) {
10
11
  payload = name;
11
12
  name = undefined;
12
13
  }
14
+ let group = undefined;
15
+ if (name && name.indexOf('/') !== -1) {
16
+ [group, name] = name.split('/');
17
+ }
13
18
  if ((name && typeof name !== 'string') || (payload !== undefined && typeof payload !== 'object')) {
14
19
  throw new Error(`Invalid arguments for useApi(): ${name}, ${payload}`);
15
20
  }
16
- return trimObj({ name, payload });
21
+ return trimObj({ group, name, payload });
17
22
  }
18
23
 
19
24
  export function normalizeLayoutsOptions(options = {}) {
@@ -105,6 +110,17 @@ export function normalizeInteractionsOptions(options) {
105
110
  return { ...options, preprocess };
106
111
  }
107
112
 
113
+ export function normalizeAutocompleteOptions(options) {
114
+ if (options === undefined) {
115
+ throw new Error(`Expect autocomplete options to be an object or a boolean value: ${options}`);
116
+ }
117
+ if (typeof options === 'boolean') {
118
+ options = { actor: options };
119
+ }
120
+ const processData = normalizeDataProcessorOptions(options.processData);
121
+ return { ...options, processData };
122
+ }
123
+
108
124
  // merge //
109
125
  export function mergeApiOptions(...optionsList) {
110
126
  if (optionsList[optionsList.length - 1] === false) {
@@ -190,6 +206,17 @@ export function mergeInteractionsOptions(...optionsList) {
190
206
  }));
191
207
  }
192
208
 
209
+ export function mergeAutocompleteOptions(...optionsList) {
210
+ if (optionsList[optionsList.length - 1] === false) {
211
+ return false;
212
+ }
213
+ return mergeOptions(optionsList, (merged, options) => Object.assign(merged, {
214
+ ...options,
215
+ api: mergeApiOptions(merged.api, options.api),
216
+ processData: mergeDataProcessorOptions(merged.processData, options.processData),
217
+ }));
218
+ }
219
+
193
220
 
194
221
 
195
222
  // manifest //
@@ -219,6 +246,11 @@ const FEATURES = [
219
246
  normalize: normalizeInteractionsOptions,
220
247
  merge: mergeInteractionsOptions,
221
248
  },
249
+ {
250
+ key: 'autocomplete',
251
+ normalize: normalizeAutocompleteOptions,
252
+ merge: mergeAutocompleteOptions,
253
+ },
222
254
  ];
223
255
 
224
256
 
@@ -328,18 +360,27 @@ for (const { key, normalize, merge } of FEATURES) {
328
360
  });
329
361
  }
330
362
 
331
- export function makeConfigurable(prototype) {
332
- Object.assign(prototype, {
333
- useApi(...args) {
334
- this._options.api.merge(args);
335
- return this;
336
- },
337
- clearApi() {
338
- this._options.api.set();
339
- return this;
340
- },
341
- });
342
- for (const feature of ['layouts', 'dataProcessor', 'trackers', 'interactions']) {
363
+ const DEFAULT_FEATURES = ['api', 'layouts', 'dataProcessor', 'trackers', 'interactions'];
364
+
365
+ export function makeConfigurable(prototype, features = DEFAULT_FEATURES) {
366
+ for (const feature of features) {
367
+ injectConfigurableFeature(prototype, feature);
368
+ }
369
+ }
370
+
371
+ function injectConfigurableFeature(prototype, feature) {
372
+ if (feature === 'api') {
373
+ Object.assign(prototype, {
374
+ useApi(...args) {
375
+ this._options.api.merge(args);
376
+ return this;
377
+ },
378
+ clearApi() {
379
+ this._options.api.set();
380
+ return this;
381
+ },
382
+ });
383
+ } else {
343
384
  const upperCased = upperCase(feature);
344
385
  Object.assign(prototype, {
345
386
  [`use${upperCased}`](value) {
@@ -53,6 +53,7 @@ export default class Recommendation extends Workflow {
53
53
  return this;
54
54
  }
55
55
 
56
+ // TODO: deprecate this
56
57
  startTracker() {
57
58
  this.useApi(false);
58
59
  this.useLayouts(false);
@@ -1,9 +1,11 @@
1
1
  import { API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
3
  import { mergeApiOptions } from './options.js';
4
- import { fields } from '../actor/index.js';
4
+ import * as sources from '../source/index.js';
5
+ import { fields, AutocompleteActor } from '../actor/index.js';
5
6
  import { ROLE } from '../constants.js';
6
7
  import { ListLayout, SearchBoxLayout } from '../layout/index.js';
8
+ import { makeConfigurable } from './options.js';
7
9
 
8
10
  const DEFAULT_API_OPTIONS = Object.freeze({
9
11
  group: API.GROUP.SEARCH,
@@ -13,6 +15,19 @@ const DEFAULT_API_OPTIONS = Object.freeze({
13
15
  },
14
16
  });
15
17
 
18
+ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
19
+ actor: false,
20
+ api: {
21
+ group: API.GROUP.SEARCH,
22
+ name: API.NAME.AUTOCOMPLETE,
23
+ payload: {
24
+ completion_fields: ['suggested_queries', 'title'],
25
+ fl: ['title', 'url', 'cover_image'],
26
+ },
27
+ },
28
+ throttle: 300,
29
+ });
30
+
16
31
  const DEFAULT_LAYOUTS = Object.freeze({
17
32
  [ROLE.QUERY]: SearchBoxLayout.type,
18
33
  [ROLE.PRODUCTS]: ListLayout.type,
@@ -24,6 +39,7 @@ const DEFAULT_TRACKERS = Object.freeze({
24
39
 
25
40
  const DEFAULT_OPTIONS = Object.freeze({
26
41
  api: DEFAULT_API_OPTIONS,
42
+ autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
27
43
  layouts: DEFAULT_LAYOUTS,
28
44
  trackers: DEFAULT_TRACKERS,
29
45
  });
@@ -39,6 +55,11 @@ export default class Search extends Workflow {
39
55
  defaults: DEFAULT_OPTIONS,
40
56
  });
41
57
 
58
+ this._autocomplete = new AutocompleteActor(this._hub, {
59
+ source: sources.api(this._client),
60
+ options: this._options,
61
+ });
62
+
42
63
  this._unsubscribes.push(this._hub.on(fields.query(), payload => this.query(payload)));
43
64
  }
44
65
 
@@ -66,9 +87,20 @@ export default class Search extends Workflow {
66
87
  return this;
67
88
  }
68
89
 
90
+ updateCompletions(event) {
91
+ // TODO: verify
92
+ this._hub.update(fields.completions(), {
93
+ ...event,
94
+ source: 'manual',
95
+ });
96
+ return this;
97
+ }
98
+
69
99
  notifyViewUpdate(role = ROLE.PRODUCTS, ...args) {
70
100
  super.notifyViewUpdate(role, ...args);
71
101
  return this;
72
102
  }
73
103
 
74
104
  }
105
+
106
+ makeConfigurable(Search.prototype, ['autocomplete']);