@miso.ai/client-sdk-ui 1.13.2-beta.1 → 1.14.0-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.
- package/dist/cjs/asset/svgs.js +15 -4
- package/dist/cjs/combo/ask/index.js +2 -2
- package/dist/cjs/constants.js +118 -98
- package/dist/cjs/defaults/ask/controls.js +1 -1
- package/dist/cjs/defaults/hybrid-search/answer-box.js +4 -4
- package/dist/cjs/defaults.js +169 -0
- package/dist/cjs/element/container/index.js +14 -0
- package/dist/cjs/element/container/miso-ask.js +3 -3
- package/dist/cjs/element/container/miso-conversation.js +21 -0
- package/dist/cjs/element/container/miso-explore.js +1 -1
- package/dist/cjs/element/container/miso-history.js +21 -0
- package/dist/cjs/element/container/miso-hybrid-search.js +1 -1
- package/dist/cjs/element/container/miso-recommendation.js +1 -1
- package/dist/cjs/element/container/miso-search.js +1 -1
- package/dist/cjs/element/context/miso-follow-ups.js +1 -1
- package/dist/cjs/element/role/index.js +42 -0
- package/dist/cjs/element/role/miso-messages.js +20 -0
- package/dist/cjs/element/role/miso-new-thread.js +20 -0
- package/dist/cjs/element/role/miso-rename.js +20 -0
- package/dist/cjs/element/role/miso-subscription.js +20 -0
- package/dist/cjs/element/role/miso-threads.js +20 -0
- package/dist/cjs/element/role/miso-title.js +20 -0
- package/dist/cjs/layout/affiliation/affiliation.js +1 -1
- package/dist/cjs/layout/banner/banner.js +2 -1
- package/dist/cjs/layout/button/button.js +130 -0
- package/dist/cjs/layout/button/checkbox.js +115 -0
- package/dist/cjs/layout/button/more-button.js +3 -3
- package/dist/cjs/layout/container/container.js +1 -1
- package/dist/cjs/layout/error/error.js +2 -1
- package/dist/cjs/layout/index.js +28 -0
- package/dist/cjs/layout/input/facets.js +4 -3
- package/dist/cjs/layout/input/option-list.js +3 -2
- package/dist/cjs/layout/input/search-box.js +42 -9
- package/dist/cjs/layout/input/select.js +2 -1
- package/dist/cjs/layout/list/cards.js +2 -1
- package/dist/cjs/layout/list/carousel.js +2 -1
- package/dist/cjs/layout/list/collection.js +5 -0
- package/dist/cjs/layout/list/gallery.js +2 -1
- package/dist/cjs/layout/list/horizontal.js +2 -1
- package/dist/cjs/layout/list/list.js +2 -1
- package/dist/cjs/layout/list/messages.js +346 -0
- package/dist/cjs/layout/list/threads.js +257 -0
- package/dist/cjs/layout/mixin/trackable.js +4 -5
- package/dist/cjs/layout/mixin/triggerable.js +2 -2
- package/dist/cjs/layout/radio/feedback.js +4 -3
- package/dist/cjs/layout/templates.js +70 -0
- package/dist/cjs/layout/text/text.js +2 -1
- package/dist/cjs/layout/text/typewriter/index.js +4 -2
- package/dist/cjs/plugin.js +43 -104
- package/dist/cjs/util/confirm.js +33 -0
- package/dist/cjs/util/modal.js +48 -0
- package/dist/cjs/util/prompt.js +47 -0
- package/dist/cjs/version.js +1 -1
- package/package.json +5 -4
- package/src/CLAUDE.md +48 -105
- package/src/asset/svgs.js +14 -3
- package/src/combo/ask/index.js +2 -2
- package/src/constants.js +37 -147
- package/src/defaults/ask/controls.js +1 -1
- package/src/defaults/hybrid-search/answer-box.js +2 -2
- package/src/defaults.js +107 -0
- package/src/element/container/index.js +2 -0
- package/src/element/container/miso-ask.js +3 -3
- package/src/element/container/miso-conversation.js +19 -0
- package/src/element/container/miso-explore.js +1 -1
- package/src/element/container/miso-history.js +19 -0
- package/src/element/container/miso-hybrid-search.js +1 -1
- package/src/element/container/miso-recommendation.js +1 -1
- package/src/element/container/miso-search.js +1 -1
- package/src/element/context/miso-follow-ups.js +1 -1
- package/src/element/role/index.js +6 -0
- package/src/element/role/miso-messages.js +16 -0
- package/src/element/role/miso-new-thread.js +16 -0
- package/src/element/role/miso-rename.js +16 -0
- package/src/element/role/miso-subscription.js +16 -0
- package/src/element/role/miso-threads.js +16 -0
- package/src/element/role/miso-title.js +16 -0
- package/src/layout/affiliation/affiliation.js +2 -2
- package/src/layout/banner/banner.js +2 -1
- package/src/layout/button/button.js +116 -0
- package/src/layout/button/checkbox.js +91 -0
- package/src/layout/button/more-button.js +3 -3
- package/src/layout/container/container.js +2 -2
- package/src/layout/error/error.js +2 -1
- package/src/layout/index.js +4 -0
- package/src/layout/input/facets.js +3 -2
- package/src/layout/input/option-list.js +3 -2
- package/src/layout/input/search-box.js +41 -6
- package/src/layout/input/select.js +2 -1
- package/src/layout/list/cards.js +2 -1
- package/src/layout/list/carousel.js +2 -1
- package/src/layout/list/collection.js +6 -1
- package/src/layout/list/gallery.js +2 -1
- package/src/layout/list/horizontal.js +2 -1
- package/src/layout/list/list.js +2 -1
- package/src/layout/list/messages.js +318 -0
- package/src/layout/list/threads.js +206 -0
- package/src/layout/mixin/trackable.js +2 -3
- package/src/layout/mixin/triggerable.js +1 -1
- package/src/layout/radio/feedback.js +3 -2
- package/src/layout/templates.js +62 -0
- package/src/layout/text/text.js +2 -1
- package/src/layout/text/typewriter/index.js +5 -3
- package/src/plugin.js +31 -117
- package/src/util/confirm.js +28 -0
- package/src/util/modal.js +49 -0
- package/src/util/prompt.js +42 -0
- package/src/version.js +1 -1
- package/dist/cjs/actor/data.js +0 -132
- package/dist/cjs/actor/feedback.js +0 -29
- package/dist/cjs/actor/fields.js +0 -57
- package/dist/cjs/actor/filters.js +0 -161
- package/dist/cjs/actor/hub.js +0 -57
- package/dist/cjs/actor/index.js +0 -73
- package/dist/cjs/actor/interactions.js +0 -36
- package/dist/cjs/actor/sessions.js +0 -25
- package/dist/cjs/actor/tracker-states.js +0 -61
- package/dist/cjs/actor/tracker.js +0 -109
- package/dist/cjs/actor/trackers.js +0 -52
- package/dist/cjs/actor/utils.js +0 -15
- package/dist/cjs/actor/view.js +0 -294
- package/dist/cjs/actor/views.js +0 -209
- package/dist/cjs/source.js +0 -27
- package/dist/cjs/util/proxy.js +0 -88
- package/dist/cjs/util/trackers.js +0 -88
- package/dist/cjs/workflow/affiliations.js +0 -140
- package/dist/cjs/workflow/answer-based.js +0 -337
- package/dist/cjs/workflow/ask.js +0 -319
- package/dist/cjs/workflow/asks.js +0 -150
- package/dist/cjs/workflow/autocompletable.js +0 -63
- package/dist/cjs/workflow/autocomplete.js +0 -120
- package/dist/cjs/workflow/base.js +0 -418
- package/dist/cjs/workflow/context.js +0 -19
- package/dist/cjs/workflow/explore.js +0 -210
- package/dist/cjs/workflow/explores.js +0 -21
- package/dist/cjs/workflow/hybrid-search-answer.js +0 -88
- package/dist/cjs/workflow/hybrid-search-views.js +0 -109
- package/dist/cjs/workflow/hybrid-search.js +0 -213
- package/dist/cjs/workflow/index.js +0 -41
- package/dist/cjs/workflow/options/api.js +0 -41
- package/dist/cjs/workflow/options/auto-query.js +0 -97
- package/dist/cjs/workflow/options/configurable.js +0 -45
- package/dist/cjs/workflow/options/data-processor.js +0 -20
- package/dist/cjs/workflow/options/filters.js +0 -15
- package/dist/cjs/workflow/options/index.js +0 -126
- package/dist/cjs/workflow/options/interactions.js +0 -41
- package/dist/cjs/workflow/options/layouts.js +0 -79
- package/dist/cjs/workflow/options/options.js +0 -156
- package/dist/cjs/workflow/options/pagination.js +0 -25
- package/dist/cjs/workflow/options/roles.js +0 -15
- package/dist/cjs/workflow/options/templates.js +0 -30
- package/dist/cjs/workflow/options/trackers.js +0 -83
- package/dist/cjs/workflow/options/utils.js +0 -33
- package/dist/cjs/workflow/processors.js +0 -607
- package/dist/cjs/workflow/recommendation.js +0 -57
- package/dist/cjs/workflow/recommendations.js +0 -18
- package/dist/cjs/workflow/search-based.js +0 -337
- package/dist/cjs/workflow/search.js +0 -64
- package/dist/cjs/workflow/unit.js +0 -57
- package/dist/cjs/workflow/units.js +0 -42
- package/dist/cjs/workflow/use-link.js +0 -49
- package/src/actor/data.js +0 -106
- package/src/actor/feedback.js +0 -28
- package/src/actor/fields.js +0 -51
- package/src/actor/filters.js +0 -149
- package/src/actor/hub.js +0 -47
- package/src/actor/index.js +0 -10
- package/src/actor/interactions.js +0 -32
- package/src/actor/sessions.js +0 -21
- package/src/actor/tracker-states.js +0 -57
- package/src/actor/tracker.js +0 -104
- package/src/actor/trackers.js +0 -45
- package/src/actor/utils.js +0 -7
- package/src/actor/view.js +0 -272
- package/src/actor/views.js +0 -224
- package/src/source.js +0 -17
- package/src/util/proxy.js +0 -81
- package/src/util/trackers.js +0 -84
- package/src/workflow/affiliations.js +0 -121
- package/src/workflow/answer-based.js +0 -312
- package/src/workflow/ask.js +0 -284
- package/src/workflow/asks.js +0 -143
- package/src/workflow/autocompletable.js +0 -67
- package/src/workflow/autocomplete.js +0 -114
- package/src/workflow/base.js +0 -396
- package/src/workflow/context.js +0 -16
- package/src/workflow/explore.js +0 -194
- package/src/workflow/explores.js +0 -18
- package/src/workflow/hybrid-search-answer.js +0 -73
- package/src/workflow/hybrid-search-views.js +0 -116
- package/src/workflow/hybrid-search.js +0 -230
- package/src/workflow/index.js +0 -5
- package/src/workflow/options/api.js +0 -31
- package/src/workflow/options/auto-query.js +0 -70
- package/src/workflow/options/configurable.js +0 -48
- package/src/workflow/options/data-processor.js +0 -15
- package/src/workflow/options/filters.js +0 -10
- package/src/workflow/options/index.js +0 -11
- package/src/workflow/options/interactions.js +0 -36
- package/src/workflow/options/layouts.js +0 -73
- package/src/workflow/options/options.js +0 -175
- package/src/workflow/options/pagination.js +0 -18
- package/src/workflow/options/roles.js +0 -12
- package/src/workflow/options/templates.js +0 -27
- package/src/workflow/options/trackers.js +0 -80
- package/src/workflow/options/utils.js +0 -26
- package/src/workflow/processors.js +0 -490
- package/src/workflow/recommendation.js +0 -63
- package/src/workflow/recommendations.js +0 -14
- package/src/workflow/search-based.js +0 -329
- package/src/workflow/search.js +0 -73
- package/src/workflow/unit.js +0 -51
- package/src/workflow/units.js +0 -43
- package/src/workflow/use-link.js +0 -41
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { getThreadId, isThreadUnread } from '@miso.ai/client-sdk-workflow';
|
|
2
|
+
import { LAYOUT_TYPE } from '../../constants.js';
|
|
3
|
+
import CollectionLayout from './collection.js';
|
|
4
|
+
import { setOrRemoveAttribute } from '../../util/dom.js';
|
|
5
|
+
import confirm from '../../util/confirm.js';
|
|
6
|
+
import prompt from '../../util/prompt.js';
|
|
7
|
+
|
|
8
|
+
const TYPE = LAYOUT_TYPE.THREADS;
|
|
9
|
+
const DEFAULT_CLASSNAME = 'miso-threads';
|
|
10
|
+
|
|
11
|
+
// the thread template renders the <li> itself (it is the item, carrying the
|
|
12
|
+
// item role and state), so the stock <li> wrapping is skipped
|
|
13
|
+
function item(layout, state, value, index) {
|
|
14
|
+
const { templates, options } = layout;
|
|
15
|
+
return templates[options.itemType](layout, state, value, { index });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The thread list of the chat history interface: a list of `thread` items.
|
|
20
|
+
*
|
|
21
|
+
* Selection comes with the data: the history workflow stamps
|
|
22
|
+
* `selectedThreadId` into its value and the role mapping decorates each
|
|
23
|
+
* thread record with a `selected` flag, so any selection change flows down
|
|
24
|
+
* the regular data path and refreshes the view. The list renders
|
|
25
|
+
* incrementally (append-only); in-place state changes on existing items
|
|
26
|
+
* (selected, unread) are applied by `_syncSelection` after each render,
|
|
27
|
+
* reading the fresh values off the item bindings — no item re-render needed.
|
|
28
|
+
*
|
|
29
|
+
* Each item carries a context menu (vertical dots) with thread actions,
|
|
30
|
+
* each asking through a shared modal dialog util, then emitting a view event
|
|
31
|
+
* for the history workflow to act on: `rename` (a prompt dialog pre-filled
|
|
32
|
+
* with the current title) and `delete` (a confirm dialog).
|
|
33
|
+
*/
|
|
34
|
+
export default class ThreadsLayout extends CollectionLayout {
|
|
35
|
+
|
|
36
|
+
static get type() {
|
|
37
|
+
return TYPE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static get defaultClassName() {
|
|
41
|
+
return DEFAULT_CLASSNAME;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
|
|
45
|
+
super({
|
|
46
|
+
className,
|
|
47
|
+
templates: { item, ...templates },
|
|
48
|
+
...options,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
initialize(view) {
|
|
53
|
+
super.initialize(view);
|
|
54
|
+
// clicking outside the list closes any open context menu
|
|
55
|
+
if (typeof document !== 'undefined') {
|
|
56
|
+
const onDocumentClick = (event) => {
|
|
57
|
+
const element = this._element;
|
|
58
|
+
if (element && !element.contains(event.target)) {
|
|
59
|
+
this._closeMenus();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
document.addEventListener('click', onDocumentClick);
|
|
63
|
+
this._unsubscribes.push(() => document.removeEventListener('click', onDocumentClick));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// the list is newest-first, so incremental renders PREPEND the fresh items
|
|
68
|
+
// (the collection layout's stock incremental mode is append-only)
|
|
69
|
+
_html(state, rendered, incremental) {
|
|
70
|
+
if (incremental) {
|
|
71
|
+
const values = this._getItems(state) || [];
|
|
72
|
+
const fresh = values.slice(0, values.length - rendered.value.length);
|
|
73
|
+
return fresh.length > 0 ? this.templates.items(this, state, fresh, { offset: 0 }) : '';
|
|
74
|
+
}
|
|
75
|
+
return this.templates.root(this, state);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_render(element, { state }, { notifyUpdate }) {
|
|
79
|
+
const { incremental, html } = state;
|
|
80
|
+
if (incremental) {
|
|
81
|
+
if (html) {
|
|
82
|
+
this._getListElement(element).insertAdjacentHTML('afterbegin', html);
|
|
83
|
+
} else {
|
|
84
|
+
notifyUpdate(false);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
element.innerHTML = html;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_afterRender(element, state) {
|
|
92
|
+
super._afterRender(element, state); // syncs bindings to the latest values
|
|
93
|
+
this._syncItems(element);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// sync in-place item changes from the bound values onto the existing item
|
|
97
|
+
// elements: selection/unread state, and record changes that arrive without
|
|
98
|
+
// a re-render (rename, a placeholder thread resolving to its real record)
|
|
99
|
+
_syncItems(element) {
|
|
100
|
+
for (const item of this._getItemElements(element)) {
|
|
101
|
+
const binding = this._bindings.get(item);
|
|
102
|
+
if (!binding) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const { value } = binding;
|
|
106
|
+
setOrRemoveAttribute(item, 'data-selected', value.selected ? '' : undefined);
|
|
107
|
+
setOrRemoveAttribute(item, 'data-unread', isThreadUnread(value) ? '' : undefined);
|
|
108
|
+
setOrRemoveAttribute(item, 'data-thread-id', getThreadId(value) || undefined);
|
|
109
|
+
const titleElement = item.querySelector(`.${this.className}__title`);
|
|
110
|
+
const title = value.title || 'Untitled';
|
|
111
|
+
if (titleElement && titleElement.textContent !== title) {
|
|
112
|
+
titleElement.textContent = title;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// a click on a thread item means selection — a navigation action, not a
|
|
118
|
+
// content-engagement click: emit a select view event and skip click tracking
|
|
119
|
+
_onClick(event) {
|
|
120
|
+
if (event.target.closest(`[data-role="thread-menu-button"]`)) {
|
|
121
|
+
this._toggleMenu(event.target.closest(`[data-role="item"]`));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (event.target.closest(`[data-role="thread-rename"]`)) {
|
|
125
|
+
this._closeMenus();
|
|
126
|
+
const binding = this._bindings.get(event.target.closest(`[data-role="item"]`));
|
|
127
|
+
binding && this._requestRename(binding);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (event.target.closest(`[data-role="thread-delete"]`)) {
|
|
131
|
+
this._closeMenus();
|
|
132
|
+
const binding = this._bindings.get(event.target.closest(`[data-role="item"]`));
|
|
133
|
+
binding && this._requestDelete(binding);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (event.target.closest(`[data-role="thread-menu"]`)) {
|
|
137
|
+
return; // other clicks inside the menu don't select
|
|
138
|
+
}
|
|
139
|
+
this._closeMenus();
|
|
140
|
+
const element = event.target.closest(`[data-role="item"]`);
|
|
141
|
+
if (!element) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const binding = this._bindings.get(element);
|
|
145
|
+
if (!binding) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const { session } = this._view._state;
|
|
149
|
+
const { value } = binding;
|
|
150
|
+
this._view._emit('select', { session, value, element: binding.element, domEvent: event });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// context menu //
|
|
154
|
+
_toggleMenu(item) {
|
|
155
|
+
const menu = item && item.querySelector(`[data-role="thread-menu"]`);
|
|
156
|
+
if (!menu) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const open = menu.hidden;
|
|
160
|
+
this._closeMenus();
|
|
161
|
+
menu.hidden = !open;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_closeMenus() {
|
|
165
|
+
const element = this._element;
|
|
166
|
+
if (!element) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const menu of element.querySelectorAll(`[data-role="thread-menu"]:not([hidden])`)) {
|
|
170
|
+
menu.hidden = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// rename //
|
|
175
|
+
async _requestRename(binding) {
|
|
176
|
+
const { value, element } = binding;
|
|
177
|
+
const title = await prompt({
|
|
178
|
+
title: 'Rename thread',
|
|
179
|
+
value: value.title || '',
|
|
180
|
+
placeholder: 'Thread name',
|
|
181
|
+
confirmText: 'Rename',
|
|
182
|
+
});
|
|
183
|
+
if (!title || title === value.title) {
|
|
184
|
+
return; // cancelled, cleared, or unchanged
|
|
185
|
+
}
|
|
186
|
+
const { session } = this._view._state;
|
|
187
|
+
this._view._emit('rename', { session, value, element, title });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// delete //
|
|
191
|
+
async _requestDelete(binding) {
|
|
192
|
+
const { value, element } = binding;
|
|
193
|
+
const confirmed = await confirm({
|
|
194
|
+
title: 'Delete thread',
|
|
195
|
+
message: `Are you sure you want to delete "${value.title || 'this thread'}"? This cannot be undone.`,
|
|
196
|
+
confirmText: 'Delete',
|
|
197
|
+
danger: true,
|
|
198
|
+
});
|
|
199
|
+
if (!confirmed) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const { session } = this._view._state;
|
|
203
|
+
this._view._emit('delete', { session, value, element });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mixin } from '@miso.ai/commons';
|
|
2
2
|
import Bindings from '../../util/bindings.js';
|
|
3
3
|
import Viewables from '../../util/viewables.js';
|
|
4
|
-
import { validateClick, markAsTracked } from '
|
|
4
|
+
import { validateClick, markAsTracked } from '@miso.ai/client-sdk-workflow';
|
|
5
5
|
|
|
6
6
|
export function makeTrackable(prototype) {
|
|
7
7
|
mixin(prototype, TrackableMixin.prototype);
|
|
@@ -29,8 +29,7 @@ export class TrackableMixin {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
_getItemKey(value) {
|
|
32
|
-
|
|
33
|
-
return value.product_id || value.text || value.id || value;
|
|
32
|
+
return this._view.tracker.getItemKey(value);
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
_getItems(state) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mixin, viewable as whenViewable } from '@miso.ai/commons';
|
|
2
2
|
import { STATUS } from '../../constants.js';
|
|
3
|
-
import { fields } from '
|
|
3
|
+
import { fields } from '@miso.ai/client-sdk-workflow';
|
|
4
4
|
|
|
5
5
|
export function makeTriggerable(prototype) {
|
|
6
6
|
mixin(prototype, TriggerableMixin.prototype);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LAYOUT_TYPE } from '../../constants.js';
|
|
2
|
+
import { fields } from '@miso.ai/client-sdk-workflow';
|
|
2
3
|
import RadioLayout from './radio.js';
|
|
3
4
|
import { THUMB } from '../svgs.js';
|
|
4
5
|
|
|
5
|
-
const TYPE =
|
|
6
|
+
const TYPE = LAYOUT_TYPE.FEEDBACK;
|
|
6
7
|
const DEFAULT_CLASSNAME = 'miso-feedback';
|
|
7
8
|
|
|
8
9
|
const OPTIONS = [ 'helpful', 'unhelpful' ];
|
package/src/layout/templates.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { escapeHtml } from '@miso.ai/commons';
|
|
2
|
+
import { getThreadId, isThreadUnread, getGeneratedBy } from '@miso.ai/client-sdk-workflow';
|
|
3
|
+
import { getIcon } from '../asset/svgs.js';
|
|
2
4
|
import { ATTR_DATA_MISO_PRODUCT_ID } from '../constants.js';
|
|
3
5
|
|
|
4
6
|
export function requiresImplementation(...names) {
|
|
@@ -30,6 +32,66 @@ export function question(layout, state, data) {
|
|
|
30
32
|
return `${openTag}${data.value || data.text || data}${closeTag}`;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
// the <li> itself is the item: it carries the item role, the thread identity
|
|
36
|
+
// and the item state — a thread item is not a link, so no anchor body (and
|
|
37
|
+
// the wrapper div around it) is needed
|
|
38
|
+
export function thread(layout, state, data) {
|
|
39
|
+
const { className, templates } = layout;
|
|
40
|
+
const threadId = getThreadId(data);
|
|
41
|
+
const threadIdAttr = threadId ? ` data-thread-id="${threadId}"` : '';
|
|
42
|
+
const unreadAttr = isThreadUnread(data) ? ' data-unread' : '';
|
|
43
|
+
const selectedAttr = data.selected ? ' data-selected' : '';
|
|
44
|
+
const title = escapeHtml(data.title || 'Untitled');
|
|
45
|
+
return `<li class="${className}__item" data-role="item"${threadIdAttr}${unreadAttr}${selectedAttr}><div class="${className}__title">${title}</div>${(templates.threadMenuBlock || threadMenuBlock)(layout, data)}</li>`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function threadMenuBlock({ className }) {
|
|
49
|
+
return `<button type="button" class="${className}__menu-button" data-role="thread-menu-button" aria-label="Thread actions" aria-haspopup="menu">${getIcon('dots-vertical')}</button>` +
|
|
50
|
+
`<div class="${className}__menu" data-role="thread-menu" role="menu" hidden>` +
|
|
51
|
+
`<button type="button" class="${className}__menu-item" data-role="thread-rename" role="menuitem">Rename</button>` +
|
|
52
|
+
`<button type="button" class="${className}__menu-item ${className}__menu-item--danger" data-role="thread-delete" role="menuitem">Delete</button>` +
|
|
53
|
+
`</div>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function message(layout, state, data) {
|
|
57
|
+
const { className, templates } = layout;
|
|
58
|
+
return [
|
|
59
|
+
`<div class="${className}__item-body" data-role="item">`,
|
|
60
|
+
(templates.messageQuestionBlock || messageQuestionBlock)(layout, data),
|
|
61
|
+
(templates.messageAnswerBlock || messageAnswerBlock)(layout, data),
|
|
62
|
+
`</div>`,
|
|
63
|
+
].join('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function messageQuestionBlock({ className }, message) {
|
|
67
|
+
const { question } = message;
|
|
68
|
+
const generatedBy = getGeneratedBy(message);
|
|
69
|
+
const author = messageAuthor(message);
|
|
70
|
+
// always rendered so it can be filled in place when the question text
|
|
71
|
+
// arrives later (the list renders incrementally); the authorship attrs
|
|
72
|
+
// drive the author label and the bubble color
|
|
73
|
+
const attrs = `${author ? ` data-author="${escapeHtml(author)}"` : ''}${generatedBy ? ` data-generated-by="${escapeHtml(generatedBy)}"` : ''}`;
|
|
74
|
+
return `<div class="${className}__question" data-role="question"${attrs}${question ? '' : ' hidden'}>${question ? escapeHtml(question) : ''}</div>`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The author label of a message's question bubble: labeled only when the
|
|
79
|
+
* question was written by the answer-updates monitor — the user's own
|
|
80
|
+
* questions carry no label.
|
|
81
|
+
*/
|
|
82
|
+
export function messageAuthor(message) {
|
|
83
|
+
return getGeneratedBy(message) === 'answer_update_monitor' ? 'Written by Miso' : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function messageAnswerBlock({ className }, { answer }) {
|
|
87
|
+
// the content is left blank here: the layout fills it in a second pass
|
|
88
|
+
// (markdown -> HTML); a spinner shows while the answer body is still
|
|
89
|
+
// being fetched from the answers API
|
|
90
|
+
const content = answer === undefined ?
|
|
91
|
+
`<div class="miso-loading" aria-label="Loading" data-role="loading"></div>` : '';
|
|
92
|
+
return `<div class="${className}__answer miso-markdown miso-circled-citation-index" data-role="answer">${content}</div>`;
|
|
93
|
+
}
|
|
94
|
+
|
|
33
95
|
export function article(layout, state, data, meta) {
|
|
34
96
|
const { templates } = layout;
|
|
35
97
|
const [openTag, closeTag] = tagPair(layout, data);
|
package/src/layout/text/text.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { LAYOUT_TYPE } from '../../constants.js';
|
|
1
2
|
import { escapeHtml } from '@miso.ai/commons';
|
|
2
3
|
import TemplateBasedLayout from '../template.js';
|
|
3
4
|
|
|
4
|
-
const TYPE =
|
|
5
|
+
const TYPE = LAYOUT_TYPE.TEXT;
|
|
5
6
|
const DEFAULT_CLASSNAME = 'miso-text';
|
|
6
7
|
|
|
7
8
|
function root(layout, state) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { trimObj, defineValues, Resolution, pacer } from '@miso.ai/commons';
|
|
2
|
-
import { STATUS } from '../../../constants.js';
|
|
2
|
+
import { STATUS, LAYOUT_TYPE } from '../../../constants.js';
|
|
3
3
|
import ProgressiveLayout from '../../progressive.js';
|
|
4
4
|
import PlaintextRenderer from './plaintext.js';
|
|
5
5
|
import { containerElement, cursorClassName, fromSameSession, normalizeOnDebug } from './utils.js';
|
|
6
6
|
|
|
7
|
-
const TYPE =
|
|
7
|
+
const TYPE = LAYOUT_TYPE.TYPEWRITER;
|
|
8
8
|
const DEFAULT_CLASSNAME = 'miso-typewriter';
|
|
9
9
|
|
|
10
10
|
export default class TypewriterLayout extends ProgressiveLayout {
|
|
@@ -67,10 +67,12 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async _setupForMarkdown() {
|
|
70
|
-
const
|
|
70
|
+
const client = this._view.workflow._client;
|
|
71
|
+
const plugin = await TypewriterLayout.MisoClient.plugins.install('std:ui-markdown');
|
|
71
72
|
if (!this._view) {
|
|
72
73
|
return; // destroyed
|
|
73
74
|
}
|
|
75
|
+
const context = plugin.getContext(client);
|
|
74
76
|
const cursorClass = cursorClassName(this.className);
|
|
75
77
|
const { onDebug, onCitationLink, variant } = this.options;
|
|
76
78
|
// TODO: options
|
package/src/plugin.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Component, Resolution, defineAndUpgrade, delegateGetters, defineValues } from '@miso.ai/commons';
|
|
2
|
-
import {
|
|
2
|
+
import { WorkflowPlugin } from '@miso.ai/client-sdk-workflow';
|
|
3
|
+
import { defaultLayouts } from './defaults.js';
|
|
3
4
|
import { AskCombo } from './combo/index.js';
|
|
4
5
|
import Layouts from './layouts.js';
|
|
5
6
|
import * as elements from './element/index.js';
|
|
6
7
|
import * as layouts from './layout/index.js';
|
|
7
|
-
import * as sources from './source.js';
|
|
8
8
|
import * as defaults from './defaults/index.js';
|
|
9
|
+
import { followUp as followUpTemplate } from './defaults/ask/templates.js';
|
|
9
10
|
import * as traits from './trait/index.js';
|
|
10
11
|
import { loadStylesIfNecessary } from './styles.js';
|
|
12
|
+
import confirm from './util/confirm.js';
|
|
13
|
+
import prompt from './util/prompt.js';
|
|
11
14
|
|
|
12
15
|
import MisoContainerElement from './element/container/miso-container.js';
|
|
13
16
|
import MisoContextElement from './element/context/miso-context.js';
|
|
@@ -24,20 +27,24 @@ export default class UiPlugin extends Component {
|
|
|
24
27
|
constructor() {
|
|
25
28
|
super('ui');
|
|
26
29
|
this.layouts = new Layouts(this);
|
|
27
|
-
this._recommendations = new WeakMap();
|
|
28
|
-
this._explores = new WeakMap();
|
|
29
|
-
this._asks = new WeakMap();
|
|
30
|
-
this._extensions = new WeakMap();
|
|
31
|
-
|
|
32
30
|
this._ready = new Resolution();
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
async install(MisoClient,
|
|
33
|
+
async install(MisoClient, context) {
|
|
34
|
+
const { addSubtree, addExtensionPoints } = context;
|
|
35
|
+
this._pluginContext = context;
|
|
36
36
|
addSubtree(this);
|
|
37
|
+
|
|
38
|
+
// make sure the workflow plugin is installed, then provide the layout
|
|
39
|
+
// registry and seed the default layout options
|
|
40
|
+
MisoClient.plugins.use(WorkflowPlugin);
|
|
41
|
+
context.workflows.plugin.setLayouts(this.layouts);
|
|
42
|
+
this._seedWorkflowDefaults(context.workflows.defaults);
|
|
43
|
+
|
|
37
44
|
MisoClient.on('create', this._injectClient.bind(this));
|
|
38
45
|
|
|
39
46
|
// the UI interface for MisoClient
|
|
40
|
-
const ui = { defaults, traits };
|
|
47
|
+
const ui = { defaults, traits, confirm, prompt };
|
|
41
48
|
delegateGetters(ui, this, ['layouts', 'ready']);
|
|
42
49
|
defineValues(this, { MisoClient });
|
|
43
50
|
defineValues(MisoClient, { ui });
|
|
@@ -78,78 +85,27 @@ export default class UiPlugin extends Component {
|
|
|
78
85
|
this._ready.resolve();
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
async requireExtension(name) {
|
|
82
|
-
switch (name) {
|
|
83
|
-
case 'markdown':
|
|
84
|
-
return await this.MisoClient.plugins.install('std:ui-markdown');
|
|
85
|
-
default:
|
|
86
|
-
throw new Error(`Unknown extension: ${name}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
88
|
get ready() {
|
|
91
89
|
return this._ready.promise;
|
|
92
90
|
}
|
|
93
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Seed the workflow plugin's defaults store with the presentational default
|
|
94
|
+
* options: layouts of each workflow, plus UI templates.
|
|
95
|
+
*/
|
|
96
|
+
_seedWorkflowDefaults(workflowDefaults) {
|
|
97
|
+
for (const [name, layouts] of Object.entries(defaultLayouts)) {
|
|
98
|
+
workflowDefaults.set(name, { layouts });
|
|
99
|
+
}
|
|
100
|
+
workflowDefaults.set('ask', { templates: { followUp: followUpTemplate } });
|
|
101
|
+
}
|
|
102
|
+
|
|
94
103
|
_injectClient(client) {
|
|
95
104
|
defineValues(client, {
|
|
96
105
|
ui: new Ui(this, client),
|
|
97
106
|
});
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
_getExtensions(client) {
|
|
101
|
-
if (this._extensions.has(client)) {
|
|
102
|
-
return this._extensions.get(client);
|
|
103
|
-
}
|
|
104
|
-
const extensions = new Extensions(this, client);
|
|
105
|
-
this._extensions.set(client, extensions);
|
|
106
|
-
return extensions;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
_getRecommendations(client) {
|
|
110
|
-
let recommendations = this._recommendations.get(client);
|
|
111
|
-
if (!recommendations) {
|
|
112
|
-
this._recommendations.set(client, recommendations = new Recommendations(this, client));
|
|
113
|
-
}
|
|
114
|
-
return recommendations;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
_getExplores(client) {
|
|
118
|
-
let explores = this._explores.get(client);
|
|
119
|
-
if (!explores) {
|
|
120
|
-
this._explores.set(client, explores = new Explores(this, client));
|
|
121
|
-
}
|
|
122
|
-
return explores;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
_getAsks(client) {
|
|
126
|
-
let asks = this._asks.get(client);
|
|
127
|
-
if (!asks) {
|
|
128
|
-
this._asks.set(client, asks = new Asks(this, client));
|
|
129
|
-
}
|
|
130
|
-
return asks;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// TODO: are we able to eliminate the concept of extension, just use plugins?
|
|
136
|
-
class Extensions {
|
|
137
|
-
|
|
138
|
-
constructor(plugin, client) {
|
|
139
|
-
this._plugin = plugin;
|
|
140
|
-
this._client = client;
|
|
141
|
-
this._contexts = {};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async require(name) {
|
|
145
|
-
if (this._contexts[name]) {
|
|
146
|
-
return this._contexts[name];
|
|
147
|
-
}
|
|
148
|
-
const plugin = await this._plugin.requireExtension(name);
|
|
149
|
-
const context = this._contexts[name] = plugin.getContext(this._client);
|
|
150
|
-
return context;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
109
|
}
|
|
154
110
|
|
|
155
111
|
function createUiPluginContext(plugin, ui, MisoClient) {
|
|
@@ -174,6 +130,9 @@ function createUiPluginContext(plugin, ui, MisoClient) {
|
|
|
174
130
|
return { plugin, addElementClasses, addLayoutClasses, addInterface };
|
|
175
131
|
}
|
|
176
132
|
|
|
133
|
+
// workflow accessors delegated to client.workflows for backward compatibility
|
|
134
|
+
const WORKFLOW_ACCESSORS = ['search', 'hybridSearch', 'asks', 'ask', 'explores', 'explore', 'recommendations', 'recommendation', 'sources'];
|
|
135
|
+
|
|
177
136
|
/**
|
|
178
137
|
* The UI interface for the client.
|
|
179
138
|
*/
|
|
@@ -182,52 +141,7 @@ class Ui {
|
|
|
182
141
|
constructor(plugin, client) {
|
|
183
142
|
this._plugin = plugin;
|
|
184
143
|
this._client = client;
|
|
185
|
-
|
|
186
|
-
defineValues(this, {
|
|
187
|
-
sources: {
|
|
188
|
-
api: sources.api(client),
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
get recommendations() {
|
|
194
|
-
return this._recommendations || (this._recommendations = this._plugin._getRecommendations(this._client));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
get recommendation() {
|
|
198
|
-
return this.recommendations.get(); // get default recommendation unit
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
get search() {
|
|
202
|
-
if (!this._search) {
|
|
203
|
-
this._search = new Search(this._plugin, this._client);
|
|
204
|
-
this._client._events.emit('postworkflow', this._search);
|
|
205
|
-
}
|
|
206
|
-
return this._search;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
get asks() {
|
|
210
|
-
return this._asks || (this._asks = this._plugin._getAsks(this._client));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
get ask() {
|
|
214
|
-
return this.asks.root;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
get hybridSearch() {
|
|
218
|
-
if (!this._hybridSearch) {
|
|
219
|
-
this._hybridSearch = new HybridSearch(this._plugin, this._client);
|
|
220
|
-
this._client._events.emit('postworkflow', this._hybridSearch);
|
|
221
|
-
}
|
|
222
|
-
return this._hybridSearch;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
get explores() {
|
|
226
|
-
return this._explores || (this._explores = this._plugin._getExplores(this._client));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
get explore() {
|
|
230
|
-
return this.explores.get(); // get default explore unit
|
|
144
|
+
delegateGetters(this, client.workflows, WORKFLOW_ACCESSORS);
|
|
231
145
|
}
|
|
232
146
|
|
|
233
147
|
get ready() {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { openModal } from './modal.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_CLASSNAME = 'miso-confirm';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Show a modal confirmation dialog (a native <dialog>), resolving to whether
|
|
7
|
+
* the user confirmed. Cancel button, backdrop click, and the Escape key all
|
|
8
|
+
* resolve to false. Exposed as `MisoClient.ui.confirm()`.
|
|
9
|
+
*/
|
|
10
|
+
export default function confirm({
|
|
11
|
+
title,
|
|
12
|
+
message,
|
|
13
|
+
confirmText = 'Confirm',
|
|
14
|
+
cancelText = 'Cancel',
|
|
15
|
+
danger = false,
|
|
16
|
+
className = DEFAULT_CLASSNAME,
|
|
17
|
+
} = {}) {
|
|
18
|
+
return openModal({
|
|
19
|
+
className,
|
|
20
|
+
title,
|
|
21
|
+
message,
|
|
22
|
+
confirmText,
|
|
23
|
+
cancelText,
|
|
24
|
+
danger,
|
|
25
|
+
result: () => true,
|
|
26
|
+
cancelValue: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { escapeHtml } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shared modal dialog core of confirm() and prompt(): a native <dialog>
|
|
5
|
+
* with title, message, an optional body, and cancel/confirm actions. Resolves
|
|
6
|
+
* with `result(dialog)` when confirmed; the cancel button, a backdrop click,
|
|
7
|
+
* and the Escape key all resolve with `cancelValue`.
|
|
8
|
+
*/
|
|
9
|
+
export function openModal({
|
|
10
|
+
className,
|
|
11
|
+
title,
|
|
12
|
+
message,
|
|
13
|
+
body = '',
|
|
14
|
+
confirmText = 'Confirm',
|
|
15
|
+
cancelText = 'Cancel',
|
|
16
|
+
danger = false,
|
|
17
|
+
onOpen,
|
|
18
|
+
result = () => true,
|
|
19
|
+
cancelValue,
|
|
20
|
+
} = {}) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const dialog = document.createElement('dialog');
|
|
23
|
+
dialog.className = className;
|
|
24
|
+
dialog.innerHTML = [
|
|
25
|
+
title ? `<div class="${className}__title">${escapeHtml(title)}</div>` : '',
|
|
26
|
+
message ? `<div class="${className}__message">${escapeHtml(message)}</div>` : '',
|
|
27
|
+
body,
|
|
28
|
+
`<div class="${className}__actions">`,
|
|
29
|
+
`<button type="button" class="${className}__button ${className}__cancel-button" data-role="cancel">${escapeHtml(cancelText)}</button>`,
|
|
30
|
+
`<button type="button" class="${className}__button ${className}__confirm-button${danger ? ` ${className}__confirm-button--danger` : ''}" data-role="confirm">${escapeHtml(confirmText)}</button>`,
|
|
31
|
+
`</div>`,
|
|
32
|
+
].join('');
|
|
33
|
+
dialog.addEventListener('click', (event) => {
|
|
34
|
+
if (event.target.closest('[data-role="confirm"]')) {
|
|
35
|
+
dialog.close('confirm');
|
|
36
|
+
} else if (event.target.closest('[data-role="cancel"]') || event.target === dialog) {
|
|
37
|
+
dialog.close(); // cancel button or backdrop click
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
dialog.addEventListener('close', () => {
|
|
41
|
+
const value = dialog.returnValue === 'confirm' ? result(dialog) : cancelValue;
|
|
42
|
+
dialog.remove();
|
|
43
|
+
resolve(value);
|
|
44
|
+
}, { once: true });
|
|
45
|
+
document.body.appendChild(dialog);
|
|
46
|
+
dialog.showModal();
|
|
47
|
+
onOpen && onOpen(dialog);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { escapeHtml } from '@miso.ai/commons';
|
|
2
|
+
import { openModal } from './modal.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CLASSNAME = 'miso-prompt';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Show a modal input dialog (a native <dialog>), resolving to the entered
|
|
8
|
+
* (trimmed) text when confirmed. Cancel button, backdrop click, and the
|
|
9
|
+
* Escape key all resolve to undefined. The Enter key confirms. Exposed as
|
|
10
|
+
* `MisoClient.ui.prompt()`.
|
|
11
|
+
*/
|
|
12
|
+
export default function prompt({
|
|
13
|
+
title,
|
|
14
|
+
message,
|
|
15
|
+
value = '',
|
|
16
|
+
placeholder,
|
|
17
|
+
confirmText = 'Confirm',
|
|
18
|
+
cancelText = 'Cancel',
|
|
19
|
+
className = DEFAULT_CLASSNAME,
|
|
20
|
+
} = {}) {
|
|
21
|
+
return openModal({
|
|
22
|
+
className,
|
|
23
|
+
title,
|
|
24
|
+
message,
|
|
25
|
+
body: `<input class="${className}__input" type="text" data-role="input" value="${escapeHtml(value)}"${placeholder ? ` placeholder="${escapeHtml(placeholder)}"` : ''}>`,
|
|
26
|
+
confirmText,
|
|
27
|
+
cancelText,
|
|
28
|
+
onOpen: (dialog) => {
|
|
29
|
+
const input = dialog.querySelector('[data-role="input"]');
|
|
30
|
+
input.addEventListener('keydown', (event) => {
|
|
31
|
+
if (event.key === 'Enter') {
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
dialog.close('confirm');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
input.focus();
|
|
37
|
+
input.select();
|
|
38
|
+
},
|
|
39
|
+
result: dialog => dialog.querySelector('[data-role="input"]').value.trim(),
|
|
40
|
+
cancelValue: undefined,
|
|
41
|
+
});
|
|
42
|
+
}
|