@miso.ai/client-sdk-ui 1.9.1-beta.0 → 1.9.1-beta.2

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.
@@ -5,23 +5,20 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _commons = require("@miso.ai/commons");
8
- var _constants = require("./constants.js");
8
+ var _index = require("../../defaults/ask/index.js");
9
9
  class AskComboElements {
10
10
  constructor(root) {
11
+ let {
12
+ classPrefix = _index.constants.CLASS_PREFIX
13
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11
14
  if (!root) {
12
15
  throw new Error('Root element is required');
13
16
  }
14
17
  (0, _commons.defineValues)(this, {
15
18
  root,
16
- followUpsSection: root.querySelector(`#${_constants.PREFIX}__follow-ups`),
17
- relatedResourcesContainer: root.querySelector(`#${_constants.PREFIX}__related-resources miso-ask`)
19
+ followUpsSection: root.querySelector(`.${classPrefix}__follow-ups`),
20
+ relatedResourcesContainer: root.querySelector(`.${classPrefix}__related-resources miso-ask`)
18
21
  });
19
- if (!this.followUpsSection) {
20
- throw new Error(`Follow-ups section element not found.`);
21
- }
22
- if (!this.relatedResourcesContainer) {
23
- throw new Error(`Related resources container element not found.`);
24
- }
25
22
  }
26
23
  }
27
24
  exports.default = AskComboElements;
@@ -45,7 +45,7 @@ class AskCombo extends _base.default {
45
45
  }
46
46
  async _start() {
47
47
  await (0, _commons.waitForDomContentLoaded)();
48
- await this.MisoClient.cmdDone;
48
+ await this.waitForElement();
49
49
 
50
50
  // setup MisoClient
51
51
  await this._setupMisoClient();
@@ -96,7 +96,7 @@ class AskCombo extends _base.default {
96
96
  templates = {}
97
97
  } = options;
98
98
  element.innerHTML = templates.root(options);
99
- this._elements = new _elements.default(element);
99
+ this._elements = new _elements.default(element, options);
100
100
  }
101
101
  async _createClientInstance() {
102
102
  this._client = new MisoClient(this.resolvedOptions);
@@ -108,40 +108,43 @@ class AskCombo extends _base.default {
108
108
  resolvedOptions: options
109
109
  } = this;
110
110
  const {
111
- templates = {}
111
+ templates = {},
112
+ features = {}
112
113
  } = options;
113
114
  const context = client.ui.asks;
114
115
  const rootWorkflow = client.ui.ask;
115
116
 
116
- // TODO: set placeholder is present in options
117
-
118
- // when a answer is fully populated, insert a new section for the follow-up question
119
- context.on('done', _ref => {
120
- let {
121
- workflow
122
- } = _ref;
123
- elements.followUpsSection.insertAdjacentHTML('beforeend', templates.followUp(Object.assign({}, options, {
124
- parentQuestionId: workflow.questionId
125
- })));
126
- });
127
-
128
- // when a new query starts, associate the last section container (for related resources) to that workflow
129
- context.on('loading', _ref2 => {
130
- let {
131
- workflow
132
- } = _ref2;
133
- elements.relatedResourcesContainer.workflow = workflow;
134
- });
117
+ // TODO: set placeholder if present in options
135
118
 
136
- // if user starts over, clean up current follow-up questions
137
- rootWorkflow.on('loading', () => {
138
- // clean up the entire follow-ups section
139
- elements.followUpsSection.innerHTML = '';
140
- // destroy all follow-up workflows
141
- context.reset({
142
- root: false
119
+ if (elements.followUpsSection && features.followUpQuestions !== false) {
120
+ // when a answer is fully populated, insert a new section for the follow-up question
121
+ context.on('done', _ref => {
122
+ let {
123
+ workflow
124
+ } = _ref;
125
+ elements.followUpsSection.insertAdjacentHTML('beforeend', templates.followUp(Object.assign({}, options, {
126
+ parentQuestionId: workflow.questionId
127
+ })));
143
128
  });
144
- });
129
+ // if user starts over, clean up current follow-up questions
130
+ rootWorkflow.on('loading', () => {
131
+ // clean up the entire follow-ups section
132
+ elements.followUpsSection.innerHTML = '';
133
+ // destroy all follow-up workflows
134
+ context.reset({
135
+ root: false
136
+ });
137
+ });
138
+ }
139
+ if (elements.relatedResourcesContainer && features.relatedResources !== false && features.followUpQuestions !== false) {
140
+ // when a new query starts, associate the last section container (for related resources) to that workflow
141
+ context.on('loading', _ref2 => {
142
+ let {
143
+ workflow
144
+ } = _ref2;
145
+ elements.relatedResourcesContainer.workflow = workflow;
146
+ });
147
+ }
145
148
 
146
149
  // start query if specified in URL
147
150
  rootWorkflow.autoQuery();
@@ -27,8 +27,13 @@ class Combo extends _commons.Component {
27
27
  get element() {
28
28
  return this._element;
29
29
  }
30
+ async waitForElement() {
31
+ return this._element || (this._elementRes || (this._elementRes = new _commons.Resolution())).promise;
32
+ }
30
33
  set element(element) {
34
+ // TODO: guard against multiple element assignment
31
35
  this._element = element;
36
+ this._elementRes && this._elementRes.resolve(element);
32
37
  }
33
38
  start(options) {
34
39
  if (this._started) {
@@ -7,90 +7,124 @@ exports.followUp = followUp;
7
7
  exports.root = root;
8
8
  var _constants = require("./constants.js");
9
9
  var DEFAULT_PHRASES = _interopRequireWildcard(require("./phrases.js"));
10
+ const _excluded = ["classPrefix", "circledCitationIndex"],
11
+ _excluded2 = ["classes"];
10
12
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
11
13
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
12
- // TODO: refactor
13
- // TODO: make ids constants
14
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
14
15
  // TODO: placeholder
15
16
 
16
17
  function root() {
17
18
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
18
- const {
19
- classPrefix = _constants.CLASS_PREFIX
20
- } = options;
21
- return `
22
- <section id="${classPrefix}__question" class="${classPrefix}__section ${classPrefix}__question">
23
- <miso-ask class="${classPrefix}__query-container">
24
- <miso-query></miso-query>
25
- </miso-ask>
26
- </section>
27
- <section class="${classPrefix}__section ${classPrefix}__answer">
28
- ${answer(options)}
29
- ${sources(options)}
30
- <miso-ask class="${classPrefix}__bottom-spacing-container" visible-when="ongoing"></miso-ask>
31
- </section>
32
- <div id="${classPrefix}__follow-ups" class="${classPrefix}__follow-ups"></div>
33
- <section id="${classPrefix}__related-resources" class="${classPrefix}__section ${classPrefix}__related-resources">
34
- <miso-ask visible-when="nonempty" logo="true">
35
- <h2 class="${classPrefix}__phrase ${classPrefix}__related-resources-phrase">${phraseText(options, 'relatedResources')}</h2>
36
- <miso-related-resources></miso-related-resources>
37
- </miso-ask>
38
- </section>`.trim();
19
+ options = normalizeOptions(options);
20
+ return [section(options, {
21
+ name: 'question'
22
+ }, [container(options, {
23
+ name: 'query'
24
+ }, '<miso-query></miso-query>')]), section(options, {
25
+ name: 'answer'
26
+ }, answerGroup(options)), followUps(options), relatedResources(options)].join('');
39
27
  }
40
28
  ;
41
29
  function followUp() {
42
30
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
43
- const {
44
- classPrefix = _constants.CLASS_PREFIX,
45
- parentQuestionId
46
- } = options;
47
- return `
48
- <section class="${classPrefix}__section ${classPrefix}__follow-up">
49
- <miso-ask class="${classPrefix}__query-suggestions-container" visible-when="initial+nonempty" parent-question-id="${parentQuestionId}">
50
- <h3 class="${classPrefix}__phrase ${classPrefix}__related-questions-phrase">${phraseText(options, 'relatedQuestions')}</h3>
51
- <miso-query-suggestions></miso-query-suggestions>
52
- </miso-ask>
53
- <miso-ask class="${classPrefix}__query-container" visible-when="initial loading" parent-question-id="${parentQuestionId}">
54
- <miso-query></miso-query>
55
- </miso-ask>
56
- ${answer(options)}
57
- ${sources(options)}
58
- <miso-ask class="${classPrefix}__bottom-spacing-container" visible-when="ongoing" parent-question-id="${parentQuestionId}"></miso-ask>
59
- </section>`.trim();
31
+ options = normalizeOptions(options);
32
+ return section(options, {
33
+ name: 'follow-up'
34
+ }, [querySuggestions(options), container(options, {
35
+ name: 'query',
36
+ visibleWhen: 'initial loading'
37
+ }, '<miso-query></miso-query>'), ...answerGroup(options)]);
60
38
  }
61
39
  ;
62
40
 
63
- // helpers //
64
- function answer(options) {
41
+ // options //
42
+ function normalizeOptions() {
43
+ let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
44
+ let {
45
+ classPrefix = _constants.CLASS_PREFIX,
46
+ circledCitationIndex = true
47
+ } = _ref,
48
+ options = _objectWithoutPropertiesLoose(_ref, _excluded);
49
+ return Object.assign({
50
+ classPrefix,
51
+ circledCitationIndex
52
+ }, options);
53
+ }
54
+
55
+ // sections & containers //
56
+ function followUps(options) {
65
57
  const {
66
- classPrefix = _constants.CLASS_PREFIX
58
+ classPrefix,
59
+ features = {}
67
60
  } = options;
68
- return `
69
- ${containerOpenTag('answer', Object.assign({}, options, {
61
+ return features.followUpQuestions === false ? '' : `<div id="${classPrefix}__follow-ups" class="${classPrefix}__follow-ups"></div>`;
62
+ }
63
+ function relatedResources(options) {
64
+ const {
65
+ features = {},
66
+ logo = true
67
+ } = options;
68
+ const body = features.relatedResources === false ? '' : container(options, {
69
+ name: 'related-resources',
70
+ visibleWhen: 'nonempty',
71
+ logo
72
+ }, [phrase(options, {
73
+ name: 'related-resources',
74
+ tag: 'h2'
75
+ }), '<miso-related-resources></miso-related-resources>']);
76
+ return section(options, {
77
+ name: 'related-resources'
78
+ }, body);
79
+ }
80
+ function querySuggestions(options) {
81
+ const {
82
+ features = {}
83
+ } = options;
84
+ return features.querySuggestions === false ? '' : container(options, {
85
+ name: 'query-suggestions',
86
+ visibleWhen: 'initial+nonempty'
87
+ }, [phrase(options, {
88
+ name: 'related-questions',
89
+ tag: 'h3'
90
+ }), '<miso-query-suggestions></miso-query-suggestions>']);
91
+ }
92
+ function answerGroup(options) {
93
+ return [answer(options), sources(options), container(options, {
94
+ name: 'bottom-spacing',
95
+ visibleWhen: 'ongoing'
96
+ })];
97
+ }
98
+ function answer(options) {
99
+ return container(options, {
100
+ name: 'answer',
70
101
  visibleWhen: 'ready'
71
- }))}
72
- ${options.parentQuestionId ? '<hr>' : ''}
73
- <div class="${classPrefix}__phrase ${classPrefix}__question-phrase">${phraseText(options, 'question')}</div>
74
- <miso-question></miso-question>
75
- <miso-answer></miso-answer>
76
- <miso-feedback></miso-feedback>
77
- </miso-ask>`.trim();
102
+ }, [options.parentQuestionId ? '<hr>' : '', phrase(options, {
103
+ name: 'question',
104
+ tag: 'div'
105
+ }), '<miso-question></miso-question>', '<miso-answer></miso-answer>', '<miso-feedback></miso-feedback>']);
78
106
  }
79
107
  ;
80
108
  function sources(options) {
81
- const {
82
- classPrefix = _constants.CLASS_PREFIX
83
- } = options;
84
- return `
85
- ${containerOpenTag('sources', Object.assign({}, options, {
109
+ return container(options, {
110
+ name: 'sources',
86
111
  visibleWhen: 'nonempty'
87
- }))}
88
- <hr>
89
- <h3 class="${classPrefix}__phrase ${classPrefix}__sources-phrase">${phraseText(options, 'sources')}</h3>
90
- <miso-sources></miso-sources>
91
- </miso-ask>`.trim();
112
+ }, ['<hr>', phrase(options, {
113
+ name: 'sources',
114
+ tag: 'h3'
115
+ }), '<miso-sources></miso-sources>']);
92
116
  }
93
117
  ;
118
+ function phrase(options, _ref2) {
119
+ let {
120
+ name,
121
+ tag = 'div'
122
+ } = _ref2;
123
+ const {
124
+ classPrefix
125
+ } = options;
126
+ return `<${tag} class="${classPrefix}__phrase ${classPrefix}__${name}-phrase">${phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
127
+ }
94
128
  function phraseText(options, key) {
95
129
  const {
96
130
  phrases = {}
@@ -105,24 +139,67 @@ function phraseText(options, key) {
105
139
  return '';
106
140
  }
107
141
  }
108
- function containerOpenTag(name, _ref) {
142
+ function section(_ref3, _ref4, body) {
143
+ let {
144
+ classPrefix
145
+ } = _ref3;
146
+ let {
147
+ name
148
+ } = _ref4;
149
+ return element('section', {
150
+ classes: [`${classPrefix}__section`, `${classPrefix}__${name}`]
151
+ }, body);
152
+ }
153
+ function container(_ref5, _ref6, body) {
109
154
  let {
155
+ classPrefix,
156
+ circledCitationIndex,
157
+ parentQuestionId
158
+ } = _ref5;
159
+ let {
160
+ name,
110
161
  visibleWhen,
111
- classPrefix = _constants.CLASS_PREFIX,
112
- logo = false,
113
- circledCitationIndex = true,
162
+ logo = false
163
+ } = _ref6;
164
+ const classes = [`${classPrefix}__${name}-container`];
165
+ circledCitationIndex && classes.push('miso-circled-citation-index');
166
+ return element('miso-ask', {
167
+ classes,
168
+ visibleWhen,
169
+ logo,
114
170
  parentQuestionId
115
- } = _ref;
116
- let className = `${classPrefix}__${name}-container`;
117
- if (circledCitationIndex) {
118
- className += ` miso-circled-citation-index`;
171
+ }, body);
172
+ }
173
+ function element(tag) {
174
+ let attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
175
+ let body = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
176
+ if (typeof attributes === 'string' || Array.isArray(attributes)) {
177
+ body = attributes;
178
+ attributes = {};
119
179
  }
120
- const attributes = [` class="${className}"`, ` logo="${logo ? 'true' : 'false'}"`];
121
- if (visibleWhen) {
122
- attributes.push(` visible-when="${visibleWhen}"`);
180
+ return `<${tag}${attrs(attributes)}>${Array.isArray(body) ? body.join('') : body}</${tag}>`;
181
+ }
182
+ function attrs() {
183
+ let _ref7 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
184
+ let {
185
+ classes = []
186
+ } = _ref7,
187
+ attributes = _objectWithoutPropertiesLoose(_ref7, _excluded2);
188
+ let str = ``;
189
+ if (classes.length) {
190
+ str += ` class="${classes.join(' ')}"`;
123
191
  }
124
- if (parentQuestionId) {
125
- attributes.push(` parent-question-id="${parentQuestionId}"`);
192
+ for (const name in attributes) {
193
+ const value = attributes[name];
194
+ if (value !== undefined) {
195
+ str += ` ${lowerCamelToKebab(name)}="${value}"`;
196
+ }
126
197
  }
127
- return `<miso-ask${attributes.join('')}>`;
198
+ return str;
199
+ }
200
+ function kebabToLowerCamel(str) {
201
+ return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
202
+ }
203
+ function lowerCamelToKebab(str) {
204
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
128
205
  }
@@ -27,7 +27,6 @@ class MisoComponentElement extends HTMLElement {
27
27
  });
28
28
  }
29
29
  }
30
-
31
30
  get role() {
32
31
  return this._role;
33
32
  }
@@ -95,7 +95,6 @@ class SearchBoxLayout extends _template.default {
95
95
  this._contexts = new WeakMap();
96
96
  this._suggestionItems = new WeakMap(); // TODO: put them on elements as layout can be potentially replaced
97
97
  }
98
-
99
98
  initialize(view) {
100
99
  const {
101
100
  autocomplete
@@ -90,7 +90,6 @@ class CarouselLayout extends _collection.default {
90
90
  if (this._itemCount === undefined) {
91
91
  return; // data not ready yet
92
92
  }
93
-
94
93
  const grid = this._findGridElement();
95
94
  if (!grid) {
96
95
  return;
@@ -214,7 +214,8 @@ class CollectionLayout extends _template.default {
214
214
  this._view._events.emit('click', {
215
215
  session,
216
216
  value,
217
- element
217
+ element,
218
+ domEvent: event
218
219
  });
219
220
  }
220
221
  destroy() {
@@ -73,7 +73,6 @@ class RadioLayout extends _template.default {
73
73
  if (states.rendered) {
74
74
  controls.skip(); // only render once
75
75
  }
76
-
77
76
  return super._preprocess(states, controls);
78
77
  }
79
78
  _handleClick(_ref2) {
@@ -85,7 +85,6 @@ class RafLayout {
85
85
  if (!this._requested) {
86
86
  return; // just in case
87
87
  }
88
-
89
88
  this._requested = false;
90
89
  this._applyRender();
91
90
  }
@@ -94,7 +93,6 @@ class RafLayout {
94
93
  if (!this._pending) {
95
94
  return; // just in case
96
95
  }
97
-
98
96
  const timestamp = Date.now();
99
97
  let [state, {
100
98
  notifyUpdate
@@ -64,7 +64,6 @@ function productInfoBlock(_ref, _ref2) {
64
64
  content += `<hr><div class="${className}__item-price">${price}</div>`;
65
65
  // TODO: enhance this
66
66
  }
67
-
68
67
  return `<div class="${className}__item-info-container">${content}</div>`;
69
68
  }
70
69
  function articleInfoBlock(_ref3, _ref4) {
@@ -55,7 +55,6 @@ class UiPlugin extends _commons.Component {
55
55
  for (const LayoutClass of LayoutClasses) {
56
56
  LayoutClass.MisoClient = MisoClient; // TODO: find better way
57
57
  }
58
-
59
58
  for (const LayoutClass of LayoutClasses) {
60
59
  this.layouts.register(LayoutClass);
61
60
  }
@@ -156,7 +155,6 @@ class Ui {
156
155
  get recommendation() {
157
156
  return this.recommendations.get(); // get default recommendation unit
158
157
  }
159
-
160
158
  get search() {
161
159
  return this._search || (this._search = new _index.Search(this._plugin, this._client));
162
160
  }
@@ -14,7 +14,6 @@ function fallbackListFunction(keyAttrName) {
14
14
  }
15
15
  }));
16
16
  }
17
-
18
17
  class Bindings {
19
18
  constructor() {
20
19
  let {
@@ -72,7 +72,6 @@ class ProxyElement {
72
72
  }
73
73
  }
74
74
  }
75
-
76
75
  _offAllFromCurrentElement() {
77
76
  if (this._element) {
78
77
  for (const {
@@ -37,7 +37,6 @@ class States {
37
37
  this.sessionId = sessionId;
38
38
  this._states = new Map(); // productId -> {impression, viewable, click}
39
39
  }
40
-
41
40
  getFullState(productId) {
42
41
  // when we receive a bind event, impression is trigger, making an entry here
43
42
  // so if the state is not found, the product is not present yet => all untracked
@@ -42,7 +42,6 @@ function normalizeOptions(options) {
42
42
  if (options === false) {
43
43
  return false; // turn off all tracking
44
44
  }
45
-
46
45
  if (options === true) {
47
46
  options = {};
48
47
  }
@@ -4,4 +4,4 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _default = exports.default = '1.9.1-beta.0';
7
+ var _default = exports.default = '1.9.1-beta.2';
@@ -56,7 +56,6 @@ const DEFAULT_TRACKERS = Object.freeze({
56
56
  }
57
57
  }
58
58
  });
59
-
60
59
  const DEFAULT_OPTIONS = Object.freeze({
61
60
  api: DEFAULT_API_OPTIONS,
62
61
  layouts: DEFAULT_LAYOUTS,
@@ -94,7 +93,6 @@ class Ask extends _base.default {
94
93
  context._events.emit('create', this);
95
94
  this.reset(); // create a session so query suggestions can be tracked
96
95
  }
97
-
98
96
  get questionId() {
99
97
  return this._questionId;
100
98
  }
@@ -16,7 +16,6 @@ class Asks extends _context.default {
16
16
  this._byPqid = new Map();
17
17
  this._root = new _ask.default(this); // initialize with default workflow
18
18
  }
19
-
20
19
  get root() {
21
20
  return this._root;
22
21
  }
@@ -56,7 +55,6 @@ class Asks extends _context.default {
56
55
  if (events === undefined) {
57
56
  events = !!root; // events are default to be true only when root = true
58
57
  }
59
-
60
58
  if (events) {
61
59
  this._events.clear();
62
60
  }
@@ -134,7 +134,6 @@ class Workflow extends _commons.Component {
134
134
  if (session.uuid !== this.session.uuid) {
135
135
  return; // ignore data for old session or inactive session
136
136
  }
137
-
138
137
  this._sessions.start(); // in case session not started yet
139
138
 
140
139
  data = this._defaultProcessData(data);
@@ -20,7 +20,8 @@ const DEFAULT_API_OPTIONS = Object.freeze({
20
20
  const DEFAULT_LAYOUTS = Object.freeze({
21
21
  [_constants.ROLE.RELATED_QUESTIONS]: [_index2.ListLayout.type, {
22
22
  itemType: 'question'
23
- }]
23
+ }],
24
+ [_constants.ROLE.QUERY]: [_index2.SearchBoxLayout.type]
24
25
  });
25
26
  const DEFAULT_TRACKERS = Object.freeze({
26
27
  [_constants.ROLE.RELATED_QUESTIONS]: {}
@@ -41,7 +42,7 @@ class Explore extends _base.default {
41
42
  });
42
43
  this._productId = undefined;
43
44
  this._linkFn = undefined;
44
- this._unsubscribes.push(this._views.get(_constants.ROLE.RELATED_QUESTIONS).on('click', event => this._handleQuestionClick(event)));
45
+ this._unsubscribes = [...this._unsubscribes, this._views.get(_constants.ROLE.RELATED_QUESTIONS).on('click', event => this._handleRelatedQuestionClick(event)), this._hub.on(_index.fields.query(), payload => this.query(payload))];
45
46
  }
46
47
  set productId(value) {
47
48
  console.warning('DEPRECATED: use useApi() instead');
@@ -102,7 +103,7 @@ class Explore extends _base.default {
102
103
  })
103
104
  });
104
105
  }
105
- _handleQuestionClick(_ref) {
106
+ _handleRelatedQuestionClick(_ref) {
106
107
  let {
107
108
  value: question
108
109
  } = _ref,
@@ -111,5 +112,15 @@ class Explore extends _base.default {
111
112
  question
112
113
  })));
113
114
  }
115
+ query() {
116
+ let {
117
+ q
118
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
119
+ if (!this._linkFn) {
120
+ return;
121
+ }
122
+ const url = this._linkFn(q);
123
+ window.open(url, '_blank');
124
+ }
114
125
  }
115
126
  exports.default = Explore;
package/package.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
27
27
  },
28
28
  "dependencies": {
29
- "@miso.ai/commons": "1.9.1-beta.0"
29
+ "@miso.ai/commons": "1.9.1-beta.2"
30
30
  },
31
- "version": "1.9.1-beta.0"
31
+ "version": "1.9.1-beta.2"
32
32
  }
@@ -1,24 +1,17 @@
1
1
  import { defineValues } from '@miso.ai/commons';
2
- import { PREFIX } from './constants.js';
2
+ import { constants } from '../../defaults/ask/index.js';
3
3
 
4
4
  export default class AskComboElements {
5
5
 
6
- constructor(root) {
6
+ constructor(root, { classPrefix = constants.CLASS_PREFIX } = {}) {
7
7
  if (!root) {
8
8
  throw new Error('Root element is required');
9
9
  }
10
10
  defineValues(this, {
11
11
  root,
12
- followUpsSection: root.querySelector(`#${PREFIX}__follow-ups`),
13
- relatedResourcesContainer: root.querySelector(`#${PREFIX}__related-resources miso-ask`),
12
+ followUpsSection: root.querySelector(`.${classPrefix}__follow-ups`),
13
+ relatedResourcesContainer: root.querySelector(`.${classPrefix}__related-resources miso-ask`),
14
14
  });
15
-
16
- if (!this.followUpsSection) {
17
- throw new Error(`Follow-ups section element not found.`);
18
- }
19
- if (!this.relatedResourcesContainer) {
20
- throw new Error(`Related resources container element not found.`);
21
- }
22
15
  }
23
16
 
24
17
  }
@@ -45,7 +45,7 @@ export default class AskCombo extends Combo {
45
45
 
46
46
  async _start() {
47
47
  await waitForDomContentLoaded();
48
- await this.MisoClient.cmdDone;
48
+ await this.waitForElement();
49
49
 
50
50
  // setup MisoClient
51
51
  await this._setupMisoClient();
@@ -89,7 +89,7 @@ export default class AskCombo extends Combo {
89
89
  const { element, resolvedOptions: options } = this;
90
90
  const { templates = {} } = options;
91
91
  element.innerHTML = templates.root(options);
92
- this._elements = new AskComboElements(element);
92
+ this._elements = new AskComboElements(element, options);
93
93
  }
94
94
 
95
95
  async _createClientInstance() {
@@ -98,30 +98,33 @@ export default class AskCombo extends Combo {
98
98
 
99
99
  async _setupWorkflows() {
100
100
  const { client, elements, resolvedOptions: options } = this;
101
- const { templates = {} } = options;
101
+ const { templates = {}, features = {} } = options;
102
102
 
103
103
  const context = client.ui.asks;
104
104
  const rootWorkflow = client.ui.ask;
105
105
 
106
- // TODO: set placeholder is present in options
107
-
108
- // when a answer is fully populated, insert a new section for the follow-up question
109
- context.on('done', ({ workflow }) => {
110
- elements.followUpsSection.insertAdjacentHTML('beforeend', templates.followUp({ ...options, parentQuestionId: workflow.questionId }));
111
- });
112
-
113
- // when a new query starts, associate the last section container (for related resources) to that workflow
114
- context.on('loading', ({ workflow }) => {
115
- elements.relatedResourcesContainer.workflow = workflow;
116
- });
106
+ // TODO: set placeholder if present in options
107
+
108
+ if (elements.followUpsSection && features.followUpQuestions !== false) {
109
+ // when a answer is fully populated, insert a new section for the follow-up question
110
+ context.on('done', ({ workflow }) => {
111
+ elements.followUpsSection.insertAdjacentHTML('beforeend', templates.followUp({ ...options, parentQuestionId: workflow.questionId }));
112
+ });
113
+ // if user starts over, clean up current follow-up questions
114
+ rootWorkflow.on('loading', () => {
115
+ // clean up the entire follow-ups section
116
+ elements.followUpsSection.innerHTML = '';
117
+ // destroy all follow-up workflows
118
+ context.reset({ root: false });
119
+ });
120
+ }
117
121
 
118
- // if user starts over, clean up current follow-up questions
119
- rootWorkflow.on('loading', () => {
120
- // clean up the entire follow-ups section
121
- elements.followUpsSection.innerHTML = '';
122
- // destroy all follow-up workflows
123
- context.reset({ root: false });
124
- });
122
+ if (elements.relatedResourcesContainer && features.relatedResources !== false && features.followUpQuestions !== false) {
123
+ // when a new query starts, associate the last section container (for related resources) to that workflow
124
+ context.on('loading', ({ workflow }) => {
125
+ elements.relatedResourcesContainer.workflow = workflow;
126
+ });
127
+ }
125
128
 
126
129
  // start query if specified in URL
127
130
  rootWorkflow.autoQuery();
package/src/combo/base.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Component } from '@miso.ai/commons';
1
+ import { Component, Resolution } from '@miso.ai/commons';
2
2
 
3
3
  export default class Combo extends Component {
4
4
 
@@ -23,8 +23,14 @@ export default class Combo extends Component {
23
23
  return this._element;
24
24
  }
25
25
 
26
+ async waitForElement() {
27
+ return this._element || (this._elementRes || (this._elementRes = new Resolution())).promise;
28
+ }
29
+
26
30
  set element(element) {
31
+ // TODO: guard against multiple element assignment
27
32
  this._element = element;
33
+ this._elementRes && this._elementRes.resolve(element);
28
34
  }
29
35
 
30
36
  start(options) {
@@ -1,74 +1,95 @@
1
1
  import { CLASS_PREFIX } from './constants.js';
2
2
  import * as DEFAULT_PHRASES from './phrases.js';
3
3
 
4
- // TODO: refactor
5
- // TODO: make ids constants
6
4
  // TODO: placeholder
7
5
 
8
6
  export function root(options = {}) {
9
- const { classPrefix = CLASS_PREFIX } = options;
10
- return `
11
- <section id="${classPrefix}__question" class="${classPrefix}__section ${classPrefix}__question">
12
- <miso-ask class="${classPrefix}__query-container">
13
- <miso-query></miso-query>
14
- </miso-ask>
15
- </section>
16
- <section class="${classPrefix}__section ${classPrefix}__answer">
17
- ${answer(options)}
18
- ${sources(options)}
19
- <miso-ask class="${classPrefix}__bottom-spacing-container" visible-when="ongoing"></miso-ask>
20
- </section>
21
- <div id="${classPrefix}__follow-ups" class="${classPrefix}__follow-ups"></div>
22
- <section id="${classPrefix}__related-resources" class="${classPrefix}__section ${classPrefix}__related-resources">
23
- <miso-ask visible-when="nonempty" logo="true">
24
- <h2 class="${classPrefix}__phrase ${classPrefix}__related-resources-phrase">${phraseText(options, 'relatedResources')}</h2>
25
- <miso-related-resources></miso-related-resources>
26
- </miso-ask>
27
- </section>`.trim();
7
+ options = normalizeOptions(options);
8
+ return [
9
+ section(options, { name: 'question' }, [
10
+ container(options, { name: 'query' }, '<miso-query></miso-query>'),
11
+ ]),
12
+ section(options, { name: 'answer' }, answerGroup(options)),
13
+ followUps(options),
14
+ relatedResources(options),
15
+ ].join('');
28
16
  };
29
17
 
30
18
  export function followUp(options = {}) {
31
- const { classPrefix = CLASS_PREFIX, parentQuestionId } = options;
32
- return `
33
- <section class="${classPrefix}__section ${classPrefix}__follow-up">
34
- <miso-ask class="${classPrefix}__query-suggestions-container" visible-when="initial+nonempty" parent-question-id="${parentQuestionId}">
35
- <h3 class="${classPrefix}__phrase ${classPrefix}__related-questions-phrase">${phraseText(options, 'relatedQuestions')}</h3>
36
- <miso-query-suggestions></miso-query-suggestions>
37
- </miso-ask>
38
- <miso-ask class="${classPrefix}__query-container" visible-when="initial loading" parent-question-id="${parentQuestionId}">
39
- <miso-query></miso-query>
40
- </miso-ask>
41
- ${answer(options)}
42
- ${sources(options)}
43
- <miso-ask class="${classPrefix}__bottom-spacing-container" visible-when="ongoing" parent-question-id="${parentQuestionId}"></miso-ask>
44
- </section>`.trim();
19
+ options = normalizeOptions(options);
20
+ return section(options, { name: 'follow-up' }, [
21
+ querySuggestions(options),
22
+ container(options, { name: 'query', visibleWhen: 'initial loading' }, '<miso-query></miso-query>'),
23
+ ...answerGroup(options),
24
+ ]);
45
25
  };
46
26
 
47
27
 
48
28
 
49
- // helpers //
29
+ // options //
30
+ function normalizeOptions({
31
+ classPrefix = CLASS_PREFIX,
32
+ circledCitationIndex = true,
33
+ ...options
34
+ } = {}) {
35
+ return { classPrefix, circledCitationIndex, ...options };
36
+ }
37
+
38
+ // sections & containers //
39
+ function followUps(options) {
40
+ const { classPrefix, features = {} } = options;
41
+ return features.followUpQuestions === false ? '' : `<div id="${classPrefix}__follow-ups" class="${classPrefix}__follow-ups"></div>`;
42
+ }
43
+
44
+ function relatedResources(options) {
45
+ const { features = {}, logo = true } = options;
46
+ const body = features.relatedResources === false ? '' : container(options, { name: 'related-resources', visibleWhen: 'nonempty', logo }, [
47
+ phrase(options, { name: 'related-resources', tag: 'h2' }),
48
+ '<miso-related-resources></miso-related-resources>',
49
+ ]);
50
+ return section(options, { name: 'related-resources' }, body);
51
+ }
52
+
53
+ function querySuggestions(options) {
54
+ const { features = {} } = options;
55
+ return features.querySuggestions === false ? '' :
56
+ container(options, { name: 'query-suggestions', visibleWhen: 'initial+nonempty' }, [
57
+ phrase(options, { name: 'related-questions', tag: 'h3' }),
58
+ '<miso-query-suggestions></miso-query-suggestions>',
59
+ ]);
60
+ }
61
+
62
+ function answerGroup(options) {
63
+ return [
64
+ answer(options),
65
+ sources(options),
66
+ container(options, { name: 'bottom-spacing', visibleWhen: 'ongoing' }),
67
+ ];
68
+ }
69
+
50
70
  function answer(options) {
51
- const { classPrefix = CLASS_PREFIX } = options;
52
- return `
53
- ${containerOpenTag('answer', { ...options, visibleWhen: 'ready' })}
54
- ${ options.parentQuestionId ? '<hr>' : '' }
55
- <div class="${classPrefix}__phrase ${classPrefix}__question-phrase">${phraseText(options, 'question')}</div>
56
- <miso-question></miso-question>
57
- <miso-answer></miso-answer>
58
- <miso-feedback></miso-feedback>
59
- </miso-ask>`.trim();
71
+ return container(options, { name: 'answer', visibleWhen: 'ready' }, [
72
+ options.parentQuestionId ? '<hr>' : '',
73
+ phrase(options, { name: 'question', tag: 'div' }),
74
+ '<miso-question></miso-question>',
75
+ '<miso-answer></miso-answer>',
76
+ '<miso-feedback></miso-feedback>',
77
+ ]);
60
78
  };
61
79
 
62
80
  function sources(options) {
63
- const { classPrefix = CLASS_PREFIX } = options;
64
- return `
65
- ${containerOpenTag('sources', { ...options, visibleWhen: 'nonempty' })}
66
- <hr>
67
- <h3 class="${classPrefix}__phrase ${classPrefix}__sources-phrase">${phraseText(options, 'sources')}</h3>
68
- <miso-sources></miso-sources>
69
- </miso-ask>`.trim();
81
+ return container(options, { name: 'sources', visibleWhen: 'nonempty' }, [
82
+ '<hr>',
83
+ phrase(options, { name: 'sources', tag: 'h3' }),
84
+ '<miso-sources></miso-sources>',
85
+ ]);
70
86
  };
71
87
 
88
+ function phrase(options, { name, tag = 'div' }) {
89
+ const { classPrefix } = options;
90
+ return `<${tag} class="${classPrefix}__phrase ${classPrefix}__${name}-phrase">${phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
91
+ }
92
+
72
93
  function phraseText(options, key) {
73
94
  const { phrases = {} } = options;
74
95
  const fn = phrases[key] || DEFAULT_PHRASES[key];
@@ -82,26 +103,52 @@ function phraseText(options, key) {
82
103
  }
83
104
  }
84
105
 
85
- function containerOpenTag(name, {
106
+ function section({ classPrefix }, { name }, body) {
107
+ return element('section', {
108
+ classes: [`${classPrefix}__section`, `${classPrefix}__${name}`],
109
+ }, body);
110
+ }
111
+
112
+ function container({
113
+ classPrefix,
114
+ circledCitationIndex,
115
+ parentQuestionId,
116
+ }, {
117
+ name,
86
118
  visibleWhen,
87
- classPrefix = CLASS_PREFIX,
88
119
  logo = false,
89
- circledCitationIndex = true,
90
- parentQuestionId,
91
- }) {
92
- let className = `${classPrefix}__${name}-container`;
93
- if (circledCitationIndex) {
94
- className += ` miso-circled-citation-index`;
120
+ }, body) {
121
+ const classes = [`${classPrefix}__${name}-container`];
122
+ circledCitationIndex && classes.push('miso-circled-citation-index');
123
+ return element('miso-ask', { classes, visibleWhen, logo, parentQuestionId }, body);
124
+ }
125
+
126
+ function element(tag, attributes = {}, body = '') {
127
+ if (typeof attributes === 'string' || Array.isArray(attributes)) {
128
+ body = attributes;
129
+ attributes = {};
95
130
  }
96
- const attributes = [
97
- ` class="${className}"`,
98
- ` logo="${logo ? 'true' : 'false'}"`,
99
- ];
100
- if (visibleWhen) {
101
- attributes.push(` visible-when="${visibleWhen}"`);
131
+ return `<${tag}${attrs(attributes)}>${Array.isArray(body) ? body.join('') : body}</${tag}>`;
132
+ }
133
+
134
+ function attrs({ classes = [], ...attributes } = {}) {
135
+ let str = ``;
136
+ if (classes.length) {
137
+ str += ` class="${classes.join(' ')}"`;
102
138
  }
103
- if (parentQuestionId) {
104
- attributes.push(` parent-question-id="${parentQuestionId}"`);
139
+ for (const name in attributes) {
140
+ const value = attributes[name];
141
+ if (value !== undefined) {
142
+ str += ` ${lowerCamelToKebab(name)}="${value}"`;
143
+ }
105
144
  }
106
- return `<miso-ask${attributes.join('')}>`;
145
+ return str;
146
+ }
147
+
148
+ function kebabToLowerCamel(str) {
149
+ return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
150
+ }
151
+
152
+ function lowerCamelToKebab(str) {
153
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
107
154
  }
@@ -176,7 +176,7 @@ export default class CollectionLayout extends TemplateBasedLayout {
176
176
  }
177
177
  const value = element[VALUE];
178
178
  const { session } = this._view._state;
179
- this._view._events.emit('click', { session, value, element });
179
+ this._view._events.emit('click', { session, value, element, domEvent: event });
180
180
  }
181
181
 
182
182
  destroy() {
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.9.1-beta.0';
1
+ export default '1.9.1-beta.2';
@@ -3,7 +3,7 @@ import Workflow from './base.js';
3
3
  import { mergeApiOptions } from './options.js';
4
4
  import { fields } from '../actor/index.js';
5
5
  import { ROLE } from '../constants.js';
6
- import { ListLayout } from '../layout/index.js';
6
+ import { ListLayout, SearchBoxLayout } from '../layout/index.js';
7
7
 
8
8
  const DEFAULT_API_OPTIONS = Object.freeze({
9
9
  group: API.GROUP.ASK,
@@ -12,6 +12,7 @@ const DEFAULT_API_OPTIONS = Object.freeze({
12
12
 
13
13
  const DEFAULT_LAYOUTS = Object.freeze({
14
14
  [ROLE.RELATED_QUESTIONS]: [ListLayout.type, { itemType: 'question' }],
15
+ [ROLE.QUERY]: [SearchBoxLayout.type],
15
16
  });
16
17
 
17
18
  const DEFAULT_TRACKERS = Object.freeze({
@@ -36,7 +37,12 @@ export default class Explore extends Workflow {
36
37
  });
37
38
  this._productId = undefined;
38
39
  this._linkFn = undefined;
39
- this._unsubscribes.push(this._views.get(ROLE.RELATED_QUESTIONS).on('click', event => this._handleQuestionClick(event)));
40
+
41
+ this._unsubscribes = [
42
+ ...this._unsubscribes,
43
+ this._views.get(ROLE.RELATED_QUESTIONS).on('click', event => this._handleRelatedQuestionClick(event)),
44
+ this._hub.on(fields.query(), payload => this.query(payload)),
45
+ ];
40
46
  }
41
47
 
42
48
  set productId(value) {
@@ -89,8 +95,16 @@ export default class Explore extends Workflow {
89
95
  };
90
96
  }
91
97
 
92
- _handleQuestionClick({ value: question, ...event }) {
98
+ _handleRelatedQuestionClick({ value: question, ...event }) {
93
99
  this._events.emit('select', Object.freeze({ ...event, question }));
94
100
  }
95
101
 
102
+ query({ q } = {}) {
103
+ if (!this._linkFn) {
104
+ return;
105
+ }
106
+ const url = this._linkFn(q);
107
+ window.open(url, '_blank');
108
+ }
109
+
96
110
  }
@@ -1,7 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.PREFIX = void 0;
7
- const PREFIX = exports.PREFIX = 'miso-ask-combo';
@@ -1 +0,0 @@
1
- export const PREFIX = 'miso-ask-combo';