@miso.ai/client-sdk-ui 1.11.0-beta.0 → 1.11.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 (118) hide show
  1. package/dist/cjs/actor/data.js +6 -7
  2. package/dist/cjs/actor/feedback.js +6 -39
  3. package/dist/cjs/actor/fields.js +14 -6
  4. package/dist/cjs/actor/hub.js +1 -2
  5. package/dist/cjs/actor/index.js +0 -7
  6. package/dist/cjs/actor/interactions.js +4 -15
  7. package/dist/cjs/actor/sessions.js +2 -33
  8. package/dist/cjs/actor/tracker.js +25 -26
  9. package/dist/cjs/actor/utils.js +0 -49
  10. package/dist/cjs/actor/view.js +40 -40
  11. package/dist/cjs/actor/views.js +32 -8
  12. package/dist/cjs/asset/svgs.js +18 -8
  13. package/dist/cjs/constants.js +18 -19
  14. package/dist/cjs/defaults/hybrid-search/answer-box.js +19 -3
  15. package/dist/cjs/defaults/hybrid-search/phrases.js +5 -5
  16. package/dist/cjs/defaults/hybrid-search/templates.js +37 -9
  17. package/dist/cjs/element/role/index.js +12 -5
  18. package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
  19. package/dist/cjs/element/role/miso-total.js +20 -0
  20. package/dist/cjs/layout/affiliation/affiliation.js +0 -3
  21. package/dist/cjs/layout/banner/banner.js +0 -4
  22. package/dist/cjs/layout/button/more-button.js +73 -0
  23. package/dist/cjs/layout/container/container.js +0 -3
  24. package/dist/cjs/layout/error/error.js +0 -4
  25. package/dist/cjs/layout/index.js +7 -0
  26. package/dist/cjs/layout/input/facets.js +5 -5
  27. package/dist/cjs/layout/input/option-list.js +0 -4
  28. package/dist/cjs/layout/input/search-box.js +33 -12
  29. package/dist/cjs/layout/list/cards.js +0 -3
  30. package/dist/cjs/layout/list/carousel.js +0 -3
  31. package/dist/cjs/layout/list/collection.js +25 -7
  32. package/dist/cjs/layout/list/list.js +0 -3
  33. package/dist/cjs/layout/radio/feedback.js +0 -3
  34. package/dist/cjs/layout/radio/radio.js +0 -4
  35. package/dist/cjs/layout/raf.js +4 -2
  36. package/dist/cjs/layout/text/text.js +0 -4
  37. package/dist/cjs/layout/text/typewriter/index.js +3 -3
  38. package/dist/cjs/layout/triggerable.js +79 -0
  39. package/dist/cjs/util/states.js +0 -15
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/workflow/answer-based.js +109 -111
  42. package/dist/cjs/workflow/ask.js +35 -27
  43. package/dist/cjs/workflow/asks.js +1 -1
  44. package/dist/cjs/workflow/autocompletable.js +33 -0
  45. package/dist/cjs/workflow/autocomplete.js +122 -0
  46. package/dist/cjs/workflow/base.js +169 -102
  47. package/dist/cjs/workflow/explore.js +26 -19
  48. package/dist/cjs/workflow/hybrid-search-answer.js +99 -0
  49. package/dist/cjs/workflow/hybrid-search-results.js +70 -0
  50. package/dist/cjs/workflow/hybrid-search-views.js +122 -0
  51. package/dist/cjs/workflow/hybrid-search.js +98 -103
  52. package/dist/cjs/workflow/options.js +32 -17
  53. package/dist/cjs/workflow/processors.js +185 -17
  54. package/dist/cjs/workflow/recommendation.js +36 -49
  55. package/dist/cjs/workflow/search-based.js +186 -0
  56. package/dist/cjs/workflow/search.js +21 -68
  57. package/package.json +2 -2
  58. package/src/actor/data.js +2 -4
  59. package/src/actor/feedback.js +6 -23
  60. package/src/actor/fields.js +14 -6
  61. package/src/actor/hub.js +1 -2
  62. package/src/actor/index.js +0 -1
  63. package/src/actor/interactions.js +4 -10
  64. package/src/actor/sessions.js +3 -30
  65. package/src/actor/tracker.js +25 -19
  66. package/src/actor/utils.js +0 -47
  67. package/src/actor/view.js +27 -15
  68. package/src/actor/views.js +36 -8
  69. package/src/asset/svgs.js +19 -7
  70. package/src/constants.js +25 -16
  71. package/src/defaults/hybrid-search/answer-box.js +17 -5
  72. package/src/defaults/hybrid-search/phrases.js +4 -4
  73. package/src/defaults/hybrid-search/templates.js +25 -9
  74. package/src/element/role/index.js +2 -1
  75. package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
  76. package/src/element/role/miso-total.js +16 -0
  77. package/src/layout/affiliation/affiliation.js +1 -5
  78. package/src/layout/banner/banner.js +0 -5
  79. package/src/layout/button/more-button.js +69 -0
  80. package/src/layout/container/container.js +1 -5
  81. package/src/layout/error/error.js +0 -5
  82. package/src/layout/index.js +2 -1
  83. package/src/layout/input/facets.js +5 -6
  84. package/src/layout/input/option-list.js +0 -5
  85. package/src/layout/input/search-box.js +27 -14
  86. package/src/layout/list/cards.js +0 -4
  87. package/src/layout/list/carousel.js +0 -4
  88. package/src/layout/list/collection.js +22 -9
  89. package/src/layout/list/list.js +0 -4
  90. package/src/layout/radio/feedback.js +0 -4
  91. package/src/layout/radio/radio.js +0 -5
  92. package/src/layout/raf.js +2 -2
  93. package/src/layout/text/text.js +0 -5
  94. package/src/layout/text/typewriter/index.js +4 -5
  95. package/src/layout/trackable.js +1 -1
  96. package/src/layout/triggerable.js +76 -0
  97. package/src/util/states.js +0 -14
  98. package/src/version.js +1 -1
  99. package/src/workflow/answer-based.js +101 -77
  100. package/src/workflow/ask.js +33 -25
  101. package/src/workflow/asks.js +1 -1
  102. package/src/workflow/autocompletable.js +29 -0
  103. package/src/workflow/autocomplete.js +113 -0
  104. package/src/workflow/base.js +147 -93
  105. package/src/workflow/explore.js +23 -8
  106. package/src/workflow/hybrid-search-answer.js +92 -0
  107. package/src/workflow/hybrid-search-results.js +63 -0
  108. package/src/workflow/hybrid-search-views.js +124 -0
  109. package/src/workflow/hybrid-search.js +102 -73
  110. package/src/workflow/options.js +26 -11
  111. package/src/workflow/processors.js +165 -14
  112. package/src/workflow/recommendation.js +32 -33
  113. package/src/workflow/search-based.js +183 -0
  114. package/src/workflow/search.js +21 -65
  115. package/dist/cjs/actor/autocomplete.js +0 -160
  116. package/dist/cjs/workflow/utils.js +0 -42
  117. package/src/actor/autocomplete.js +0 -133
  118. package/src/workflow/utils.js +0 -29
