@miso.ai/client-sdk-ui 1.11.0-beta.9 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/actor/feedback.js +6 -39
- package/dist/cjs/actor/fields.js +14 -6
- package/dist/cjs/actor/index.js +0 -7
- package/dist/cjs/actor/interactions.js +4 -15
- package/dist/cjs/actor/sessions.js +2 -32
- package/dist/cjs/actor/tracker.js +25 -26
- package/dist/cjs/actor/utils.js +0 -49
- package/dist/cjs/actor/view.js +40 -40
- package/dist/cjs/actor/views.js +29 -8
- package/dist/cjs/constants.js +18 -19
- package/dist/cjs/defaults/hybrid-search/phrases.js +3 -3
- package/dist/cjs/defaults/hybrid-search/templates.js +22 -7
- package/dist/cjs/element/role/index.js +12 -5
- package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
- package/dist/cjs/element/role/miso-total.js +20 -0
- package/dist/cjs/layout/affiliation/affiliation.js +0 -3
- package/dist/cjs/layout/banner/banner.js +0 -4
- package/dist/cjs/layout/button/more-button.js +73 -0
- package/dist/cjs/layout/container/container.js +0 -3
- package/dist/cjs/layout/error/error.js +0 -4
- package/dist/cjs/layout/index.js +7 -0
- package/dist/cjs/layout/input/facets.js +0 -4
- package/dist/cjs/layout/input/option-list.js +0 -4
- package/dist/cjs/layout/input/search-box.js +13 -12
- package/dist/cjs/layout/list/cards.js +0 -3
- package/dist/cjs/layout/list/carousel.js +0 -3
- package/dist/cjs/layout/list/collection.js +22 -7
- package/dist/cjs/layout/list/list.js +0 -3
- package/dist/cjs/layout/radio/feedback.js +0 -3
- package/dist/cjs/layout/radio/radio.js +0 -4
- package/dist/cjs/layout/raf.js +4 -2
- package/dist/cjs/layout/text/text.js +0 -4
- package/dist/cjs/layout/text/typewriter/index.js +0 -3
- package/dist/cjs/layout/triggerable.js +79 -0
- package/dist/cjs/util/states.js +0 -15
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/answer-based.js +70 -105
- package/dist/cjs/workflow/ask.js +29 -27
- package/dist/cjs/workflow/asks.js +1 -1
- package/dist/cjs/workflow/autocompletable.js +33 -0
- package/dist/cjs/workflow/autocomplete.js +122 -0
- package/dist/cjs/workflow/base.js +156 -98
- package/dist/cjs/workflow/explore.js +48 -46
- package/dist/cjs/workflow/hybrid-search-answer.js +17 -7
- package/dist/cjs/workflow/hybrid-search-results.js +17 -87
- package/dist/cjs/workflow/hybrid-search-views.js +122 -0
- package/dist/cjs/workflow/hybrid-search.js +47 -168
- package/dist/cjs/workflow/options.js +32 -17
- package/dist/cjs/workflow/processors.js +166 -2
- package/dist/cjs/workflow/recommendation.js +36 -49
- package/dist/cjs/workflow/search-based.js +228 -0
- package/dist/cjs/workflow/search.js +21 -68
- package/dist/cjs/workflow/use-link.js +44 -0
- package/package.json +2 -2
- package/src/actor/feedback.js +6 -23
- package/src/actor/fields.js +14 -6
- package/src/actor/index.js +0 -1
- package/src/actor/interactions.js +4 -10
- package/src/actor/sessions.js +3 -31
- package/src/actor/tracker.js +25 -19
- package/src/actor/utils.js +0 -47
- package/src/actor/view.js +27 -15
- package/src/actor/views.js +32 -8
- package/src/constants.js +25 -16
- package/src/defaults/hybrid-search/phrases.js +2 -2
- package/src/defaults/hybrid-search/templates.js +17 -5
- package/src/element/role/index.js +2 -1
- package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
- package/src/element/role/miso-total.js +16 -0
- package/src/layout/affiliation/affiliation.js +1 -5
- package/src/layout/banner/banner.js +0 -5
- package/src/layout/button/more-button.js +69 -0
- package/src/layout/container/container.js +1 -5
- package/src/layout/error/error.js +0 -5
- package/src/layout/index.js +2 -1
- package/src/layout/input/facets.js +0 -5
- package/src/layout/input/option-list.js +0 -5
- package/src/layout/input/search-box.js +11 -14
- package/src/layout/list/cards.js +0 -4
- package/src/layout/list/carousel.js +0 -4
- package/src/layout/list/collection.js +21 -9
- package/src/layout/list/list.js +0 -4
- package/src/layout/radio/feedback.js +0 -4
- package/src/layout/radio/radio.js +0 -5
- package/src/layout/raf.js +2 -2
- package/src/layout/text/text.js +0 -5
- package/src/layout/text/typewriter/index.js +1 -5
- package/src/layout/triggerable.js +76 -0
- package/src/util/states.js +0 -14
- package/src/version.js +1 -1
- package/src/workflow/answer-based.js +61 -72
- package/src/workflow/ask.js +30 -24
- package/src/workflow/asks.js +1 -1
- package/src/workflow/autocompletable.js +29 -0
- package/src/workflow/autocomplete.js +113 -0
- package/src/workflow/base.js +133 -87
- package/src/workflow/explore.js +38 -30
- package/src/workflow/hybrid-search-answer.js +17 -6
- package/src/workflow/hybrid-search-results.js +17 -77
- package/src/workflow/hybrid-search-views.js +124 -0
- package/src/workflow/hybrid-search.js +47 -157
- package/src/workflow/options.js +26 -11
- package/src/workflow/processors.js +151 -3
- package/src/workflow/recommendation.js +32 -33
- package/src/workflow/search-based.js +222 -0
- package/src/workflow/search.js +21 -65
- package/src/workflow/use-link.js +40 -0
- package/dist/cjs/actor/autocomplete.js +0 -160
- package/dist/cjs/workflow/utils.js +0 -42
- package/src/actor/autocomplete.js +0 -133
- package/src/workflow/utils.js +0 -29
|
@@ -1,39 +1,49 @@
|
|
|
1
|
-
import { defineValues,
|
|
1
|
+
import { defineValues, API } from '@miso.ai/commons';
|
|
2
2
|
import Workflow from './base.js';
|
|
3
|
-
import { mergeApiOptions } from './options.js';
|
|
4
|
-
import { fields } from '../actor/index.js';
|
|
5
3
|
import { ListLayout } from '../layout/index.js';
|
|
6
4
|
import { ROLE } from '../constants.js';
|
|
5
|
+
import { mergeRolesOptions } from './options.js';
|
|
6
|
+
import { mergeInteraction } from './processors.js';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_API_OPTIONS = Object.freeze({
|
|
9
|
+
...Workflow.DEFAULT_API_OPTIONS,
|
|
9
10
|
group: API.GROUP.RECOMMENDATION,
|
|
10
11
|
name: API.NAME.USER_TO_PRODUCTS,
|
|
11
12
|
payload: {
|
|
13
|
+
...Workflow.DEFAULT_API_OPTIONS.payload,
|
|
12
14
|
fl: ['*'],
|
|
13
15
|
},
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
const DEFAULT_LAYOUTS = Object.freeze({
|
|
19
|
+
...Workflow.DEFAULT_LAYOUTS,
|
|
17
20
|
[ROLE.PRODUCTS]: ListLayout.type,
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
24
|
+
...Workflow.DEFAULT_TRACKERS,
|
|
21
25
|
[ROLE.PRODUCTS]: {},
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
29
|
+
...Workflow.DEFAULT_OPTIONS,
|
|
25
30
|
api: DEFAULT_API_OPTIONS,
|
|
26
31
|
layouts: DEFAULT_LAYOUTS,
|
|
27
32
|
trackers: DEFAULT_TRACKERS,
|
|
28
33
|
});
|
|
29
34
|
|
|
35
|
+
const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
|
|
36
|
+
main: ROLE.PRODUCTS,
|
|
37
|
+
members: Object.keys(DEFAULT_LAYOUTS),
|
|
38
|
+
});
|
|
39
|
+
|
|
30
40
|
export default class Recommendation extends Workflow {
|
|
31
41
|
|
|
32
42
|
constructor(context, id) {
|
|
33
43
|
super({
|
|
34
44
|
name: 'recommendation',
|
|
35
45
|
context,
|
|
36
|
-
roles:
|
|
46
|
+
roles: ROLES_OPTIONS,
|
|
37
47
|
defaults: DEFAULT_OPTIONS,
|
|
38
48
|
id,
|
|
39
49
|
});
|
|
@@ -45,28 +55,15 @@ export default class Recommendation extends Workflow {
|
|
|
45
55
|
defineValues(this, { id });
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
_initSession(args) {
|
|
49
59
|
this._context._members.set(args.id, this);
|
|
50
|
-
super.
|
|
60
|
+
super._initSession(args);
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
// lifecycle //
|
|
54
64
|
start() {
|
|
55
|
-
this._sessions.start();
|
|
56
65
|
// in recommendation workflow, start() triggers query
|
|
57
|
-
|
|
58
|
-
const { session } = this;
|
|
59
|
-
this._hub.update(fields.request(), mergeApiOptions(this._options.resolved.api, { session }));
|
|
60
|
-
return this;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// TODO: deprecate this
|
|
64
|
-
startTracker() {
|
|
65
|
-
this.useApi(false);
|
|
66
|
-
this.useLayouts(false);
|
|
67
|
-
this._sessions.start();
|
|
68
|
-
this.notifyViewUpdate(ROLE.CONTAINER);
|
|
69
|
-
this.notifyViewUpdate(ROLE.PRODUCTS);
|
|
66
|
+
this._request();
|
|
70
67
|
return this;
|
|
71
68
|
}
|
|
72
69
|
|
|
@@ -76,21 +73,23 @@ export default class Recommendation extends Workflow {
|
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
// interactions //
|
|
79
|
-
|
|
80
|
-
payload = super.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
_defaultProcessInteraction(payload, args) {
|
|
77
|
+
payload = super._defaultProcessInteraction(payload, args);
|
|
78
|
+
payload = this._writeUnitIdToInteraction(payload, args);
|
|
79
|
+
return payload;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_writeUnitIdToInteraction(payload) {
|
|
83
|
+
const unit_id = this.id;
|
|
84
|
+
const unit_instance_uuid = this.uuid;
|
|
85
|
+
return mergeInteraction(payload, {
|
|
85
86
|
context: {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
...custom_context,
|
|
91
|
-
}),
|
|
87
|
+
custom_context: {
|
|
88
|
+
unit_id,
|
|
89
|
+
unit_instance_uuid,
|
|
90
|
+
},
|
|
92
91
|
},
|
|
93
|
-
};
|
|
92
|
+
});
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// destroy //
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { API } from '@miso.ai/commons';
|
|
2
|
+
import Workflow from './base.js';
|
|
3
|
+
import { fields } from '../actor/index.js';
|
|
4
|
+
import { STATUS, ROLE } from '../constants.js';
|
|
5
|
+
import { SearchBoxLayout, ListLayout, TextLayout, FacetsLayout, MoreButtonLayout } from '../layout/index.js';
|
|
6
|
+
import { writeKeywordsToData, writeFiltersToPayload, retainFacetCountsInData, writeExhaustionToData, concatItemsFromMoreResponse } from './processors.js';
|
|
7
|
+
import { mergeRolesOptions, makeConfigurable } from './options.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_ROWS = 10;
|
|
10
|
+
const DEFAULT_PAGE_LIMIT = 10;
|
|
11
|
+
|
|
12
|
+
const DEFAULT_API_OPTIONS = Object.freeze({
|
|
13
|
+
...Workflow.DEFAULT_API_OPTIONS,
|
|
14
|
+
group: API.GROUP.SEARCH,
|
|
15
|
+
payload: {
|
|
16
|
+
...Workflow.DEFAULT_API_OPTIONS.payload,
|
|
17
|
+
fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'title'],
|
|
18
|
+
rows: DEFAULT_ROWS,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const DEFAULT_LAYOUTS = Object.freeze({
|
|
23
|
+
...Workflow.DEFAULT_LAYOUTS,
|
|
24
|
+
[ROLE.QUERY]: [SearchBoxLayout.type],
|
|
25
|
+
[ROLE.PRODUCTS]: [ListLayout.type, { incremental: true, infiniteScroll: true }],
|
|
26
|
+
[ROLE.KEYWORDS]: [TextLayout.type, { raw: true }],
|
|
27
|
+
[ROLE.TOTAL]: [TextLayout.type, { raw: true, format: 'number' }],
|
|
28
|
+
[ROLE.FACETS]: [FacetsLayout.type],
|
|
29
|
+
[ROLE.MORE]: [MoreButtonLayout.type],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const DEFAULT_TRACKERS = Object.freeze({
|
|
33
|
+
...Workflow.DEFAULT_TRACKERS,
|
|
34
|
+
[ROLE.PRODUCTS]: {},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const DEFAULT_PAGINATION = Object.freeze({
|
|
38
|
+
active: false,
|
|
39
|
+
mode: 'infiniteScroll',
|
|
40
|
+
pageLimit: 10,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const DEFAULT_OPTIONS = Object.freeze({
|
|
44
|
+
...Workflow.DEFAULT_OPTIONS,
|
|
45
|
+
api: DEFAULT_API_OPTIONS,
|
|
46
|
+
layouts: DEFAULT_LAYOUTS,
|
|
47
|
+
trackers: DEFAULT_TRACKERS,
|
|
48
|
+
pagination: DEFAULT_PAGINATION,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
|
|
52
|
+
main: ROLE.PRODUCTS,
|
|
53
|
+
members: Object.keys(DEFAULT_LAYOUTS),
|
|
54
|
+
mappings: {
|
|
55
|
+
[ROLE.FACETS]: 'facet_counts',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default class SearchBasedWorkflow extends Workflow {
|
|
60
|
+
|
|
61
|
+
_initSubscriptions(args) {
|
|
62
|
+
super._initSubscriptions(args);
|
|
63
|
+
this._unsubscribes = [
|
|
64
|
+
...this._unsubscribes,
|
|
65
|
+
this._hub.on(fields.query(), args => this._query(args)),
|
|
66
|
+
this._hub.on(fields.filters(), filters => this._refine(filters)),
|
|
67
|
+
this._hub.on(fields.more(), () => this._more()),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
restart() {
|
|
72
|
+
super.restart();
|
|
73
|
+
this._page = 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// properties //
|
|
77
|
+
get exhausted() {
|
|
78
|
+
const data = this._hub.states[fields.data()];
|
|
79
|
+
return !!(data && data.meta && data.meta.exhausted);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get page() {
|
|
83
|
+
return this._page;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// query //
|
|
87
|
+
query(args) {
|
|
88
|
+
if (!args.q) {
|
|
89
|
+
throw new Error(`q is required in query() call`);
|
|
90
|
+
}
|
|
91
|
+
this._hub.update(fields.query(), args);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_query(args) {
|
|
95
|
+
// keep track of the query args for refine and more calls
|
|
96
|
+
this._queryArgs = args;
|
|
97
|
+
|
|
98
|
+
// start a new session
|
|
99
|
+
this.restart();
|
|
100
|
+
|
|
101
|
+
// payload
|
|
102
|
+
const payload = this._buildPayload(args);
|
|
103
|
+
|
|
104
|
+
this._request({ payload });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_refine(filters) {
|
|
108
|
+
// remember current facet_counts
|
|
109
|
+
const { facet_counts } = this._hub.states[fields.data()].value || {};
|
|
110
|
+
this._currentFacetCounts = facet_counts;
|
|
111
|
+
|
|
112
|
+
// start a new session
|
|
113
|
+
this.restart();
|
|
114
|
+
|
|
115
|
+
// payload
|
|
116
|
+
const query = this._getQuery();
|
|
117
|
+
const payload = this._buildPayload({ ...query, filters });
|
|
118
|
+
|
|
119
|
+
this._request({ payload });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_more() {
|
|
123
|
+
// if still loading, complain
|
|
124
|
+
if (this.status !== STATUS.READY) {
|
|
125
|
+
throw new Error(`Can not call more() while loading.`);
|
|
126
|
+
}
|
|
127
|
+
// no more pages, ignore
|
|
128
|
+
if (this.exhausted || this._page >= DEFAULT_PAGE_LIMIT - 1) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// don't create a new session!
|
|
132
|
+
|
|
133
|
+
this._page++;
|
|
134
|
+
|
|
135
|
+
// payload
|
|
136
|
+
const query = this._getQuery();
|
|
137
|
+
const filters = this._hub.states[fields.filters()]; // get stored filters
|
|
138
|
+
let payload = this._buildPayload({ ...query, filters });
|
|
139
|
+
payload = this._processPayloadForMoreRequest(payload);
|
|
140
|
+
|
|
141
|
+
this._request({ payload });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_getQuery() {
|
|
145
|
+
// TODO: just get from the hub
|
|
146
|
+
return this._queryArgs;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_getPageSize() {
|
|
150
|
+
return this._options.resolved.api.payload.rows;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_buildPayload({ filters, ...payload } = {}) {
|
|
154
|
+
payload = writeFiltersToPayload(payload, filters);
|
|
155
|
+
payload = this._writePageInfoToPayload(payload);
|
|
156
|
+
return payload;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_writePageInfoToPayload(payload) {
|
|
160
|
+
return {
|
|
161
|
+
...payload,
|
|
162
|
+
_meta: {
|
|
163
|
+
...payload._meta,
|
|
164
|
+
page: this._page,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_processPayloadForMoreRequest(payload = {}) {
|
|
170
|
+
const data = this._hub.states[fields.data()];
|
|
171
|
+
const { [this._roles.main]: items = [] } = (data && data.value) || {};
|
|
172
|
+
const productIds = items.map(item => item.product_id).filter(v => v);
|
|
173
|
+
const exclude = [...(payload.exclude || []), ...productIds];
|
|
174
|
+
if (exclude.length === 0) {
|
|
175
|
+
return payload;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
...payload,
|
|
179
|
+
exclude,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// data //
|
|
184
|
+
_defaultProcessData(data) {
|
|
185
|
+
data = super._defaultProcessData(data);
|
|
186
|
+
data = writeKeywordsToData(data);
|
|
187
|
+
data = retainFacetCountsInData(data, this._currentFacetCounts);
|
|
188
|
+
data = writeExhaustionToData(data, { role: this._roles.main });
|
|
189
|
+
return data;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
_updateDataInHub(data) {
|
|
193
|
+
data = this._appendResultsFromMoreRequest(data);
|
|
194
|
+
super._updateDataInHub(data);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_appendResultsFromMoreRequest(data) {
|
|
198
|
+
const { request } = data;
|
|
199
|
+
if (!request) {
|
|
200
|
+
return data; // the initial state
|
|
201
|
+
}
|
|
202
|
+
const { _meta } = request.payload;
|
|
203
|
+
if (!_meta || !_meta.page) {
|
|
204
|
+
return data; // not from "more" request
|
|
205
|
+
}
|
|
206
|
+
// concat records if it's from "more" request
|
|
207
|
+
const currentData = this._hub.states[fields.data()];
|
|
208
|
+
return concatItemsFromMoreResponse(currentData, data, { role: this._roles.main });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
makeConfigurable(SearchBasedWorkflow.prototype, ['pagination']);
|
|
214
|
+
|
|
215
|
+
Object.assign(SearchBasedWorkflow, {
|
|
216
|
+
DEFAULT_API_OPTIONS,
|
|
217
|
+
DEFAULT_LAYOUTS,
|
|
218
|
+
DEFAULT_TRACKERS,
|
|
219
|
+
DEFAULT_PAGINATION,
|
|
220
|
+
DEFAULT_OPTIONS,
|
|
221
|
+
ROLES_OPTIONS,
|
|
222
|
+
});
|
package/src/workflow/search.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { API } from '@miso.ai/commons';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { fields, AutocompleteActor } from '../actor/index.js';
|
|
6
|
-
import { ROLE, DATA_ASPECT } from '../constants.js';
|
|
7
|
-
import { ListLayout, SearchBoxLayout } from '../layout/index.js';
|
|
8
|
-
import { makeConfigurable } from './options.js';
|
|
2
|
+
import { ROLE } from '../constants.js';
|
|
3
|
+
import SearchBasedWorkflow from './search-based.js';
|
|
4
|
+
import { makeAutocompletable } from './autocompletable.js';
|
|
9
5
|
|
|
10
6
|
const DEFAULT_API_OPTIONS = Object.freeze({
|
|
11
7
|
group: API.GROUP.SEARCH,
|
|
@@ -16,7 +12,6 @@ const DEFAULT_API_OPTIONS = Object.freeze({
|
|
|
16
12
|
});
|
|
17
13
|
|
|
18
14
|
const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
|
|
19
|
-
actor: false,
|
|
20
15
|
api: {
|
|
21
16
|
group: API.GROUP.SEARCH,
|
|
22
17
|
name: API.NAME.AUTOCOMPLETE,
|
|
@@ -25,54 +20,43 @@ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
|
|
|
25
20
|
fl: ['title', 'url', 'cover_image'],
|
|
26
21
|
},
|
|
27
22
|
},
|
|
28
|
-
throttle: 300,
|
|
29
23
|
});
|
|
30
24
|
|
|
31
25
|
const DEFAULT_LAYOUTS = Object.freeze({
|
|
32
|
-
|
|
33
|
-
[ROLE.PRODUCTS]: ListLayout.type,
|
|
26
|
+
...SearchBasedWorkflow.DEFAULT_LAYOUTS,
|
|
34
27
|
});
|
|
35
28
|
|
|
36
29
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
37
|
-
|
|
30
|
+
...SearchBasedWorkflow.DEFAULT_TRACKERS,
|
|
38
31
|
});
|
|
39
32
|
|
|
40
33
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
34
|
+
...SearchBasedWorkflow.DEFAULT_OPTIONS,
|
|
41
35
|
api: DEFAULT_API_OPTIONS,
|
|
42
36
|
autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
|
|
43
37
|
layouts: DEFAULT_LAYOUTS,
|
|
44
38
|
trackers: DEFAULT_TRACKERS,
|
|
45
39
|
});
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
const ROLES_OPTIONS = SearchBasedWorkflow.ROLES_OPTIONS;
|
|
42
|
+
|
|
43
|
+
export default class Search extends SearchBasedWorkflow {
|
|
48
44
|
|
|
49
45
|
constructor(plugin, client) {
|
|
50
46
|
super({
|
|
51
47
|
name: 'search',
|
|
52
48
|
plugin,
|
|
53
49
|
client,
|
|
54
|
-
roles:
|
|
50
|
+
roles: ROLES_OPTIONS,
|
|
55
51
|
defaults: DEFAULT_OPTIONS,
|
|
56
52
|
});
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
super.
|
|
61
|
-
this.
|
|
62
|
-
source: sources.api(this._client),
|
|
63
|
-
options: this._options,
|
|
64
|
-
});
|
|
55
|
+
_initProperties(args) {
|
|
56
|
+
super._initProperties(args);
|
|
57
|
+
this._initAutocomplete(args);
|
|
65
58
|
}
|
|
66
59
|
|
|
67
|
-
_initSubscriptions(args) {
|
|
68
|
-
super._initSubscriptions(args);
|
|
69
|
-
this._unsubscribes = [
|
|
70
|
-
...this._unsubscribes,
|
|
71
|
-
this._hub.on(fields.query(), args => this._query(args)),
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// lifecycle //
|
|
76
60
|
autoQuery({ setValue = true, focus = true } = {}) {
|
|
77
61
|
const q = new URLSearchParams(window.location.search).get('q');
|
|
78
62
|
const { layout } = this._views.get(ROLE.QUERY);
|
|
@@ -89,46 +73,18 @@ export default class Search extends Workflow {
|
|
|
89
73
|
}
|
|
90
74
|
}
|
|
91
75
|
|
|
92
|
-
query(args) {
|
|
93
|
-
if (!args.q) {
|
|
94
|
-
throw new Error(`q is required in query() call`);
|
|
95
|
-
}
|
|
96
|
-
this._hub.update(fields.query(), args);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
_query(args) {
|
|
100
|
-
// start a new session
|
|
101
|
-
this.restart();
|
|
102
|
-
const { session } = this;
|
|
103
|
-
|
|
104
|
-
// build payload and trigger request
|
|
105
|
-
const payload = this._buildPayload(args);
|
|
106
|
-
const event = mergeApiOptions(this._options.resolved.api, { payload, session });
|
|
107
|
-
this._request(event);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
_buildPayload(args) {
|
|
111
|
-
return args;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
_request(event) {
|
|
115
|
-
this._hub.update(fields.request(), event);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
updateCompletions(event) {
|
|
119
|
-
// TODO: verify
|
|
120
|
-
this._hub.update(fields.data(DATA_ASPECT.AUTOCOMPLETE), {
|
|
121
|
-
...event,
|
|
122
|
-
source: 'manual',
|
|
123
|
-
});
|
|
124
|
-
return this;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
76
|
notifyViewUpdate(role = ROLE.PRODUCTS, ...args) {
|
|
128
77
|
super.notifyViewUpdate(role, ...args);
|
|
129
78
|
return this;
|
|
130
79
|
}
|
|
131
80
|
|
|
81
|
+
// destroy //
|
|
82
|
+
_destroy(options) {
|
|
83
|
+
this._feedback._destroy();
|
|
84
|
+
this._destroyAutocomplete();
|
|
85
|
+
super._destroy(options);
|
|
86
|
+
}
|
|
87
|
+
|
|
132
88
|
}
|
|
133
89
|
|
|
134
|
-
|
|
90
|
+
makeAutocompletable(Search.prototype);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mixin } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
export function enableUseLink(prototype) {
|
|
4
|
+
mixin(prototype, UseLinkMixin.prototype);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class UseLinkMixin {
|
|
8
|
+
|
|
9
|
+
useLink(fn, options = {}) {
|
|
10
|
+
if (typeof fn !== 'function' && fn !== false) {
|
|
11
|
+
throw new Error('useLink(fn) expects fn to be a function or false');
|
|
12
|
+
}
|
|
13
|
+
this._linkFn = fn ? [fn, options] : false;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_submitToPage(args = {}) {
|
|
18
|
+
if (!this._linkFn) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// TODO: we want a submit event
|
|
22
|
+
const url = this._getSubmitUrl(args);
|
|
23
|
+
const [_, options = {}] = this._linkFn;
|
|
24
|
+
if (options.open === false) {
|
|
25
|
+
window.location.href = url;
|
|
26
|
+
} else {
|
|
27
|
+
const target = options.target || '_blank';
|
|
28
|
+
const windowFeatures = options.windowFeatures || '';
|
|
29
|
+
window.open(url, target, windowFeatures);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_getSubmitUrl(args) {
|
|
34
|
+
if (!this._linkFn) {
|
|
35
|
+
throw new Error('useLink(fn) must be called before _getSubmitUrl()');
|
|
36
|
+
}
|
|
37
|
+
return this._linkFn[0](args.q);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _constants = require("../constants.js");
|
|
8
|
-
var fields = _interopRequireWildcard(require("./fields.js"));
|
|
9
|
-
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); }
|
|
10
|
-
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 && {}.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; }
|
|
11
|
-
class AutocompleteActor {
|
|
12
|
-
constructor(hub, _ref) {
|
|
13
|
-
let {
|
|
14
|
-
source,
|
|
15
|
-
options
|
|
16
|
-
} = _ref;
|
|
17
|
-
this._hub = hub;
|
|
18
|
-
this._source = source;
|
|
19
|
-
this._options = options;
|
|
20
|
-
this._index = -1;
|
|
21
|
-
|
|
22
|
-
// TODO: listen to request:input instead
|
|
23
|
-
this._unsubscribes = [hub.on(fields.input(), () => this._sync()), options.on('autocomplete', () => this._syncOptions())];
|
|
24
|
-
|
|
25
|
-
// sync right away
|
|
26
|
-
this._sync(true);
|
|
27
|
-
}
|
|
28
|
-
get active() {
|
|
29
|
-
return this._options.resolved.autocomplete.actor !== false;
|
|
30
|
-
}
|
|
31
|
-
get source() {
|
|
32
|
-
return this._source;
|
|
33
|
-
}
|
|
34
|
-
_syncOptions() {
|
|
35
|
-
// TODO: explore scenarios where options.actor true -> false
|
|
36
|
-
this._sync(true);
|
|
37
|
-
}
|
|
38
|
-
async _sync() {
|
|
39
|
-
let force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
40
|
-
// protocol: inactive -> no reaction
|
|
41
|
-
if (!this.active) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const {
|
|
45
|
-
input
|
|
46
|
-
} = this._hub.states;
|
|
47
|
-
const value = input && input.value && input.value.trim() || undefined;
|
|
48
|
-
if (sameInput(this._input, input) && !force) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
this._input = input;
|
|
52
|
-
const index = ++this._index;
|
|
53
|
-
|
|
54
|
-
// new input, abort previous data fetch if any
|
|
55
|
-
this._clearTimeout();
|
|
56
|
-
this._abortPendingRequest({
|
|
57
|
-
type: 'new-input',
|
|
58
|
-
message: 'A new input renders previous autocomplete request obsolete.'
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// emit loading/initial state
|
|
62
|
-
this._emitCompletions({
|
|
63
|
-
index,
|
|
64
|
-
input,
|
|
65
|
-
status: value ? _constants.STATUS.LOADING : _constants.STATUS.INITIAL
|
|
66
|
-
});
|
|
67
|
-
if (!value) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
const response = await this._fetchCompletions(input);
|
|
72
|
-
if (this._index !== index) {
|
|
73
|
-
return; // new results loading, discard this one
|
|
74
|
-
}
|
|
75
|
-
this._emitCompletions({
|
|
76
|
-
index,
|
|
77
|
-
input,
|
|
78
|
-
status: _constants.STATUS.READY,
|
|
79
|
-
value: response.completions
|
|
80
|
-
});
|
|
81
|
-
} catch (error) {
|
|
82
|
-
this._error(error);
|
|
83
|
-
if (this._index !== index) {
|
|
84
|
-
return; // ignore
|
|
85
|
-
}
|
|
86
|
-
this._emitCompletions({
|
|
87
|
-
index,
|
|
88
|
-
input,
|
|
89
|
-
status: _constants.STATUS.ERRONEOUS,
|
|
90
|
-
error
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async _getCompletions(input) {
|
|
95
|
-
// TODO: cache
|
|
96
|
-
return await this._fetchCompletions(input);
|
|
97
|
-
}
|
|
98
|
-
async _fetchCompletions(_ref2) {
|
|
99
|
-
let {
|
|
100
|
-
value
|
|
101
|
-
} = _ref2;
|
|
102
|
-
// TODO: other options?
|
|
103
|
-
const {
|
|
104
|
-
api
|
|
105
|
-
} = this._options.resolved.autocomplete;
|
|
106
|
-
const {
|
|
107
|
-
group,
|
|
108
|
-
name
|
|
109
|
-
} = api;
|
|
110
|
-
const {
|
|
111
|
-
signal
|
|
112
|
-
} = this._ac || {};
|
|
113
|
-
const options = Object.assign({}, api.options, {
|
|
114
|
-
signal
|
|
115
|
-
});
|
|
116
|
-
const payload = Object.assign({}, api.payload, {
|
|
117
|
-
q: value
|
|
118
|
-
});
|
|
119
|
-
return await this._source({
|
|
120
|
-
group,
|
|
121
|
-
name,
|
|
122
|
-
payload,
|
|
123
|
-
options
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
_clearTimeout() {
|
|
127
|
-
if (this._timeoutId) {
|
|
128
|
-
clearTimeout(this._timeoutId);
|
|
129
|
-
this._timeoutId = undefined;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
_abortPendingRequest(info) {
|
|
133
|
-
this._ac && this._ac.abort(info);
|
|
134
|
-
this._ac = new AbortController();
|
|
135
|
-
}
|
|
136
|
-
_emitCompletions(event) {
|
|
137
|
-
event.source = 'actor';
|
|
138
|
-
// TDOO: update response:autocomplete instead
|
|
139
|
-
this._hub.update(fields.data(_constants.DATA_ASPECT.AUTOCOMPLETE), event);
|
|
140
|
-
}
|
|
141
|
-
_error(error) {
|
|
142
|
-
// TODO
|
|
143
|
-
console.error(error);
|
|
144
|
-
}
|
|
145
|
-
_destroy() {
|
|
146
|
-
// abort ongoing data fetch if any
|
|
147
|
-
this._ac && this._ac.abort({
|
|
148
|
-
type: 'autocomplete-actor-destroy',
|
|
149
|
-
message: 'Autocomplete actor is destroyed.'
|
|
150
|
-
});
|
|
151
|
-
for (const unsubscribe of this._unsubscribes) {
|
|
152
|
-
unsubscribe();
|
|
153
|
-
}
|
|
154
|
-
this._unsubscribes = [];
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
exports.default = AutocompleteActor;
|
|
158
|
-
function sameInput(a, b) {
|
|
159
|
-
return !a && !b || a && b && a.value === b.value;
|
|
160
|
-
}
|