@miso.ai/client-sdk-ui 1.9.10 → 1.10.0-beta.1
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/autocomplete.js +157 -0
- package/dist/cjs/actor/data.js +3 -11
- package/dist/cjs/actor/fields.js +8 -0
- package/dist/cjs/actor/index.js +7 -0
- package/dist/cjs/actor/utils.js +10 -0
- package/dist/cjs/defaults/ask/templates.js +21 -0
- package/dist/cjs/layout/input/search-box.js +213 -127
- package/dist/cjs/layout/list/collection.js +6 -1
- package/dist/cjs/layout/templates.js +50 -35
- package/dist/cjs/layout/text/typewriter/index.js +8 -53
- package/dist/cjs/source/index.js +20 -8
- package/dist/cjs/styles.js +4 -83
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/ask.js +48 -16
- package/dist/cjs/workflow/base.js +1 -2
- package/dist/cjs/workflow/explore.js +4 -1
- package/dist/cjs/workflow/options.js +54 -13
- package/dist/cjs/workflow/search.js +35 -7
- package/package.json +2 -2
- package/src/actor/autocomplete.js +131 -0
- package/src/actor/data.js +3 -10
- package/src/actor/fields.js +8 -0
- package/src/actor/index.js +1 -0
- package/src/actor/utils.js +8 -0
- package/src/defaults/ask/templates.js +21 -0
- package/src/layout/input/search-box.js +186 -94
- package/src/layout/list/collection.js +6 -1
- package/src/layout/templates.js +31 -25
- package/src/layout/text/typewriter/index.js +6 -46
- package/src/source/index.js +14 -1
- package/src/styles.js +4 -86
- package/src/version.js +1 -1
- package/src/workflow/ask.js +31 -1
- package/src/workflow/base.js +1 -2
- package/src/workflow/explore.js +1 -1
- package/src/workflow/options.js +49 -12
- package/src/workflow/search.js +33 -1
package/src/layout/templates.js
CHANGED
|
@@ -16,34 +16,33 @@ export function unimplemented(name) {
|
|
|
16
16
|
|
|
17
17
|
export function product(layout, state, data, meta) {
|
|
18
18
|
const { templates } = layout;
|
|
19
|
-
const [openTag, closeTag] =
|
|
19
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
20
20
|
return [
|
|
21
21
|
openTag,
|
|
22
|
-
(templates.imageBlock ||
|
|
23
|
-
(templates.infoBlock || productInfoBlock)(layout, data, meta),
|
|
24
|
-
(templates.indexBlock || renderIndexBlock)(layout, data, meta),
|
|
22
|
+
(templates.imageBlock || imageBlock)(layout, data, meta),
|
|
23
|
+
(templates.infoBlock || templates.productInfoBlock || productInfoBlock)(layout, data, meta),
|
|
25
24
|
closeTag,
|
|
26
25
|
].join('');
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
export function question(layout, state, data) {
|
|
30
|
-
const [openTag, closeTag] =
|
|
29
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
31
30
|
return `${openTag}${data.value || data.text || data}${closeTag}`;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export function article(layout, state, data, meta) {
|
|
35
34
|
const { templates } = layout;
|
|
36
|
-
const [openTag, closeTag] =
|
|
35
|
+
const [openTag, closeTag] = tagPair(layout, data);
|
|
37
36
|
return [
|
|
38
37
|
openTag,
|
|
39
|
-
(templates.imageBlock ||
|
|
40
|
-
(templates.infoBlock || articleInfoBlock)(layout, data, meta),
|
|
41
|
-
(templates.indexBlock ||
|
|
38
|
+
(templates.imageBlock || imageBlock)(layout, data, meta),
|
|
39
|
+
(templates.infoBlock || templates.articleInfoBlock || articleInfoBlock)(layout, data, meta),
|
|
40
|
+
(templates.indexBlock || indexBlock)(layout, data, meta),
|
|
42
41
|
closeTag,
|
|
43
42
|
].join('');
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
function productInfoBlock({ className }, { title, description, sale_price, original_price }) {
|
|
45
|
+
export function productInfoBlock({ className }, { title, description, sale_price, original_price }) {
|
|
47
46
|
let content = '';
|
|
48
47
|
if (title) {
|
|
49
48
|
content += `<div class="${className}__item-title">${escapeHtml(title)}</div>`;
|
|
@@ -59,14 +58,14 @@ function productInfoBlock({ className }, { title, description, sale_price, origi
|
|
|
59
58
|
return `<div class="${className}__item-info-container">${content}</div>`;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
function articleInfoBlock({ className, templates }, { title, snippet, description, created_at, updated_at, published_at }) {
|
|
61
|
+
export function articleInfoBlock({ className, templates }, { title, snippet, description, created_at, updated_at, published_at }) {
|
|
63
62
|
const date = published_at || created_at || updated_at;
|
|
64
63
|
let content = '';
|
|
65
64
|
if (title) {
|
|
66
65
|
content += `<div class="${className}__item-title">${escapeHtml(title)}</div>`;
|
|
67
66
|
}
|
|
68
67
|
if (date) {
|
|
69
|
-
content += `<div class="${className}__item-date">${
|
|
68
|
+
content += `<div class="${className}__item-date">${formatDate(date, templates.date)}</div>`;
|
|
70
69
|
}
|
|
71
70
|
if (snippet) {
|
|
72
71
|
content += `<div class="${className}__item-snippet">${snippet}</div>`;
|
|
@@ -76,17 +75,7 @@ function articleInfoBlock({ className, templates }, { title, snippet, descriptio
|
|
|
76
75
|
return `<div class="${className}__item-info-container">${content}</div>`;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
function renderTagPair({ className, options = {} }, { product_id, url }) {
|
|
81
|
-
const { link = {} } = options;
|
|
82
|
-
const { target = '_blank', rel = 'noopener' } = link; // TODO: other properties
|
|
83
|
-
const tag = url ? 'a' : 'div';
|
|
84
|
-
const urlAttrs = url ? `href="${url}" target="${target}" rel="${rel}"` : '';
|
|
85
|
-
const productAttrs = product_id ? `${ATTR_DATA_MISO_PRODUCT_ID}="${product_id}"` : '';
|
|
86
|
-
return [`<${tag} class="${className}__item-body" data-role="item" ${productAttrs} ${urlAttrs}>`, `</${tag}>`];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function renderImageBlock({ className }, { cover_image }) {
|
|
78
|
+
export function imageBlock({ className }, { cover_image }) {
|
|
90
79
|
if (!cover_image) {
|
|
91
80
|
return '';
|
|
92
81
|
}
|
|
@@ -94,14 +83,25 @@ function renderImageBlock({ className }, { cover_image }) {
|
|
|
94
83
|
return `<div class="${className}__item-cover-image-container">${img}</div>`;
|
|
95
84
|
}
|
|
96
85
|
|
|
97
|
-
function
|
|
86
|
+
export function indexBlock({ className }, data, { index }) {
|
|
98
87
|
const i = index + 1;
|
|
99
88
|
return `<div class="${className}__item-index-container"><span class="${className}__item-index miso-citation-index" data-index="${i}"></span></div>`;
|
|
100
89
|
}
|
|
101
90
|
|
|
91
|
+
// helpers //
|
|
92
|
+
function tagPair({ className, options = {} }, { product_id, url }, { classSuffix = 'item-body', role = 'item' } = {}) {
|
|
93
|
+
const { link = {} } = options;
|
|
94
|
+
const { target = '_blank', rel = 'noopener' } = link; // TODO: other properties
|
|
95
|
+
const tag = url ? 'a' : 'div';
|
|
96
|
+
const urlAttrs = url ? `href="${url}" target="${target}" rel="${rel}"` : '';
|
|
97
|
+
const productAttrs = product_id ? `${ATTR_DATA_MISO_PRODUCT_ID}="${product_id}"` : '';
|
|
98
|
+
const roleAttrs = role ? `data-role="${role}"` : '';
|
|
99
|
+
return [`<${tag} class="${className}__${classSuffix}" ${roleAttrs} ${productAttrs} ${urlAttrs}>`, `</${tag}>`];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
102
|
const DEFAULT_DATE_OPTIONS = Object.freeze({ locale: 'en-US', year: 'numeric', month: 'short', day: 'numeric' });
|
|
103
103
|
|
|
104
|
-
function
|
|
104
|
+
function formatDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
105
105
|
switch (typeof fn) {
|
|
106
106
|
case 'function':
|
|
107
107
|
return fn(date);
|
|
@@ -113,3 +113,9 @@ function renderDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
|
113
113
|
throw new Error(`Invalid date format: ${fn}`);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
export const helpers = Object.freeze({
|
|
118
|
+
tagPair,
|
|
119
|
+
formatDate,
|
|
120
|
+
escapeHtml,
|
|
121
|
+
});
|
|
@@ -72,63 +72,23 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
72
72
|
async _setupForMarkdown() {
|
|
73
73
|
const context = await this._view._views._extensions.require('markdown');
|
|
74
74
|
const cursorClass = cursorClassName(this.className);
|
|
75
|
-
const { onDebug } = this.options;
|
|
75
|
+
const { onDebug, onCitationLink } = this.options;
|
|
76
76
|
// TODO: options
|
|
77
77
|
this._renderer = context.createRenderer({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
onDone: (element) => {
|
|
83
|
-
element.classList.add('done');
|
|
84
|
-
},
|
|
78
|
+
cursorClass,
|
|
79
|
+
getSource: index => this._getSource(index),
|
|
80
|
+
onCitationLink,
|
|
85
81
|
onDebug,
|
|
86
|
-
onCitationLink: this._handleCitationLink.bind(this),
|
|
87
82
|
});
|
|
88
83
|
// capture citation link click if necessary
|
|
89
84
|
this._unsubscribes.push(this._view.proxyElement.on('click', (e) => this._handleClick(e)));
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
const { onCitationLink } = this.options;
|
|
94
|
-
if (typeof onCitationLink !== 'function') {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const { data = {} } = this._view._data;
|
|
98
|
-
const { sources = [] } = data;
|
|
99
|
-
const source = sources[index];
|
|
100
|
-
try {
|
|
101
|
-
onCitationLink(methods, { source, index });
|
|
102
|
-
} catch (e) {
|
|
103
|
-
console.error(e);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/*
|
|
108
|
-
_handleCitationTooltip({ node, tooltip, index }) {
|
|
109
|
-
const { tooltip: tooltipOptions } = this.options;
|
|
110
|
-
if (!tooltipOptions || !tooltip || tooltip.properties['data-value']) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
87
|
+
_getSource(index) {
|
|
113
88
|
const { data = {} } = this._view._data;
|
|
114
89
|
const { sources = [] } = data;
|
|
115
|
-
|
|
116
|
-
if (!source) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
// TODO: allow HTML
|
|
120
|
-
let value;
|
|
121
|
-
try {
|
|
122
|
-
value = typeof tooltipOptions === 'function' ? tooltipOptions(source) : source.title;
|
|
123
|
-
} catch (e) {
|
|
124
|
-
console.error(e);
|
|
125
|
-
}
|
|
126
|
-
if (!value) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
tooltip.properties['data-value'] = escapeHtml(value);
|
|
90
|
+
return sources[index];
|
|
130
91
|
}
|
|
131
|
-
*/
|
|
132
92
|
|
|
133
93
|
async _setupForPlaintext() {
|
|
134
94
|
this._renderer = new PlaintextRenderer();
|
package/src/source/index.js
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { API } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
//export { default as api } from './api.js';
|
|
4
|
+
|
|
5
|
+
export function api(client) {
|
|
6
|
+
// TODO: send uuid & unit id
|
|
7
|
+
return async ({ group, name, payload, options }) => {
|
|
8
|
+
if (group === API.GROUP.ASK && name === API.NAME.QUESTIONS) {
|
|
9
|
+
return client.api.ask.questions(payload, options);
|
|
10
|
+
}
|
|
11
|
+
// because name is in snake case
|
|
12
|
+
return client.api[group]._run(name, payload, options);
|
|
13
|
+
};
|
|
14
|
+
}
|
package/src/styles.js
CHANGED
|
@@ -1,97 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { loadStyles, resolveCssUrl } from '@miso.ai/commons';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const NAME = 'ui';
|
|
4
4
|
|
|
5
5
|
let cssUrl;
|
|
6
6
|
let stylesLoaded;
|
|
7
7
|
|
|
8
8
|
export async function loadStylesIfNecessary() {
|
|
9
|
-
return stylesLoaded || (stylesLoaded = loadStyles());
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function loadStyles() {
|
|
13
|
-
try {
|
|
14
|
-
let link = getExistedLinkElement();
|
|
15
|
-
if (!link) {
|
|
16
|
-
link = document.createElement('link');
|
|
17
|
-
link.rel = 'stylesheet';
|
|
18
|
-
link.href = getCssUrl();
|
|
19
|
-
document.head.appendChild(link);
|
|
20
|
-
}
|
|
21
|
-
return waitForStylesApplied(link);
|
|
22
|
-
} catch(e) {
|
|
23
|
-
console.error(e);
|
|
24
|
-
}
|
|
9
|
+
return stylesLoaded || (stylesLoaded = loadStyles(getCssUrl()));
|
|
25
10
|
}
|
|
26
11
|
|
|
27
12
|
// helpers //
|
|
28
13
|
function getCssUrl() {
|
|
29
|
-
return cssUrl || (cssUrl = resolveCssUrl());
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function resolveCssUrl() {
|
|
33
|
-
// TODO: refactor with getPluginScriptUrl() in core
|
|
34
|
-
if (version === 'dev') {
|
|
35
|
-
const src = currentScript.src;
|
|
36
|
-
const searchIndex = src.indexOf('?');
|
|
37
|
-
const k = searchIndex < 0 ? src.length : searchIndex;
|
|
38
|
-
const i = src.lastIndexOf('/', k); // .../dist/umd/*.min.js
|
|
39
|
-
return `${src.slice(0, i - 4)}/css/ui.css`;
|
|
40
|
-
} else {
|
|
41
|
-
return `https://cdn.jsdelivr.net/npm/@miso.ai/client-sdk@${version}/dist/css/ui.css`;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getExistedLinkElement() {
|
|
46
|
-
return document.head.querySelector(`link[href="${getCssUrl()}"]`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function waitForStylesApplied(element) {
|
|
50
|
-
await waitForSheetAvailable(element);
|
|
51
|
-
await waitForSheetIncluded(element);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function waitForSheetAvailable(element) {
|
|
55
|
-
if (element.sheet) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
let intervalId;
|
|
60
|
-
element.onerror = () => {
|
|
61
|
-
intervalId && clearInterval(intervalId);
|
|
62
|
-
reject();
|
|
63
|
-
};
|
|
64
|
-
intervalId = setInterval(() => {
|
|
65
|
-
if (!element.sheet) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
intervalId && clearInterval(intervalId);
|
|
69
|
-
resolve();
|
|
70
|
-
}, 100);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function waitForSheetIncluded(element) {
|
|
75
|
-
if (isSheetIncluded(element)) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
return new Promise((resolve) => {
|
|
79
|
-
let intervalId;
|
|
80
|
-
intervalId = setInterval(() => {
|
|
81
|
-
if (!isSheetIncluded(element)) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
intervalId && clearInterval(intervalId);
|
|
85
|
-
resolve();
|
|
86
|
-
}, 100);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isSheetIncluded(element) {
|
|
91
|
-
for (const sheet of document.styleSheets) {
|
|
92
|
-
if (sheet.ownerNode === element) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
14
|
+
return cssUrl || (cssUrl = resolveCssUrl(NAME));
|
|
97
15
|
}
|
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.
|
|
1
|
+
export default '1.10.0-beta.1';
|
package/src/workflow/ask.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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
|
|
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
7
|
import { SearchBoxLayout, OptionListLayout, ListLayout, TextLayout, TypewriterLayout, FeedbackLayout } 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.ASK,
|
|
@@ -17,6 +19,14 @@ const DEFAULT_API_OPTIONS = Object.freeze({
|
|
|
17
19
|
},
|
|
18
20
|
});
|
|
19
21
|
|
|
22
|
+
const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
|
|
23
|
+
actor: false,
|
|
24
|
+
api: {
|
|
25
|
+
group: API.GROUP.ASK,
|
|
26
|
+
name: API.NAME.AUTOCOMPLETE,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
20
30
|
const DEFAULT_LAYOUTS = Object.freeze({
|
|
21
31
|
[ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
|
|
22
32
|
[ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
|
|
@@ -40,6 +50,7 @@ const DEFAULT_TRACKERS = Object.freeze({
|
|
|
40
50
|
|
|
41
51
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
42
52
|
api: DEFAULT_API_OPTIONS,
|
|
53
|
+
autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
|
|
43
54
|
layouts: DEFAULT_LAYOUTS,
|
|
44
55
|
trackers: DEFAULT_TRACKERS,
|
|
45
56
|
});
|
|
@@ -60,6 +71,13 @@ export default class Ask extends Workflow {
|
|
|
60
71
|
this._feedback = new FeedbackActor(this._hub);
|
|
61
72
|
this._setSuggestedQuestions();
|
|
62
73
|
|
|
74
|
+
/*
|
|
75
|
+
this._autocomplete = new AutocompleteActor(this._hub, {
|
|
76
|
+
source: sources.api(this._client),
|
|
77
|
+
options: this._options,
|
|
78
|
+
});
|
|
79
|
+
*/
|
|
80
|
+
|
|
63
81
|
this._unsubscribes = [
|
|
64
82
|
...this._unsubscribes,
|
|
65
83
|
this._hub.on(fields.query(), payload => this.query(payload)),
|
|
@@ -152,6 +170,17 @@ export default class Ask extends Workflow {
|
|
|
152
170
|
super.restart();
|
|
153
171
|
}
|
|
154
172
|
|
|
173
|
+
/*
|
|
174
|
+
updateCompletions(event) {
|
|
175
|
+
// TODO: verify
|
|
176
|
+
this._hub.update(fields.completions(), {
|
|
177
|
+
...event,
|
|
178
|
+
source: 'manual',
|
|
179
|
+
});
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
*/
|
|
183
|
+
|
|
155
184
|
_defaultProcessData(data) {
|
|
156
185
|
data = super._defaultProcessData(data);
|
|
157
186
|
const { value, request } = data;
|
|
@@ -259,6 +288,7 @@ export default class Ask extends Workflow {
|
|
|
259
288
|
this._context._byQid.delete(questionId);
|
|
260
289
|
}
|
|
261
290
|
this._feedback._destroy();
|
|
291
|
+
this._autocomplete._destroy();
|
|
262
292
|
super._destroy(options);
|
|
263
293
|
}
|
|
264
294
|
|
package/src/workflow/base.js
CHANGED
|
@@ -42,11 +42,10 @@ 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
50
|
this._interactions = new InteractionsActor(hub, { client, options });
|
|
52
51
|
this._trackers = new Trackers(hub, { views, options });
|
package/src/workflow/explore.js
CHANGED
|
@@ -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
|
|
package/src/workflow/options.js
CHANGED
|
@@ -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
|
}
|
|
@@ -105,6 +106,17 @@ export function normalizeInteractionsOptions(options) {
|
|
|
105
106
|
return { ...options, preprocess };
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
export function normalizeAutocompleteOptions(options) {
|
|
110
|
+
if (options === undefined) {
|
|
111
|
+
throw new Error(`Expect autocomplete options to be an object or a boolean value: ${options}`);
|
|
112
|
+
}
|
|
113
|
+
if (typeof options === 'boolean') {
|
|
114
|
+
options = { actor: options };
|
|
115
|
+
}
|
|
116
|
+
const processData = normalizeDataProcessorOptions(options.processData);
|
|
117
|
+
return { ...options, processData };
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
// merge //
|
|
109
121
|
export function mergeApiOptions(...optionsList) {
|
|
110
122
|
if (optionsList[optionsList.length - 1] === false) {
|
|
@@ -190,6 +202,17 @@ export function mergeInteractionsOptions(...optionsList) {
|
|
|
190
202
|
}));
|
|
191
203
|
}
|
|
192
204
|
|
|
205
|
+
export function mergeAutocompleteOptions(...optionsList) {
|
|
206
|
+
if (optionsList[optionsList.length - 1] === false) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
return mergeOptions(optionsList, (merged, options) => Object.assign(merged, {
|
|
210
|
+
...options,
|
|
211
|
+
api: mergeApiOptions(merged.api, options.api),
|
|
212
|
+
processData: mergeDataProcessorOptions(merged.processData, options.processData),
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
|
|
193
216
|
|
|
194
217
|
|
|
195
218
|
// manifest //
|
|
@@ -219,6 +242,11 @@ const FEATURES = [
|
|
|
219
242
|
normalize: normalizeInteractionsOptions,
|
|
220
243
|
merge: mergeInteractionsOptions,
|
|
221
244
|
},
|
|
245
|
+
{
|
|
246
|
+
key: 'autocomplete',
|
|
247
|
+
normalize: normalizeAutocompleteOptions,
|
|
248
|
+
merge: mergeAutocompleteOptions,
|
|
249
|
+
},
|
|
222
250
|
];
|
|
223
251
|
|
|
224
252
|
|
|
@@ -328,18 +356,27 @@ for (const { key, normalize, merge } of FEATURES) {
|
|
|
328
356
|
});
|
|
329
357
|
}
|
|
330
358
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
359
|
+
const DEFAULT_FEATURES = ['api', 'layouts', 'dataProcessor', 'trackers', 'interactions'];
|
|
360
|
+
|
|
361
|
+
export function makeConfigurable(prototype, features = DEFAULT_FEATURES) {
|
|
362
|
+
for (const feature of features) {
|
|
363
|
+
injectConfigurableFeature(prototype, feature);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function injectConfigurableFeature(prototype, feature) {
|
|
368
|
+
if (feature === 'api') {
|
|
369
|
+
Object.assign(prototype, {
|
|
370
|
+
useApi(...args) {
|
|
371
|
+
this._options.api.merge(args);
|
|
372
|
+
return this;
|
|
373
|
+
},
|
|
374
|
+
clearApi() {
|
|
375
|
+
this._options.api.set();
|
|
376
|
+
return this;
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
} else {
|
|
343
380
|
const upperCased = upperCase(feature);
|
|
344
381
|
Object.assign(prototype, {
|
|
345
382
|
[`use${upperCased}`](value) {
|
package/src/workflow/search.js
CHANGED
|
@@ -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
|
|
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']);
|