@@ -1,13 +1,14 @@
1
- import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
1
+ import { STATUS } from '../../constants.js';
2
2
  import TemplateBasedLayout from '../template.js';
3
3
  import { makeTrackable } from '../trackable.js';
4
+ import { makeTriggerable } from '../triggerable.js';
4
5
  import { product, article, question, productInfoBlock, articleInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock } from '../templates.js';
5
6
 
6
7
  function root(layout, state) {
7
8
  const { className, role, templates } = layout;
8
9
  const { status } = state;
9
10
  const roleAttr = role ? `data-role="${role}"` : '';
10
- return `<div class="${className} ${status}" ${roleAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}</div>`;
11
+ return `<div class="${className} ${status}" ${roleAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}${templates.trigger(layout, state)}${templates.loadingBar(layout, state)}</div>`;
11
12
  }
12
13
 
13
14
  function ready(layout, state) {
@@ -15,7 +16,7 @@ function ready(layout, state) {
15
16
  const values = layout._getItems(state);
16
17
 
17
18
  // TODO: handle categories, attributes, etc. by introducing sublayout
18
- if ((values && values.length > 0) || state.ongoing) {
19
+ if ((values && values.length > 0) || state.ongoing) { // TODO: ad-hoc ongoing?
19
20
  return templates.list(layout, state, values);
20
21
  } else {
21
22
  return templates.empty(layout, state);
@@ -42,6 +43,16 @@ function item(layout, state, value, index) {
42
43
  return `<li class="${className}__item">${body}</li>`;
43
44
  }
44
45
 
46
+ function trigger(layout, state) {
47
+ const { className } = layout;
48
+ return `<div class="${className}__trigger" data-role="trigger"></div>`;
49
+ }
50
+
51
+ function loadingBar(layout, state) {
52
+ const { className } = layout;
53
+ return `<div class="${className}__loading-bar"></div>`;
54
+ }
55
+
45
56
  // TODO: let templates.js control what to be included here
46
57
 
47
58
  const DEFAULT_TEMPLATES = Object.freeze({
@@ -67,14 +78,12 @@ const DEFAULT_TEMPLATES = Object.freeze({
67
78
  cta,
68
79
  imageBlock,
69
80
  indexBlock,
81
+ trigger,
82
+ loadingBar,
70
83
  });
71
84
 
72
85
  export default class CollectionLayout extends TemplateBasedLayout {
73
86
 
74
- static get category() {
75
- return LAYOUT_CATEGORY.LIST;
76
- }
77
-
78
87
  static get defaultTemplates() {
79
88
  return DEFAULT_TEMPLATES;
80
89
  }
@@ -86,6 +95,7 @@ export default class CollectionLayout extends TemplateBasedLayout {
86
95
  ...options,
87
96
  });
88
97
  this._initTrackable();
98
+ this._initTriggerable();
89
99
  }
90
100
 
91
101
  initialize(view) {
@@ -106,8 +116,7 @@ export default class CollectionLayout extends TemplateBasedLayout {
106
116
  // TODO: compare item ids as well
107
117
  return this.options.incremental &&
108
118
  rendered && rendered.value && rendered.value.length > 0 &&
109
- state.status === STATUS.READY &&
110
- rendered.status === STATUS.READY &&
119
+ state.value && state.value.length >= rendered.value.length &&
111
120
  state.session && rendered.session &&
112
121
  state.session.id === rendered.session.id;
113
122
  }
@@ -138,10 +147,12 @@ export default class CollectionLayout extends TemplateBasedLayout {
138
147
 
139
148
  _afterRender(element, state) {
140
149
  this._syncBindings(element, state);
150
+ this._syncTrigger(element, state);
141
151
  }
142
152
 
143
153
  _unrender() {
144
154
  this._clearBindings();
155
+ this._untrackTrigger();
145
156
  }
146
157
 
147
158
  _getItems(state) {
@@ -177,6 +188,7 @@ export default class CollectionLayout extends TemplateBasedLayout {
177
188
 
178
189
  destroy() {
179
190
  this._destroyTrackable();
191
+ this._destroyTriggerable();
180
192
  this._view = undefined;
181
193
  super.destroy();
182
194
  }
@@ -184,3 +196,4 @@ export default class CollectionLayout extends TemplateBasedLayout {
184
196
  }
185
197
 
186
198
  makeTrackable(CollectionLayout.prototype);
199
+ makeTriggerable(CollectionLayout.prototype);
@@ -5,10 +5,6 @@ const DEFAULT_CLASSNAME = 'miso-list';
5
5
 
6
6
  export default class ListLayout extends CollectionLayout {
7
7
 
8
- static get category() {
9
- return super.category;
10
- }
11
-
12
8
  static get type() {
13
9
  return TYPE;
14
10
  }
@@ -50,10 +50,6 @@ const DEFAULT_TEMPLATES = Object.freeze({
50
50
 
51
51
  export default class FeedbackLayout extends RadioLayout {
52
52
 
53
- static get category() {
54
- return super.category;
55
- }
56
-
57
53
  static get type() {
58
54
  return TYPE;
59
55
  }
@@ -1,5 +1,4 @@
1
1
  import { findInAncestors, requestAnimationFrame as raf } from '@miso.ai/commons';
2
- import { LAYOUT_CATEGORY } from '../../constants.js';
3
2
  import TemplateBasedLayout from '../template.js';
4
3
  import { requiresImplementation } from '../templates.js';
5
4
 
@@ -30,10 +29,6 @@ const DEFAULT_TEMPLATES = Object.freeze({
30
29
 
31
30
  export default class RadioLayout extends TemplateBasedLayout {
32
31
 
33
- static get category() {
34
- return LAYOUT_CATEGORY.RADIO;
35
- }
36
-
37
32
  static get defaultTemplates() {
38
33
  return DEFAULT_TEMPLATES;
39
34
  }
package/src/layout/raf.js CHANGED
@@ -2,8 +2,8 @@ import { defineValues, requestAnimationFrame as raf, capture } from '@miso.ai/co
2
2
 
3
3
  export default class RafLayout {
4
4
 
5
- constructor({ role, workflow, ...options } = {}) {
6
- defineValues(this, { role, workflow, options });
5
+ constructor({ role, workflow, workflowOptions, ...options } = {}) {
6
+ defineValues(this, { role, workflow, workflowOptions, options });
7
7
  this._unsubscribes = [];
8
8
  this._rendered = new WeakMap();
9
9
  this._element = undefined;
@@ -1,5 +1,4 @@
1
1
  import { escapeHtml } from '@miso.ai/commons';
2
- import { LAYOUT_CATEGORY } from '../../constants.js';
3
2
  import TemplateBasedLayout from '../template.js';
4
3
 
5
4
  const TYPE = 'text';
@@ -42,10 +41,6 @@ const DEFAULT_TEMPLATES = Object.freeze({
42
41
 
43
42
  export default class TextLayout extends TemplateBasedLayout {
44
43
 
45
- static get category() {
46
- return LAYOUT_CATEGORY.TEXT;
47
- }
48
-
49
44
  static get type() {
50
45
  return TYPE;
51
46
  }
@@ -1,5 +1,5 @@
1
1
  import { trimObj, defineValues, Resolution, pacer } from '@miso.ai/commons';
2
- import { LAYOUT_CATEGORY, STATUS } from '../../../constants.js';
2
+ import { STATUS } from '../../../constants.js';
3
3
  import ProgressiveLayout from '../../progressive.js';
4
4
  import PlaintextRenderer from './plaintext.js';
5
5
  import { containerElement, cursorClassName, fromSameSession, normalizeOnDebug } from './utils.js';
@@ -9,10 +9,6 @@ const DEFAULT_CLASSNAME = 'miso-typewriter';
9
9
 
10
10
  export default class TypewriterLayout extends ProgressiveLayout {
11
11
 
12
- static get category() {
13
- return LAYOUT_CATEGORY.TEXT;
14
- }
15
-
16
12
  static get type() {
17
13
  return TYPE;
18
14
  }
@@ -72,6 +68,9 @@ export default class TypewriterLayout extends ProgressiveLayout {
72
68
 
73
69
  async _setupForMarkdown() {
74
70
  const context = await this._view._views._extensions.require('markdown');
71
+ if (!this._view) {
72
+ return; // destroyed
73
+ }
75
74
  const cursorClass = cursorClassName(this.className);
76
75
  const { onDebug, onCitationLink } = this.options;
77
76
  // TODO: options
@@ -1,4 +1,4 @@
1
- import { mixin } from '@miso.ai/commons';
1
+ import { mixin } from '@miso.ai/commons';
2
2
  import Bindings from '../util/bindings.js';
3
3
  import Viewables from '../util/viewables.js';
4
4
  import { validateClick } from '../util/trackers.js';
@@ -0,0 +1,76 @@
1
+ import { mixin, viewable as whenViewable } from '@miso.ai/commons';
2
+ import { STATUS } from '../constants.js';
3
+ import { fields } from '../actor/index.js';
4
+
5
+ export function makeTriggerable(prototype) {
6
+ mixin(prototype, TriggerableMixin.prototype);
7
+ }
8
+
9
+ export class TriggerableMixin {
10
+
11
+ _initTriggerable() {
12
+ this._trigger = undefined;
13
+ }
14
+
15
+ _syncTrigger(element, state) {
16
+ if (!this.options.infiniteScroll) {
17
+ return;
18
+ }
19
+ const workflowPaginationOptions = this.workflowOptions.pagination;
20
+ if (!workflowPaginationOptions.active || workflowPaginationOptions.mode !== 'infiniteScroll') {
21
+ return;
22
+ }
23
+ // only track trigger when ready
24
+ if (state.status === STATUS.READY) {
25
+ this._trackTrigger(element);
26
+ } else {
27
+ this._untrackTrigger();
28
+ }
29
+ }
30
+
31
+ async _trackTrigger(element) {
32
+ if (this._trigger) {
33
+ return;
34
+ }
35
+ const triggerElement = element.querySelector('[data-role="trigger"]');
36
+ if (!triggerElement) {
37
+ return;
38
+ }
39
+ const ac = new AbortController();
40
+ this._trigger = Object.freeze({
41
+ element: triggerElement,
42
+ ac,
43
+ });
44
+ let result = false;
45
+ try {
46
+ await whenViewable(triggerElement, { area: 1, duration: 0, signal: ac.signal });
47
+ result = true;
48
+ } catch (e) {
49
+ if (e.name !== 'AbortError') {
50
+ throw e;
51
+ }
52
+ } finally {
53
+ this._trigger = undefined;
54
+ }
55
+ if (result) {
56
+ this._onTrigger();
57
+ }
58
+ }
59
+
60
+ _onTrigger() {
61
+ this._view.hub.trigger(fields.more());
62
+ }
63
+
64
+ _untrackTrigger() {
65
+ if (!this._trigger) {
66
+ return;
67
+ }
68
+ this._trigger.ac && this._trigger.ac.abort();
69
+ this._trigger = undefined;
70
+ }
71
+
72
+ _destroyTriggerable() {
73
+ this._untrackTrigger();
74
+ }
75
+
76
+ }
@@ -4,20 +4,6 @@ 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
- /*
8
- const STATES = {
9
- NEW: Object.freeze({
10
- [IMPRESSION]: UNTRACKED,
11
- [VIEWABLE]: UNTRACKED,
12
- }),
13
- UNTRACKED: Object.freeze({
14
- [IMPRESSION]: UNTRACKED,
15
- [VIEWABLE]: UNTRACKED,
16
- [CLICK]: UNTRACKED,
17
- }),
18
- };
19
- */
20
-
21
7
  const UNTRACKED_STATES = Object.freeze({
22
8
  [IMPRESSION]: UNTRACKED,
23
9
  [VIEWABLE]: UNTRACKED,
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.11.0-beta.0';
1
+ export default '1.11.0-beta.10';
@@ -1,16 +1,17 @@
1
- import { trimObj, API } from '@miso.ai/commons';
1
+ import { API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- // import * as sources from '../source.js';
5
- import { fields, FeedbackActor, AutocompleteActor } from '../actor/index.js';
6
- import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE, DATA_ASPECT } from '../constants.js';
3
+ import { fields, FeedbackActor } from '../actor/index.js';
4
+ import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
7
5
  import { SearchBoxLayout, TextLayout, ListLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
8
6
  import { processData as processAffiliationData } from '../affiliation/index.js';
9
- import { writeAnswerStageToMeta } from './processors.js';
7
+ import { mergeRolesOptions } from './options.js';
8
+ import { writeAnswerStageToMeta, mergeInteraction } from './processors.js';
10
9
 
11
10
  const DEFAULT_API_OPTIONS = Object.freeze({
11
+ ...Workflow.DEFAULT_API_OPTIONS,
12
12
  group: API.GROUP.ASK,
13
13
  payload: {
14
+ ...Workflow.DEFAULT_API_OPTIONS.payload,
14
15
  source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at'],
15
16
  cite_link: 1,
16
17
  cite_start: '[',
@@ -27,6 +28,7 @@ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
27
28
  });
28
29
 
29
30
  const DEFAULT_LAYOUTS = Object.freeze({
31
+ ...Workflow.DEFAULT_LAYOUTS,
30
32
  [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
31
33
  [ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
32
34
  [ROLE.ANSWER]: TypewriterLayout.type,
@@ -36,17 +38,25 @@ const DEFAULT_LAYOUTS = Object.freeze({
36
38
  });
37
39
 
38
40
  const DEFAULT_TRACKERS = Object.freeze({
41
+ ...Workflow.DEFAULT_TRACKERS,
39
42
  [ROLE.SOURCES]: {},
40
43
  [ROLE.AFFILIATION]: {},
44
+ [ROLE.PRODUCTS]: {},
41
45
  });
42
46
 
43
47
  const DEFAULT_OPTIONS = Object.freeze({
48
+ ...Workflow.DEFAULT_OPTIONS,
44
49
  api: DEFAULT_API_OPTIONS,
45
50
  autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
46
51
  layouts: DEFAULT_LAYOUTS,
47
52
  trackers: DEFAULT_TRACKERS,
48
53
  });
49
54
 
55
+ const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
56
+ main: ROLE.ANSWER,
57
+ members: Object.keys(DEFAULT_LAYOUTS),
58
+ });
59
+
50
60
  export default class AnswerBasedWorkflow extends Workflow {
51
61
 
52
62
  // constructor //
@@ -59,12 +69,6 @@ export default class AnswerBasedWorkflow extends Workflow {
59
69
  _initActors(args) {
60
70
  super._initActors(args);
61
71
  this._feedback = new FeedbackActor(this._hub);
62
- /*
63
- this._autocomplete = new AutocompleteActor(this._hub, {
64
- source: sources.api(this._client),
65
- options: this._options,
66
- });
67
- */
68
72
  }
69
73
 
70
74
  _initSubscriptions(args) {
@@ -72,30 +76,49 @@ export default class AnswerBasedWorkflow extends Workflow {
72
76
  this._unsubscribes = [
73
77
  ...this._unsubscribes,
74
78
  this._hub.on(fields.query(), args => this._query(args)),
75
- this._hub.on(fields.view(ROLE.ANSWER), data => this._onAnswerViewUpdate(data)),
76
79
  this._views.get(ROLE.ANSWER).on('citation-click', event => this._onCitationClick(event)),
77
80
  ];
78
81
  }
79
82
 
83
+ // configuration //
84
+ useLink(fn) {
85
+ if (typeof fn !== 'function' && fn !== false) {
86
+ throw new Error('useLink(fn) expects fn to be a function or false');
87
+ }
88
+ this._linkFn = fn;
89
+ return this;
90
+ }
91
+
80
92
  // lifecycle //
81
93
  restart() {
82
94
  // clear question id from previous session
83
95
  if (this._questionId) {
84
96
  this._questionId = undefined;
85
97
  }
86
- // interrupt event
87
- // TODO: move to workflow base
88
- const { session, status, ongoing } = this._hub.states[fields.view(ROLE.ANSWER)] || {};
89
- if (session && status !== STATUS.INITIAL && (status !== STATUS.READY || ongoing)) {
90
- // it's interrupted by a new question
91
- const state = Object.freeze({ session, status, ongoing });
92
- this._emit('interrupt', state);
93
- this._emit('finally', state);
94
- }
95
98
  // restart
96
99
  super.restart();
97
100
  }
98
101
 
102
+ _emitInterruptEventIfNecessary() {
103
+ const state = this._hub.states[fields.view(this._roles.main)] || {};
104
+ if (state.session && state.status !== STATUS.INITIAL && (state.status !== STATUS.READY || state.ongoing)) {
105
+ // it's interrupted by a new question
106
+ this._emitLifecycleEvent('interrupt', state);
107
+ this._emitLifecycleEvent('finally', state);
108
+ }
109
+ }
110
+
111
+ _onMainViewUpdate(state) {
112
+ const { status } = state;
113
+ const done = status === STATUS.READY && !state.ongoing;
114
+ const erroneous = status === STATUS.ERRONEOUS;
115
+ const eventName = done ? 'done' : erroneous ? 'error' : status;
116
+ this._emitLifecycleEvent(eventName, state);
117
+ if (done || erroneous) {
118
+ this._emitLifecycleEvent('finally', state);
119
+ }
120
+ }
121
+
99
122
  // properties //
100
123
  get questionId() {
101
124
  return this._questionId;
@@ -130,53 +153,61 @@ export default class AnswerBasedWorkflow extends Workflow {
130
153
  }
131
154
 
132
155
  _query(args = {}) {
156
+ if (this._linkFn) {
157
+ this._submit(args);
158
+ return;
159
+ }
133
160
  // start a new session
134
161
  this.restart();
135
162
 
136
- // keep track of question source on this session
137
- const { session } = this;
138
- session.meta.question_source = args.questionSource || ORGANIC_QUESTION_SOURCE; // might be null, not undefined
163
+ // keep track of question source on this session, for suggested questions interactions
164
+ this._writeQuestionSourceToSession(args);
139
165
 
140
166
  // build payload and trigger request
141
167
  const payload = this._buildPayload(args);
142
- const event = mergeApiOptions(this._options.resolved.api, { payload, session });
143
- this._request(event);
168
+ this._request({ payload });
144
169
  }
145
170
 
146
- _request(event) {
147
- this._hub.update(fields.request(), event);
171
+ _submit(args) {
172
+ if (!this._linkFn) {
173
+ return;
174
+ }
175
+ // TODO: we want a submit event, but only with linkFn
176
+ const url = this._linkFn(args.q);
177
+ window.open(url, '_blank');
148
178
  }
149
179
 
150
- _buildPayload() {
151
- throw new Error(`Not implemented`);
180
+ _writeQuestionSourceToSession(args) {
181
+ this.session.meta.question_source = args.qs || ORGANIC_QUESTION_SOURCE; // might be null, not undefined
152
182
  }
153
183
 
154
- // autocomplete //
155
- /*
156
- updateCompletions(event) {
157
- // TODO: verify
158
- this._hub.update(fields.data(DATA_ASPECT.AUTOCOMPLETE), {
159
- ...event,
160
- source: 'manual',
161
- });
162
- return this;
184
+ _buildPayload() {
185
+ throw new Error(`Not implemented`);
163
186
  }
164
- */
165
187
 
166
188
  // data //
167
- _defaultProcessData(data) {
168
- data = super._defaultProcessData(data);
169
-
170
- // capture question ID and register at context
171
- this._writeQuestionIdFromData(data);
189
+ _updateData(data) {
190
+ // if it's the head response, write question id and return
191
+ if (data.value && !data.value.answer_stage) {
192
+ this._handleHeadResponse(data);
193
+ return;
194
+ }
195
+ super._updateData(data);
172
196
 
173
197
  // update URL if autoQuery.updateUrl is not false
174
198
  this._updateUrlIfNecessary(data);
199
+ }
175
200
 
176
- // affiliation
177
- data = processAffiliationData(data);
201
+ _handleHeadResponse(data) {
202
+ // capture question ID and register at context
203
+ this._writeQuestionIdFromData(data);
204
+ }
178
205
 
179
- return writeAnswerStageToMeta(data);
206
+ _defaultProcessData(data) {
207
+ data = super._defaultProcessData(data);
208
+ data = processAffiliationData(data);
209
+ data = writeAnswerStageToMeta(data);
210
+ return data;
180
211
  }
181
212
 
182
213
  _writeQuestionIdFromData({ value }) {
@@ -204,38 +235,32 @@ export default class AnswerBasedWorkflow extends Workflow {
204
235
  }
205
236
 
206
237
  // interactions //
207
- _preprocessInteraction(payload) {
208
- payload = super._preprocessInteraction(payload) || {};
209
- const { context = {} } = payload;
210
- const { custom_context = {} } = context;
211
- let { questionId } = this;
212
- return {
213
- ...payload,
238
+ _defaultProcessInteraction(payload, args) {
239
+ payload = super._defaultProcessInteraction(payload, args);
240
+ payload = this._writeAskPropertiesToInteraction(payload, args);
241
+ return payload;
242
+ }
243
+
244
+ _writeAskPropertiesToInteraction(payload = {}, args) {
245
+ const question_source = this._getQuestionSourceFromViewState(args);
246
+ const question_id = this.questionId;
247
+ return mergeInteraction(payload, {
214
248
  context: {
215
- ...context,
216
- custom_context: trimObj({
217
- question_id: questionId,
218
- ...custom_context,
219
- }),
249
+ custom_context: {
250
+ question_source,
251
+ question_id,
252
+ },
220
253
  },
221
- };
254
+ });
222
255
  }
223
256
 
224
- // handlers //
225
- _onAnswerViewUpdate({ session, status, ongoing }) {
226
- if (status === STATUS.INITIAL) {
227
- return;
228
- }
229
- const done = status === STATUS.READY && !ongoing;
230
- const erroneous = status === STATUS.ERRONEOUS;
231
- const eventName = done ? 'done' : erroneous ? 'error' : status;
232
- const state = Object.freeze({ session, status, ongoing });
233
- this._emit(eventName, state);
234
- if (done || erroneous) {
235
- this._emit('finally', state);
236
- }
257
+ _getQuestionSourceFromViewState(args) {
258
+ const { request } = this._hub.states[fields.view(args.role)] || {};
259
+ const { payload } = request || {};
260
+ return (payload && payload._meta && payload._meta.question_source) || undefined;
237
261
  }
238
262
 
263
+ // handlers //
239
264
  _onCitationClick({ index }) {
240
265
  const { value } = this.states[fields.data()] || {};
241
266
  const { sources } = value || {};
@@ -269,12 +294,11 @@ export default class AnswerBasedWorkflow extends Workflow {
269
294
 
270
295
  }
271
296
 
272
- // makeConfigurable(AnswerBasedWorkflow.prototype, ['autocomplete']);
273
-
274
297
  Object.assign(AnswerBasedWorkflow, {
275
298
  DEFAULT_API_OPTIONS,
276
299
  DEFAULT_AUTOCOMPLETE_OPTIONS,
277
300
  DEFAULT_LAYOUTS,
278
301
  DEFAULT_TRACKERS,
279
302
  DEFAULT_OPTIONS,
303
+ ROLES_OPTIONS,
280
304
  });