@use-kona/editor 0.1.17 → 0.1.19
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/plugins/CommandsPlugin/Menu.js +69 -68
- package/dist/plugins/CommandsPlugin/resolveCommands.d.ts +0 -1
- package/dist/plugins/CommandsPlugin/resolveCommands.js +21 -30
- package/dist/plugins/CommandsPlugin/resolveCommands.spec.js +58 -12
- package/dist/plugins/CommandsPlugin/styles.module.js +0 -2
- package/dist/plugins/CommandsPlugin/styles_module.css +2 -18
- package/dist/plugins/CommandsPlugin/types.d.ts +5 -0
- package/dist/plugins/CommandsPlugin/useResolvedCommands.js +23 -3
- package/package.json +1 -1
- package/src/plugins/CommandsPlugin/Menu.tsx +65 -53
- package/src/plugins/CommandsPlugin/resolveCommands.spec.ts +30 -13
- package/src/plugins/CommandsPlugin/resolveCommands.ts +40 -38
- package/src/plugins/CommandsPlugin/styles.module.css +2 -18
- package/src/plugins/CommandsPlugin/types.ts +5 -0
- package/src/plugins/CommandsPlugin/useResolvedCommands.ts +28 -7
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useStore } from "@nanostores/react";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
|
-
import { Editor } from "slate";
|
|
6
|
+
import { Editor, Transforms } from "slate";
|
|
7
7
|
import { useFocused, useSlate, useSlateSelection } from "slate-react";
|
|
8
8
|
import { insert, insertText, removeCommand, set, wrap } from "./actions.js";
|
|
9
9
|
import styles_module from "./styles.module.js";
|
|
@@ -23,7 +23,6 @@ const Menu = (props)=>{
|
|
|
23
23
|
match: (n)=>Editor.isBlock(editor, n)
|
|
24
24
|
});
|
|
25
25
|
const isBrowseMode = 'string' == typeof store.filter && '' === store.filter;
|
|
26
|
-
const isSearchMode = 'string' == typeof store.filter && '' !== store.filter;
|
|
27
26
|
const { commands, isLoading, isError } = useResolvedCommands({
|
|
28
27
|
rootCommands,
|
|
29
28
|
filter: store.filter,
|
|
@@ -48,6 +47,26 @@ const Menu = (props)=>{
|
|
|
48
47
|
isBrowseMode,
|
|
49
48
|
path.length
|
|
50
49
|
]);
|
|
50
|
+
const enterSubmenu = useCallback((nextPath)=>{
|
|
51
|
+
if (!isBrowseMode && 'string' == typeof store.filter && store.filter) {
|
|
52
|
+
const focus = selection?.focus ?? editor.selection?.focus;
|
|
53
|
+
if (focus) Transforms["delete"](editor, {
|
|
54
|
+
at: focus,
|
|
55
|
+
distance: store.filter.length,
|
|
56
|
+
reverse: true,
|
|
57
|
+
unit: 'character'
|
|
58
|
+
});
|
|
59
|
+
$store.setKey('filter', '');
|
|
60
|
+
}
|
|
61
|
+
setPath(nextPath);
|
|
62
|
+
setActive(0);
|
|
63
|
+
}, [
|
|
64
|
+
editor,
|
|
65
|
+
isBrowseMode,
|
|
66
|
+
selection,
|
|
67
|
+
store.filter,
|
|
68
|
+
$store
|
|
69
|
+
]);
|
|
51
70
|
const actions = useMemo(()=>({
|
|
52
71
|
removeCommand: removeCommand(editor, selection, store.filter),
|
|
53
72
|
set: set(editor, selection, store.filter),
|
|
@@ -67,9 +86,8 @@ const Menu = (props)=>{
|
|
|
67
86
|
store.openId
|
|
68
87
|
]);
|
|
69
88
|
useEffect(()=>{
|
|
70
|
-
if (false === store.filter ||
|
|
89
|
+
if (false === store.filter || 'string' == typeof store.filter && '' !== store.filter) setActive(0);
|
|
71
90
|
}, [
|
|
72
|
-
isSearchMode,
|
|
73
91
|
store.filter
|
|
74
92
|
]);
|
|
75
93
|
useEffect(()=>{
|
|
@@ -141,9 +159,7 @@ const Menu = (props)=>{
|
|
|
141
159
|
if (!entry || 'command' !== entry.type || !entry.command.isSubmenu) return;
|
|
142
160
|
event.preventDefault();
|
|
143
161
|
event.stopPropagation();
|
|
144
|
-
|
|
145
|
-
if (!isBrowseMode) $store.setKey('filter', '');
|
|
146
|
-
setActive(0);
|
|
162
|
+
enterSubmenu(entry.command.path);
|
|
147
163
|
break;
|
|
148
164
|
}
|
|
149
165
|
case 'ArrowLeft':
|
|
@@ -166,9 +182,7 @@ const Menu = (props)=>{
|
|
|
166
182
|
}
|
|
167
183
|
if ('command' !== entry.type) break;
|
|
168
184
|
if (entry.command.isSubmenu) {
|
|
169
|
-
|
|
170
|
-
if (!isBrowseMode) $store.setKey('filter', '');
|
|
171
|
-
setActive(0);
|
|
185
|
+
enterSubmenu(entry.command.path);
|
|
172
186
|
break;
|
|
173
187
|
}
|
|
174
188
|
entry.command.command.action?.(actions, editor);
|
|
@@ -191,15 +205,15 @@ const Menu = (props)=>{
|
|
|
191
205
|
active,
|
|
192
206
|
entries,
|
|
193
207
|
editor,
|
|
208
|
+
enterSubmenu,
|
|
194
209
|
isBrowseMode,
|
|
195
210
|
path.length,
|
|
196
211
|
store.isOpen,
|
|
197
212
|
$store
|
|
198
213
|
]);
|
|
199
|
-
const hasRows = entries.length > 0 ||
|
|
214
|
+
const hasRows = entries.length > 0 || isError;
|
|
200
215
|
if (false === store.filter || !hasRows) return null;
|
|
201
216
|
if (entry && ignoreNodes.includes(entry[0].type)) return null;
|
|
202
|
-
const pathLabel = path.map((item)=>item.title).join(' / ');
|
|
203
217
|
return /*#__PURE__*/ createPortal(renderMenu(/*#__PURE__*/ jsxs(Fragment, {
|
|
204
218
|
children: [
|
|
205
219
|
store.isOpen && /*#__PURE__*/ jsx("div", {
|
|
@@ -216,10 +230,6 @@ const Menu = (props)=>{
|
|
|
216
230
|
event.preventDefault();
|
|
217
231
|
},
|
|
218
232
|
children: [
|
|
219
|
-
isBrowseMode && pathLabel && /*#__PURE__*/ jsx("div", {
|
|
220
|
-
className: styles_module.path,
|
|
221
|
-
children: pathLabel
|
|
222
|
-
}),
|
|
223
233
|
entries.map((entry, index)=>{
|
|
224
234
|
if ('back' === entry.type) return /*#__PURE__*/ jsxs("button", {
|
|
225
235
|
type: "button",
|
|
@@ -247,7 +257,7 @@ const Menu = (props)=>{
|
|
|
247
257
|
})
|
|
248
258
|
]
|
|
249
259
|
}, "back");
|
|
250
|
-
return /*#__PURE__*/
|
|
260
|
+
return /*#__PURE__*/ jsx("button", {
|
|
251
261
|
type: "button",
|
|
252
262
|
ref: (element)=>{
|
|
253
263
|
refs.current[index] = element;
|
|
@@ -257,64 +267,55 @@ const Menu = (props)=>{
|
|
|
257
267
|
}),
|
|
258
268
|
onMouseDown: (event)=>{
|
|
259
269
|
event.preventDefault();
|
|
260
|
-
if (entry.command.isSubmenu)
|
|
261
|
-
setPath(entry.command.path);
|
|
262
|
-
if (!isBrowseMode) $store.setKey('filter', '');
|
|
263
|
-
setActive(0);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
270
|
+
if (entry.command.isSubmenu) return void enterSubmenu(entry.command.path);
|
|
266
271
|
entry.command.command.action?.(actions, editor);
|
|
267
272
|
$store.setKey('isOpen', false);
|
|
268
273
|
},
|
|
269
|
-
children:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
children: entry.command.command.render?.({
|
|
275
|
+
command: entry.command.command,
|
|
276
|
+
isSubmenu: entry.command.isSubmenu,
|
|
277
|
+
isActive: index === active
|
|
278
|
+
}) ?? /*#__PURE__*/ jsxs(Fragment, {
|
|
279
|
+
children: [
|
|
280
|
+
/*#__PURE__*/ jsx("span", {
|
|
281
|
+
className: styles_module.icon,
|
|
282
|
+
children: entry.command.command.icon
|
|
283
|
+
}),
|
|
284
|
+
/*#__PURE__*/ jsx("span", {
|
|
285
|
+
className: styles_module.content,
|
|
286
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
278
287
|
children: entry.command.command.title
|
|
279
|
-
}),
|
|
280
|
-
isSearchMode && entry.command.breadcrumb && /*#__PURE__*/ jsx("span", {
|
|
281
|
-
className: styles_module.breadcrumb,
|
|
282
|
-
children: entry.command.breadcrumb
|
|
283
288
|
})
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
289
|
+
}),
|
|
290
|
+
entry.command.isSubmenu && /*#__PURE__*/ jsx("span", {
|
|
291
|
+
className: styles_module.submenu,
|
|
292
|
+
"aria-hidden": "true",
|
|
293
|
+
children: /*#__PURE__*/ jsxs("svg", {
|
|
294
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
295
|
+
width: "12",
|
|
296
|
+
height: "12",
|
|
297
|
+
viewBox: "0 0 24 24",
|
|
298
|
+
fill: "none",
|
|
299
|
+
stroke: "currentColor",
|
|
300
|
+
strokeWidth: "2",
|
|
301
|
+
strokeLinecap: "round",
|
|
302
|
+
strokeLinejoin: "round",
|
|
303
|
+
children: [
|
|
304
|
+
/*#__PURE__*/ jsx("path", {
|
|
305
|
+
stroke: "none",
|
|
306
|
+
d: "M0 0h24v24H0z",
|
|
307
|
+
fill: "none"
|
|
308
|
+
}),
|
|
309
|
+
/*#__PURE__*/ jsx("path", {
|
|
310
|
+
d: "M9 6l6 6l-6 6"
|
|
311
|
+
})
|
|
312
|
+
]
|
|
313
|
+
})
|
|
309
314
|
})
|
|
310
|
-
|
|
311
|
-
|
|
315
|
+
]
|
|
316
|
+
})
|
|
312
317
|
}, entry.command.key);
|
|
313
318
|
}),
|
|
314
|
-
isLoading && /*#__PURE__*/ jsx("div", {
|
|
315
|
-
className: styles_module.systemRow,
|
|
316
|
-
children: "Loading..."
|
|
317
|
-
}),
|
|
318
319
|
isError && /*#__PURE__*/ jsx("div", {
|
|
319
320
|
className: styles_module.systemRow,
|
|
320
321
|
children: "Could not load commands"
|
|
@@ -18,17 +18,21 @@ const toResolvedCommand = (command, parentPath)=>{
|
|
|
18
18
|
command,
|
|
19
19
|
path,
|
|
20
20
|
key: pathToKey(path),
|
|
21
|
-
breadcrumb: parentPath.map((item)=>item.title).join(' / '),
|
|
22
21
|
isSubmenu: Boolean(command.getCommands)
|
|
23
22
|
};
|
|
24
23
|
};
|
|
25
|
-
const
|
|
24
|
+
const resolveCurrentLevelCommands = async (params, resolveChildCommands, queryForCurrentLevel)=>{
|
|
26
25
|
const { rootCommands, path, editor } = params;
|
|
27
26
|
let currentCommands = rootCommands;
|
|
28
27
|
let currentPath = [];
|
|
29
|
-
for
|
|
28
|
+
for(let index = 0; index < path.length; index++){
|
|
29
|
+
const item = path[index];
|
|
30
|
+
const isLastPathItem = index === path.length - 1;
|
|
30
31
|
const command = currentCommands.find((entry)=>entry.name === item.name);
|
|
31
|
-
if (!command?.getCommands) return
|
|
32
|
+
if (!command?.getCommands) return {
|
|
33
|
+
commands: [],
|
|
34
|
+
path: currentPath
|
|
35
|
+
};
|
|
32
36
|
currentPath = [
|
|
33
37
|
...currentPath,
|
|
34
38
|
toPathEntry(command)
|
|
@@ -36,37 +40,24 @@ const resolveBrowseCommands = async (params, resolveChildCommands)=>{
|
|
|
36
40
|
currentCommands = await resolveChildCommands({
|
|
37
41
|
command,
|
|
38
42
|
path: currentPath,
|
|
39
|
-
query: '',
|
|
43
|
+
query: isLastPathItem ? queryForCurrentLevel : '',
|
|
40
44
|
editor
|
|
41
45
|
});
|
|
42
46
|
}
|
|
43
|
-
return
|
|
47
|
+
return {
|
|
48
|
+
commands: currentCommands,
|
|
49
|
+
path: currentPath
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const resolveBrowseCommands = async (params, resolveChildCommands)=>{
|
|
53
|
+
const currentLevel = await resolveCurrentLevelCommands(params, resolveChildCommands, '');
|
|
54
|
+
return currentLevel.commands.map((command)=>toResolvedCommand(command, currentLevel.path));
|
|
44
55
|
};
|
|
45
56
|
const resolveSearchCommands = async (params, resolveChildCommands)=>{
|
|
46
|
-
const {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
if (command.getCommands) {
|
|
51
|
-
if (isCommandMatchesQuery(command, filter)) commands.push(toResolvedCommand(command, parentPath));
|
|
52
|
-
const currentPath = [
|
|
53
|
-
...parentPath,
|
|
54
|
-
toPathEntry(command)
|
|
55
|
-
];
|
|
56
|
-
const children = await resolveChildCommands({
|
|
57
|
-
command,
|
|
58
|
-
path: currentPath,
|
|
59
|
-
query: filter,
|
|
60
|
-
editor
|
|
61
|
-
});
|
|
62
|
-
await walk(children, currentPath);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (command.action && isCommandMatchesQuery(command, filter)) commands.push(toResolvedCommand(command, parentPath));
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
await walk(rootCommands, []);
|
|
69
|
-
return commands;
|
|
57
|
+
const { filter } = params;
|
|
58
|
+
const currentLevel = await resolveCurrentLevelCommands(params, resolveChildCommands, filter);
|
|
59
|
+
const matchedCommands = currentLevel.commands.filter((command)=>isCommandMatchesQuery(command, filter));
|
|
60
|
+
return matchedCommands.map((command)=>toResolvedCommand(command, currentLevel.path));
|
|
70
61
|
};
|
|
71
62
|
class CommandsResolver {
|
|
72
63
|
cache = new Map();
|
|
@@ -110,7 +110,7 @@ describe('CommandsResolver', ()=>{
|
|
|
110
110
|
expect(result.commands).toHaveLength(1);
|
|
111
111
|
expect(result.commands[0]?.command.name).toBe('code');
|
|
112
112
|
});
|
|
113
|
-
it('
|
|
113
|
+
it('searches only root level when path is empty', async ()=>{
|
|
114
114
|
const resolver = new CommandsResolver();
|
|
115
115
|
const rootCommands = [
|
|
116
116
|
{
|
|
@@ -120,14 +120,37 @@ describe('CommandsResolver', ()=>{
|
|
|
120
120
|
icon: null,
|
|
121
121
|
getCommands: ()=>[
|
|
122
122
|
leaf({
|
|
123
|
-
name: '
|
|
124
|
-
title: '
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
name: 'code',
|
|
124
|
+
title: 'Code'
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
leaf({
|
|
129
|
+
name: 'paragraph',
|
|
130
|
+
title: 'Paragraph'
|
|
131
|
+
})
|
|
132
|
+
];
|
|
133
|
+
const request = resolver.resolve({
|
|
134
|
+
rootCommands,
|
|
135
|
+
filter: 'code',
|
|
136
|
+
path: [],
|
|
137
|
+
editor
|
|
138
|
+
});
|
|
139
|
+
const result = await request.promise;
|
|
140
|
+
expect(result.commands).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
it('searches only current nested level', async ()=>{
|
|
143
|
+
const resolver = new CommandsResolver();
|
|
144
|
+
const rootCommands = [
|
|
145
|
+
{
|
|
146
|
+
name: 'insert',
|
|
147
|
+
title: 'Insert',
|
|
148
|
+
commandName: 'insert',
|
|
149
|
+
icon: null,
|
|
150
|
+
getCommands: ()=>[
|
|
127
151
|
leaf({
|
|
128
152
|
name: 'code',
|
|
129
|
-
title: 'Code'
|
|
130
|
-
commandName: 'code'
|
|
153
|
+
title: 'Code'
|
|
131
154
|
})
|
|
132
155
|
]
|
|
133
156
|
}
|
|
@@ -135,13 +158,18 @@ describe('CommandsResolver', ()=>{
|
|
|
135
158
|
const request = resolver.resolve({
|
|
136
159
|
rootCommands,
|
|
137
160
|
filter: 'code',
|
|
138
|
-
path: [
|
|
161
|
+
path: [
|
|
162
|
+
{
|
|
163
|
+
name: 'insert',
|
|
164
|
+
title: 'Insert',
|
|
165
|
+
commandName: 'insert'
|
|
166
|
+
}
|
|
167
|
+
],
|
|
139
168
|
editor
|
|
140
169
|
});
|
|
141
170
|
const result = await request.promise;
|
|
142
171
|
expect(result.commands).toHaveLength(1);
|
|
143
172
|
expect(result.commands[0]?.command.name).toBe('code');
|
|
144
|
-
expect(result.commands[0]?.breadcrumb).toBe('Insert');
|
|
145
173
|
});
|
|
146
174
|
it('returns matching submenu commands in query mode', async ()=>{
|
|
147
175
|
const resolver = new CommandsResolver();
|
|
@@ -189,13 +217,25 @@ describe('CommandsResolver', ()=>{
|
|
|
189
217
|
const firstRequest = resolver.resolve({
|
|
190
218
|
rootCommands,
|
|
191
219
|
filter: 'a',
|
|
192
|
-
path: [
|
|
220
|
+
path: [
|
|
221
|
+
{
|
|
222
|
+
name: 'remote',
|
|
223
|
+
title: 'Remote',
|
|
224
|
+
commandName: 'remote'
|
|
225
|
+
}
|
|
226
|
+
],
|
|
193
227
|
editor
|
|
194
228
|
});
|
|
195
229
|
const secondRequest = resolver.resolve({
|
|
196
230
|
rootCommands,
|
|
197
231
|
filter: 'b',
|
|
198
|
-
path: [
|
|
232
|
+
path: [
|
|
233
|
+
{
|
|
234
|
+
name: 'remote',
|
|
235
|
+
title: 'Remote',
|
|
236
|
+
commandName: 'remote'
|
|
237
|
+
}
|
|
238
|
+
],
|
|
199
239
|
editor
|
|
200
240
|
});
|
|
201
241
|
searchB.resolve([
|
|
@@ -257,7 +297,13 @@ describe('CommandsResolver', ()=>{
|
|
|
257
297
|
const loadingRequest = resolver.resolve({
|
|
258
298
|
rootCommands,
|
|
259
299
|
filter: 'code',
|
|
260
|
-
path: [
|
|
300
|
+
path: [
|
|
301
|
+
{
|
|
302
|
+
name: 'remote',
|
|
303
|
+
title: 'Remote',
|
|
304
|
+
commandName: 'remote'
|
|
305
|
+
}
|
|
306
|
+
],
|
|
261
307
|
editor
|
|
262
308
|
});
|
|
263
309
|
expect(loadingRequest.state.isLoading).toBe(true);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
will-change: transform, opacity;
|
|
9
9
|
contain: layout paint style;
|
|
10
10
|
backface-visibility: hidden;
|
|
11
|
-
border-radius:
|
|
11
|
+
border-radius: 8px;
|
|
12
12
|
flex-direction: column;
|
|
13
13
|
max-height: 200px;
|
|
14
14
|
margin-top: -6px;
|
|
@@ -70,32 +70,16 @@
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
.content-VZW7lK {
|
|
73
|
-
flex-direction:
|
|
73
|
+
flex-direction: row;
|
|
74
74
|
flex: 1;
|
|
75
|
-
row-gap: 2px;
|
|
76
75
|
min-width: 0;
|
|
77
76
|
display: flex;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
.breadcrumb-Jr0wXh {
|
|
81
|
-
color: var(--kona-editor-secondary-text-color, #777);
|
|
82
|
-
white-space: nowrap;
|
|
83
|
-
text-overflow: ellipsis;
|
|
84
|
-
font-size: 11px;
|
|
85
|
-
overflow: hidden;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
79
|
.submenu-zNsqOg {
|
|
89
80
|
color: var(--kona-editor-secondary-text-color, #777);
|
|
90
81
|
}
|
|
91
82
|
|
|
92
|
-
.path-fYY_14 {
|
|
93
|
-
color: var(--kona-editor-secondary-text-color, #777);
|
|
94
|
-
border-bottom: 1px solid var(--kona-editor-border-color, #ddd);
|
|
95
|
-
padding: 6px 8px;
|
|
96
|
-
font-size: 11px;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
83
|
.systemRow-ln5twO {
|
|
100
84
|
min-height: 32px;
|
|
101
85
|
color: var(--kona-editor-secondary-text-color, #777);
|
|
@@ -29,6 +29,11 @@ export type Command = {
|
|
|
29
29
|
icon: ReactNode;
|
|
30
30
|
action?: (actions: Actions, editor: Editor) => void;
|
|
31
31
|
getCommands?: (context: GetCommandsContext) => Command[] | Promise<Command[]>;
|
|
32
|
+
render?: (params: {
|
|
33
|
+
command: Command;
|
|
34
|
+
isSubmenu: boolean;
|
|
35
|
+
isActive: boolean;
|
|
36
|
+
}) => ReactNode;
|
|
32
37
|
};
|
|
33
38
|
export type Actions = {
|
|
34
39
|
removeCommand: () => void;
|
|
@@ -10,14 +10,35 @@ const useResolvedCommands = (params)=>{
|
|
|
10
10
|
const { rootCommands, filter, path, editor, isOpen } = params;
|
|
11
11
|
const resolverRef = useRef(new CommandsResolver());
|
|
12
12
|
const [state, setState] = useState(EMPTY_STATE);
|
|
13
|
+
const prevIsOpenRef = useRef(false);
|
|
14
|
+
const prevPathKeyRef = useRef('');
|
|
15
|
+
const prevQueryRef = useRef('');
|
|
13
16
|
const query = 'string' == typeof filter ? filter : '';
|
|
14
17
|
useEffect(()=>{
|
|
15
|
-
if (!isOpen || false === filter)
|
|
16
|
-
|
|
18
|
+
if (!isOpen || false === filter) {
|
|
19
|
+
setState(EMPTY_STATE);
|
|
20
|
+
prevIsOpenRef.current = false;
|
|
21
|
+
prevPathKeyRef.current = '';
|
|
22
|
+
prevQueryRef.current = '';
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const pathKey = path.map((item)=>item.name).join('/');
|
|
26
|
+
const isNewSession = !prevIsOpenRef.current;
|
|
27
|
+
const isPathChanged = !isNewSession && prevPathKeyRef.current !== pathKey;
|
|
28
|
+
const isQueryChanged = !isNewSession && prevQueryRef.current !== query;
|
|
29
|
+
if (isNewSession || isPathChanged) setState({
|
|
17
30
|
commands: [],
|
|
18
31
|
isLoading: true,
|
|
19
32
|
isError: false
|
|
20
33
|
});
|
|
34
|
+
else if (isQueryChanged) setState((state)=>({
|
|
35
|
+
...state,
|
|
36
|
+
isLoading: true,
|
|
37
|
+
isError: false
|
|
38
|
+
}));
|
|
39
|
+
prevIsOpenRef.current = true;
|
|
40
|
+
prevPathKeyRef.current = pathKey;
|
|
41
|
+
prevQueryRef.current = query;
|
|
21
42
|
const timeout = window.setTimeout(()=>{
|
|
22
43
|
const request = resolverRef.current.resolve({
|
|
23
44
|
rootCommands,
|
|
@@ -25,7 +46,6 @@ const useResolvedCommands = (params)=>{
|
|
|
25
46
|
path,
|
|
26
47
|
editor
|
|
27
48
|
});
|
|
28
|
-
setState(request.state);
|
|
29
49
|
request.promise.then((resolved)=>{
|
|
30
50
|
setState(resolved);
|
|
31
51
|
});
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import type { MapStore } from 'nanostores';
|
|
|
4
4
|
import {
|
|
5
5
|
type CSSProperties,
|
|
6
6
|
type ReactNode,
|
|
7
|
+
useCallback,
|
|
7
8
|
useEffect,
|
|
8
9
|
useLayoutEffect,
|
|
9
10
|
useMemo,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
useState,
|
|
12
13
|
} from 'react';
|
|
13
14
|
import { createPortal } from 'react-dom';
|
|
14
|
-
import { Editor } from 'slate';
|
|
15
|
+
import { Editor, Transforms } from 'slate';
|
|
15
16
|
import { useFocused, useSlate, useSlateSelection } from 'slate-react';
|
|
16
17
|
import type { CustomElement } from '../../../types';
|
|
17
18
|
import { insert, insertText, removeCommand, set, wrap } from './actions';
|
|
@@ -44,7 +45,6 @@ export const Menu = (props: Props) => {
|
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
const isBrowseMode = typeof store.filter === 'string' && store.filter === '';
|
|
47
|
-
const isSearchMode = typeof store.filter === 'string' && store.filter !== '';
|
|
48
48
|
|
|
49
49
|
const { commands, isLoading, isError } = useResolvedCommands({
|
|
50
50
|
rootCommands,
|
|
@@ -66,6 +66,29 @@ export const Menu = (props: Props) => {
|
|
|
66
66
|
return commandEntries;
|
|
67
67
|
}, [commands, isBrowseMode, path.length]);
|
|
68
68
|
|
|
69
|
+
const enterSubmenu = useCallback(
|
|
70
|
+
(nextPath: CommandPathEntry[]) => {
|
|
71
|
+
if (!isBrowseMode && typeof store.filter === 'string' && store.filter) {
|
|
72
|
+
const focus = selection?.focus ?? editor.selection?.focus;
|
|
73
|
+
|
|
74
|
+
if (focus) {
|
|
75
|
+
Transforms.delete(editor, {
|
|
76
|
+
at: focus,
|
|
77
|
+
distance: store.filter.length,
|
|
78
|
+
reverse: true,
|
|
79
|
+
unit: 'character',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
$store.setKey('filter', '');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setPath(nextPath);
|
|
87
|
+
setActive(0);
|
|
88
|
+
},
|
|
89
|
+
[editor, isBrowseMode, selection, store.filter, $store],
|
|
90
|
+
);
|
|
91
|
+
|
|
69
92
|
// biome-ignore lint/correctness/useExhaustiveDependencies: we care only about those deps
|
|
70
93
|
const actions = useMemo(() => {
|
|
71
94
|
return {
|
|
@@ -87,10 +110,13 @@ export const Menu = (props: Props) => {
|
|
|
87
110
|
}, [store.isOpen, store.openId]);
|
|
88
111
|
|
|
89
112
|
useEffect(() => {
|
|
90
|
-
if (
|
|
113
|
+
if (
|
|
114
|
+
store.filter === false ||
|
|
115
|
+
(typeof store.filter === 'string' && store.filter !== '')
|
|
116
|
+
) {
|
|
91
117
|
setActive(0);
|
|
92
118
|
}
|
|
93
|
-
}, [
|
|
119
|
+
}, [store.filter]);
|
|
94
120
|
|
|
95
121
|
useEffect(() => {
|
|
96
122
|
if (!entries.length) {
|
|
@@ -183,11 +209,7 @@ export const Menu = (props: Props) => {
|
|
|
183
209
|
|
|
184
210
|
event.preventDefault();
|
|
185
211
|
event.stopPropagation();
|
|
186
|
-
|
|
187
|
-
if (!isBrowseMode) {
|
|
188
|
-
$store.setKey('filter', '');
|
|
189
|
-
}
|
|
190
|
-
setActive(0);
|
|
212
|
+
enterSubmenu(entry.command.path);
|
|
191
213
|
break;
|
|
192
214
|
}
|
|
193
215
|
case 'ArrowLeft': {
|
|
@@ -221,11 +243,7 @@ export const Menu = (props: Props) => {
|
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
if (entry.command.isSubmenu) {
|
|
224
|
-
|
|
225
|
-
if (!isBrowseMode) {
|
|
226
|
-
$store.setKey('filter', '');
|
|
227
|
-
}
|
|
228
|
-
setActive(0);
|
|
246
|
+
enterSubmenu(entry.command.path);
|
|
229
247
|
break;
|
|
230
248
|
}
|
|
231
249
|
|
|
@@ -252,13 +270,14 @@ export const Menu = (props: Props) => {
|
|
|
252
270
|
active,
|
|
253
271
|
entries,
|
|
254
272
|
editor,
|
|
273
|
+
enterSubmenu,
|
|
255
274
|
isBrowseMode,
|
|
256
275
|
path.length,
|
|
257
276
|
store.isOpen,
|
|
258
277
|
$store,
|
|
259
278
|
]);
|
|
260
279
|
|
|
261
|
-
const hasRows = entries.length > 0 ||
|
|
280
|
+
const hasRows = entries.length > 0 || isError;
|
|
262
281
|
|
|
263
282
|
if (store.filter === false || !hasRows) {
|
|
264
283
|
return null;
|
|
@@ -268,8 +287,6 @@ export const Menu = (props: Props) => {
|
|
|
268
287
|
return null;
|
|
269
288
|
}
|
|
270
289
|
|
|
271
|
-
const pathLabel = path.map((item) => item.title).join(' / ');
|
|
272
|
-
|
|
273
290
|
return createPortal(
|
|
274
291
|
renderMenu(
|
|
275
292
|
<>
|
|
@@ -289,9 +306,6 @@ export const Menu = (props: Props) => {
|
|
|
289
306
|
event.preventDefault();
|
|
290
307
|
}}
|
|
291
308
|
>
|
|
292
|
-
{isBrowseMode && pathLabel && (
|
|
293
|
-
<div className={styles.path}>{pathLabel}</div>
|
|
294
|
-
)}
|
|
295
309
|
{entries.map((entry, index) => {
|
|
296
310
|
if (entry.type === 'back') {
|
|
297
311
|
return (
|
|
@@ -331,11 +345,7 @@ export const Menu = (props: Props) => {
|
|
|
331
345
|
onMouseDown={(event) => {
|
|
332
346
|
event.preventDefault();
|
|
333
347
|
if (entry.command.isSubmenu) {
|
|
334
|
-
|
|
335
|
-
if (!isBrowseMode) {
|
|
336
|
-
$store.setKey('filter', '');
|
|
337
|
-
}
|
|
338
|
-
setActive(0);
|
|
348
|
+
enterSubmenu(entry.command.path);
|
|
339
349
|
return;
|
|
340
350
|
}
|
|
341
351
|
|
|
@@ -343,39 +353,41 @@ export const Menu = (props: Props) => {
|
|
|
343
353
|
$store.setKey('isOpen', false);
|
|
344
354
|
}}
|
|
345
355
|
>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
<span className={styles.
|
|
353
|
-
{entry.command.
|
|
356
|
+
{entry.command.command.render?.({
|
|
357
|
+
command: entry.command.command,
|
|
358
|
+
isSubmenu: entry.command.isSubmenu,
|
|
359
|
+
isActive: index === active,
|
|
360
|
+
}) ?? (
|
|
361
|
+
<>
|
|
362
|
+
<span className={styles.icon}>
|
|
363
|
+
{entry.command.command.icon}
|
|
354
364
|
</span>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
365
|
+
<span className={styles.content}>
|
|
366
|
+
<span>{entry.command.command.title}</span>
|
|
367
|
+
</span>
|
|
368
|
+
{entry.command.isSubmenu && (
|
|
369
|
+
<span className={styles.submenu} aria-hidden="true">
|
|
370
|
+
<svg
|
|
371
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
372
|
+
width="12"
|
|
373
|
+
height="12"
|
|
374
|
+
viewBox="0 0 24 24"
|
|
375
|
+
fill="none"
|
|
376
|
+
stroke="currentColor"
|
|
377
|
+
strokeWidth="2"
|
|
378
|
+
strokeLinecap="round"
|
|
379
|
+
strokeLinejoin="round"
|
|
380
|
+
>
|
|
381
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
382
|
+
<path d="M9 6l6 6l-6 6" />
|
|
383
|
+
</svg>
|
|
384
|
+
</span>
|
|
385
|
+
)}
|
|
386
|
+
</>
|
|
374
387
|
)}
|
|
375
388
|
</button>
|
|
376
389
|
);
|
|
377
390
|
})}
|
|
378
|
-
{isLoading && <div className={styles.systemRow}>Loading...</div>}
|
|
379
391
|
{isError && (
|
|
380
392
|
<div className={styles.systemRow}>Could not load commands</div>
|
|
381
393
|
)}
|
|
@@ -102,7 +102,7 @@ describe('CommandsResolver', () => {
|
|
|
102
102
|
expect(result.commands[0]?.command.name).toBe('code');
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it('
|
|
105
|
+
it('searches only root level when path is empty', async () => {
|
|
106
106
|
const resolver = new CommandsResolver();
|
|
107
107
|
const rootCommands: Command[] = [
|
|
108
108
|
{
|
|
@@ -110,15 +110,9 @@ describe('CommandsResolver', () => {
|
|
|
110
110
|
title: 'Insert',
|
|
111
111
|
commandName: 'insert',
|
|
112
112
|
icon: null,
|
|
113
|
-
getCommands: () => [
|
|
114
|
-
leaf({
|
|
115
|
-
name: 'heading-1',
|
|
116
|
-
title: 'Heading 1',
|
|
117
|
-
commandName: 'heading1',
|
|
118
|
-
}),
|
|
119
|
-
leaf({ name: 'code', title: 'Code', commandName: 'code' }),
|
|
120
|
-
],
|
|
113
|
+
getCommands: () => [leaf({ name: 'code', title: 'Code' })],
|
|
121
114
|
},
|
|
115
|
+
leaf({ name: 'paragraph', title: 'Paragraph' }),
|
|
122
116
|
];
|
|
123
117
|
|
|
124
118
|
const request = resolver.resolve({
|
|
@@ -130,9 +124,32 @@ describe('CommandsResolver', () => {
|
|
|
130
124
|
|
|
131
125
|
const result = await request.promise;
|
|
132
126
|
|
|
127
|
+
expect(result.commands).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('searches only current nested level', async () => {
|
|
131
|
+
const resolver = new CommandsResolver();
|
|
132
|
+
const rootCommands: Command[] = [
|
|
133
|
+
{
|
|
134
|
+
name: 'insert',
|
|
135
|
+
title: 'Insert',
|
|
136
|
+
commandName: 'insert',
|
|
137
|
+
icon: null,
|
|
138
|
+
getCommands: () => [leaf({ name: 'code', title: 'Code' })],
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const request = resolver.resolve({
|
|
143
|
+
rootCommands,
|
|
144
|
+
filter: 'code',
|
|
145
|
+
path: [{ name: 'insert', title: 'Insert', commandName: 'insert' }],
|
|
146
|
+
editor,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const result = await request.promise;
|
|
150
|
+
|
|
133
151
|
expect(result.commands).toHaveLength(1);
|
|
134
152
|
expect(result.commands[0]?.command.name).toBe('code');
|
|
135
|
-
expect(result.commands[0]?.breadcrumb).toBe('Insert');
|
|
136
153
|
});
|
|
137
154
|
|
|
138
155
|
it('returns matching submenu commands in query mode', async () => {
|
|
@@ -185,13 +202,13 @@ describe('CommandsResolver', () => {
|
|
|
185
202
|
const firstRequest = resolver.resolve({
|
|
186
203
|
rootCommands,
|
|
187
204
|
filter: 'a',
|
|
188
|
-
path: [],
|
|
205
|
+
path: [{ name: 'remote', title: 'Remote', commandName: 'remote' }],
|
|
189
206
|
editor,
|
|
190
207
|
});
|
|
191
208
|
const secondRequest = resolver.resolve({
|
|
192
209
|
rootCommands,
|
|
193
210
|
filter: 'b',
|
|
194
|
-
path: [],
|
|
211
|
+
path: [{ name: 'remote', title: 'Remote', commandName: 'remote' }],
|
|
195
212
|
editor,
|
|
196
213
|
});
|
|
197
214
|
|
|
@@ -243,7 +260,7 @@ describe('CommandsResolver', () => {
|
|
|
243
260
|
const loadingRequest = resolver.resolve({
|
|
244
261
|
rootCommands,
|
|
245
262
|
filter: 'code',
|
|
246
|
-
path: [],
|
|
263
|
+
path: [{ name: 'remote', title: 'Remote', commandName: 'remote' }],
|
|
247
264
|
editor,
|
|
248
265
|
});
|
|
249
266
|
|
|
@@ -16,7 +16,6 @@ export type ResolvedCommand = {
|
|
|
16
16
|
command: Command;
|
|
17
17
|
key: string;
|
|
18
18
|
path: CommandPathEntry[];
|
|
19
|
-
breadcrumb: string;
|
|
20
19
|
isSubmenu: boolean;
|
|
21
20
|
};
|
|
22
21
|
|
|
@@ -84,36 +83,57 @@ const toResolvedCommand = (
|
|
|
84
83
|
command,
|
|
85
84
|
path,
|
|
86
85
|
key: pathToKey(path),
|
|
87
|
-
breadcrumb: parentPath.map((item) => item.title).join(' / '),
|
|
88
86
|
isSubmenu: Boolean(command.getCommands),
|
|
89
87
|
};
|
|
90
88
|
};
|
|
91
89
|
|
|
92
|
-
const
|
|
90
|
+
const resolveCurrentLevelCommands = async (
|
|
93
91
|
params: ResolveCommandsParams,
|
|
94
92
|
resolveChildCommands: ResolveChildCommands,
|
|
93
|
+
queryForCurrentLevel: string,
|
|
95
94
|
) => {
|
|
96
95
|
const { rootCommands, path, editor } = params;
|
|
97
96
|
let currentCommands = rootCommands;
|
|
98
97
|
let currentPath: CommandPathEntry[] = [];
|
|
99
98
|
|
|
100
|
-
for (
|
|
99
|
+
for (let index = 0; index < path.length; index++) {
|
|
100
|
+
const item = path[index];
|
|
101
|
+
const isLastPathItem = index === path.length - 1;
|
|
101
102
|
const command = currentCommands.find((entry) => entry.name === item.name);
|
|
102
103
|
if (!command?.getCommands) {
|
|
103
|
-
return
|
|
104
|
+
return {
|
|
105
|
+
commands: [],
|
|
106
|
+
path: currentPath,
|
|
107
|
+
};
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
currentPath = [...currentPath, toPathEntry(command)];
|
|
107
111
|
currentCommands = await resolveChildCommands({
|
|
108
112
|
command,
|
|
109
113
|
path: currentPath,
|
|
110
|
-
query: '',
|
|
114
|
+
query: isLastPathItem ? queryForCurrentLevel : '',
|
|
111
115
|
editor,
|
|
112
116
|
});
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
return
|
|
116
|
-
|
|
119
|
+
return {
|
|
120
|
+
commands: currentCommands,
|
|
121
|
+
path: currentPath,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const resolveBrowseCommands = async (
|
|
126
|
+
params: ResolveCommandsParams,
|
|
127
|
+
resolveChildCommands: ResolveChildCommands,
|
|
128
|
+
) => {
|
|
129
|
+
const currentLevel = await resolveCurrentLevelCommands(
|
|
130
|
+
params,
|
|
131
|
+
resolveChildCommands,
|
|
132
|
+
'',
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return currentLevel.commands.map((command) =>
|
|
136
|
+
toResolvedCommand(command, currentLevel.path),
|
|
117
137
|
);
|
|
118
138
|
};
|
|
119
139
|
|
|
@@ -121,38 +141,20 @@ const resolveSearchCommands = async (
|
|
|
121
141
|
params: ResolveCommandsParams,
|
|
122
142
|
resolveChildCommands: ResolveChildCommands,
|
|
123
143
|
) => {
|
|
124
|
-
const {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
): Promise<void> => {
|
|
131
|
-
for (const command of levelCommands) {
|
|
132
|
-
if (command.getCommands) {
|
|
133
|
-
if (isCommandMatchesQuery(command, filter)) {
|
|
134
|
-
commands.push(toResolvedCommand(command, parentPath));
|
|
135
|
-
}
|
|
144
|
+
const { filter } = params;
|
|
145
|
+
const currentLevel = await resolveCurrentLevelCommands(
|
|
146
|
+
params,
|
|
147
|
+
resolveChildCommands,
|
|
148
|
+
filter,
|
|
149
|
+
);
|
|
136
150
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
path: currentPath,
|
|
141
|
-
query: filter,
|
|
142
|
-
editor,
|
|
143
|
-
});
|
|
144
|
-
await walk(children, currentPath);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (command.action && isCommandMatchesQuery(command, filter)) {
|
|
149
|
-
commands.push(toResolvedCommand(command, parentPath));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
};
|
|
151
|
+
const matchedCommands = currentLevel.commands.filter((command) =>
|
|
152
|
+
isCommandMatchesQuery(command, filter),
|
|
153
|
+
);
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
return matchedCommands.map((command) =>
|
|
156
|
+
toResolvedCommand(command, currentLevel.path),
|
|
157
|
+
);
|
|
156
158
|
};
|
|
157
159
|
|
|
158
160
|
export class CommandsResolver {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
background-color: var(--kona-editor-background-color, #fff);
|
|
12
12
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.025);
|
|
13
13
|
border: 1px solid var(--kona-editor-border-color, #ddd);
|
|
14
|
-
border-radius:
|
|
14
|
+
border-radius: 8px;
|
|
15
15
|
transition:
|
|
16
16
|
transform 0.12s ease,
|
|
17
17
|
opacity 0.12s ease;
|
|
@@ -79,29 +79,13 @@
|
|
|
79
79
|
display: flex;
|
|
80
80
|
flex: 1;
|
|
81
81
|
min-width: 0;
|
|
82
|
-
flex-direction:
|
|
83
|
-
row-gap: 2px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.breadcrumb {
|
|
87
|
-
font-size: 11px;
|
|
88
|
-
color: var(--kona-editor-secondary-text-color, #777);
|
|
89
|
-
white-space: nowrap;
|
|
90
|
-
overflow: hidden;
|
|
91
|
-
text-overflow: ellipsis;
|
|
82
|
+
flex-direction: row;
|
|
92
83
|
}
|
|
93
84
|
|
|
94
85
|
.submenu {
|
|
95
86
|
color: var(--kona-editor-secondary-text-color, #777);
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
.path {
|
|
99
|
-
padding: 6px 8px;
|
|
100
|
-
font-size: 11px;
|
|
101
|
-
color: var(--kona-editor-secondary-text-color, #777);
|
|
102
|
-
border-bottom: 1px solid var(--kona-editor-border-color, #ddd);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
89
|
.systemRow {
|
|
106
90
|
min-height: 32px;
|
|
107
91
|
padding: 8px;
|
|
@@ -35,6 +35,11 @@ export type Command = {
|
|
|
35
35
|
icon: ReactNode;
|
|
36
36
|
action?: (actions: Actions, editor: Editor) => void;
|
|
37
37
|
getCommands?: (context: GetCommandsContext) => Command[] | Promise<Command[]>;
|
|
38
|
+
render?: (params: {
|
|
39
|
+
command: Command;
|
|
40
|
+
isSubmenu: boolean;
|
|
41
|
+
isActive: boolean;
|
|
42
|
+
}) => ReactNode;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
export type Actions = {
|
|
@@ -26,19 +26,42 @@ export const useResolvedCommands = (params: Params) => {
|
|
|
26
26
|
const { rootCommands, filter, path, editor, isOpen } = params;
|
|
27
27
|
const resolverRef = useRef(new CommandsResolver());
|
|
28
28
|
const [state, setState] = useState<ResolvedCommandsState>(EMPTY_STATE);
|
|
29
|
+
const prevIsOpenRef = useRef(false);
|
|
30
|
+
const prevPathKeyRef = useRef('');
|
|
31
|
+
const prevQueryRef = useRef('');
|
|
29
32
|
const query = typeof filter === 'string' ? filter : '';
|
|
30
33
|
|
|
31
34
|
useEffect(() => {
|
|
32
35
|
if (!isOpen || filter === false) {
|
|
33
36
|
setState(EMPTY_STATE);
|
|
37
|
+
prevIsOpenRef.current = false;
|
|
38
|
+
prevPathKeyRef.current = '';
|
|
39
|
+
prevQueryRef.current = '';
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const pathKey = path.map((item) => item.name).join('/');
|
|
44
|
+
const isNewSession = !prevIsOpenRef.current;
|
|
45
|
+
const isPathChanged = !isNewSession && prevPathKeyRef.current !== pathKey;
|
|
46
|
+
const isQueryChanged = !isNewSession && prevQueryRef.current !== query;
|
|
47
|
+
|
|
48
|
+
if (isNewSession || isPathChanged) {
|
|
49
|
+
setState({
|
|
50
|
+
commands: [],
|
|
51
|
+
isLoading: true,
|
|
52
|
+
isError: false,
|
|
53
|
+
});
|
|
54
|
+
} else if (isQueryChanged) {
|
|
55
|
+
setState((state) => ({
|
|
56
|
+
...state,
|
|
57
|
+
isLoading: true,
|
|
58
|
+
isError: false,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
prevIsOpenRef.current = true;
|
|
63
|
+
prevPathKeyRef.current = pathKey;
|
|
64
|
+
prevQueryRef.current = query;
|
|
42
65
|
|
|
43
66
|
const timeout = window.setTimeout(
|
|
44
67
|
() => {
|
|
@@ -49,8 +72,6 @@ export const useResolvedCommands = (params: Params) => {
|
|
|
49
72
|
editor,
|
|
50
73
|
});
|
|
51
74
|
|
|
52
|
-
setState(request.state);
|
|
53
|
-
|
|
54
75
|
request.promise.then((resolved) => {
|
|
55
76
|
setState(resolved);
|
|
56
77
|
});
|