@use-kona/editor 0.1.15 → 0.1.17
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/LICENSE +21 -0
- package/dist/plugins/CommandsPlugin/CommandsPlugin.d.ts +2 -3
- package/dist/plugins/CommandsPlugin/CommandsPlugin.js +5 -15
- package/dist/plugins/CommandsPlugin/Menu.d.ts +3 -3
- package/dist/plugins/CommandsPlugin/Menu.js +240 -69
- package/dist/plugins/CommandsPlugin/index.d.ts +1 -1
- package/dist/plugins/CommandsPlugin/resolveCommands.d.ts +34 -0
- package/dist/plugins/CommandsPlugin/resolveCommands.js +150 -0
- package/dist/plugins/CommandsPlugin/resolveCommands.spec.d.ts +1 -0
- package/dist/plugins/CommandsPlugin/resolveCommands.spec.js +277 -0
- package/dist/plugins/CommandsPlugin/styles.module.js +5 -0
- package/dist/plugins/CommandsPlugin/styles_module.css +47 -7
- package/dist/plugins/CommandsPlugin/types.d.ts +14 -2
- package/dist/plugins/CommandsPlugin/useResolvedCommands.d.ts +12 -0
- package/dist/plugins/CommandsPlugin/useResolvedCommands.js +46 -0
- package/dist/plugins/DnDPlugin/DnDPlugin.d.ts +8 -0
- package/dist/plugins/DnDPlugin/DnDPlugin.js +35 -7
- package/dist/plugins/NodeIdPlugin/NodeIdPlugin.js +6 -1
- package/dist/plugins/index.d.ts +1 -1
- package/package.json +11 -11
- package/src/plugins/CommandsPlugin/CommandsPlugin.tsx +5 -25
- package/src/plugins/CommandsPlugin/Menu.tsx +260 -86
- package/src/plugins/CommandsPlugin/index.ts +1 -1
- package/src/plugins/CommandsPlugin/resolveCommands.spec.ts +261 -0
- package/src/plugins/CommandsPlugin/resolveCommands.ts +275 -0
- package/src/plugins/CommandsPlugin/styles.module.css +49 -7
- package/src/plugins/CommandsPlugin/types.ts +17 -3
- package/src/plugins/CommandsPlugin/useResolvedCommands.ts +67 -0
- package/src/plugins/DnDPlugin/DnDPlugin.tsx +66 -9
- package/src/plugins/NodeIdPlugin/NodeIdPlugin.ts +10 -2
- package/src/plugins/index.ts +6 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const toPathEntry = (command)=>({
|
|
2
|
+
name: command.name,
|
|
3
|
+
title: command.title,
|
|
4
|
+
commandName: command.commandName
|
|
5
|
+
});
|
|
6
|
+
const pathToKey = (path)=>path.map((item)=>item.name).join('/');
|
|
7
|
+
const normalizeCommands = (commands)=>Array.isArray(commands) ? commands : [];
|
|
8
|
+
const isCommandMatchesQuery = (command, query)=>{
|
|
9
|
+
const normalized = query.toLocaleLowerCase();
|
|
10
|
+
return command.commandName.toLocaleLowerCase().includes(normalized) || command.title.toLocaleLowerCase().includes(normalized);
|
|
11
|
+
};
|
|
12
|
+
const toResolvedCommand = (command, parentPath)=>{
|
|
13
|
+
const path = [
|
|
14
|
+
...parentPath,
|
|
15
|
+
toPathEntry(command)
|
|
16
|
+
];
|
|
17
|
+
return {
|
|
18
|
+
command,
|
|
19
|
+
path,
|
|
20
|
+
key: pathToKey(path),
|
|
21
|
+
breadcrumb: parentPath.map((item)=>item.title).join(' / '),
|
|
22
|
+
isSubmenu: Boolean(command.getCommands)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const resolveBrowseCommands = async (params, resolveChildCommands)=>{
|
|
26
|
+
const { rootCommands, path, editor } = params;
|
|
27
|
+
let currentCommands = rootCommands;
|
|
28
|
+
let currentPath = [];
|
|
29
|
+
for (const item of path){
|
|
30
|
+
const command = currentCommands.find((entry)=>entry.name === item.name);
|
|
31
|
+
if (!command?.getCommands) return [];
|
|
32
|
+
currentPath = [
|
|
33
|
+
...currentPath,
|
|
34
|
+
toPathEntry(command)
|
|
35
|
+
];
|
|
36
|
+
currentCommands = await resolveChildCommands({
|
|
37
|
+
command,
|
|
38
|
+
path: currentPath,
|
|
39
|
+
query: '',
|
|
40
|
+
editor
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return currentCommands.map((command)=>toResolvedCommand(command, currentPath));
|
|
44
|
+
};
|
|
45
|
+
const resolveSearchCommands = async (params, resolveChildCommands)=>{
|
|
46
|
+
const { rootCommands, filter, editor } = params;
|
|
47
|
+
const commands = [];
|
|
48
|
+
const walk = async (levelCommands, parentPath)=>{
|
|
49
|
+
for (const command of levelCommands){
|
|
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;
|
|
70
|
+
};
|
|
71
|
+
class CommandsResolver {
|
|
72
|
+
cache = new Map();
|
|
73
|
+
requestId = 0;
|
|
74
|
+
state = {
|
|
75
|
+
commands: [],
|
|
76
|
+
isLoading: false,
|
|
77
|
+
isError: false
|
|
78
|
+
};
|
|
79
|
+
getState() {
|
|
80
|
+
return this.state;
|
|
81
|
+
}
|
|
82
|
+
resolve(params) {
|
|
83
|
+
const currentRequestId = ++this.requestId;
|
|
84
|
+
this.state = {
|
|
85
|
+
commands: [],
|
|
86
|
+
isLoading: true,
|
|
87
|
+
isError: false
|
|
88
|
+
};
|
|
89
|
+
const promise = this.resolveInternal(params).then((commands)=>{
|
|
90
|
+
if (currentRequestId !== this.requestId) return this.state;
|
|
91
|
+
this.state = {
|
|
92
|
+
commands,
|
|
93
|
+
isLoading: false,
|
|
94
|
+
isError: false
|
|
95
|
+
};
|
|
96
|
+
return this.state;
|
|
97
|
+
}).catch(()=>{
|
|
98
|
+
if (currentRequestId !== this.requestId) return this.state;
|
|
99
|
+
this.state = {
|
|
100
|
+
...this.state,
|
|
101
|
+
isLoading: false,
|
|
102
|
+
isError: true
|
|
103
|
+
};
|
|
104
|
+
return this.state;
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
state: this.state,
|
|
108
|
+
promise
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
resolveChildCommands = async (params)=>{
|
|
112
|
+
const { command, path, query, editor } = params;
|
|
113
|
+
if (!command.getCommands) return [];
|
|
114
|
+
const cacheKey = `${query}::${pathToKey(path)}`;
|
|
115
|
+
const cached = this.cache.get(cacheKey);
|
|
116
|
+
if (cached?.status === 'resolved') return cached.value;
|
|
117
|
+
if (cached?.status === 'rejected') throw cached.error;
|
|
118
|
+
if (cached?.status === 'pending') return cached.promise;
|
|
119
|
+
const promise = Promise.resolve(command.getCommands({
|
|
120
|
+
query,
|
|
121
|
+
editor,
|
|
122
|
+
path,
|
|
123
|
+
parent: command
|
|
124
|
+
})).then((commands)=>normalizeCommands(commands));
|
|
125
|
+
this.cache.set(cacheKey, {
|
|
126
|
+
status: 'pending',
|
|
127
|
+
promise
|
|
128
|
+
});
|
|
129
|
+
try {
|
|
130
|
+
const commands = await promise;
|
|
131
|
+
this.cache.set(cacheKey, {
|
|
132
|
+
status: 'resolved',
|
|
133
|
+
value: commands
|
|
134
|
+
});
|
|
135
|
+
return commands;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this.cache.set(cacheKey, {
|
|
138
|
+
status: 'rejected',
|
|
139
|
+
error
|
|
140
|
+
});
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
async resolveInternal(params) {
|
|
145
|
+
const { filter } = params;
|
|
146
|
+
if (!filter) return resolveBrowseCommands(params, this.resolveChildCommands);
|
|
147
|
+
return resolveSearchCommands(params, this.resolveChildCommands);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export { CommandsResolver };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { CommandsResolver } from "./resolveCommands.js";
|
|
3
|
+
const editor = {};
|
|
4
|
+
const leaf = (params)=>({
|
|
5
|
+
name: params.name,
|
|
6
|
+
title: params.title,
|
|
7
|
+
commandName: params.commandName ?? params.title.toLocaleLowerCase(),
|
|
8
|
+
icon: null,
|
|
9
|
+
action: ()=>{}
|
|
10
|
+
});
|
|
11
|
+
const deferred = ()=>{
|
|
12
|
+
let resolve = ()=>{};
|
|
13
|
+
let reject = ()=>{};
|
|
14
|
+
const promise = new Promise((res, rej)=>{
|
|
15
|
+
resolve = res;
|
|
16
|
+
reject = rej;
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
promise,
|
|
20
|
+
resolve,
|
|
21
|
+
reject
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
describe('CommandsResolver', ()=>{
|
|
25
|
+
it('keeps leaf-only legacy behavior', async ()=>{
|
|
26
|
+
const resolver = new CommandsResolver();
|
|
27
|
+
const rootCommands = [
|
|
28
|
+
leaf({
|
|
29
|
+
name: 'paragraph',
|
|
30
|
+
title: 'Paragraph'
|
|
31
|
+
}),
|
|
32
|
+
leaf({
|
|
33
|
+
name: 'code',
|
|
34
|
+
title: 'Code'
|
|
35
|
+
})
|
|
36
|
+
];
|
|
37
|
+
const request = resolver.resolve({
|
|
38
|
+
rootCommands,
|
|
39
|
+
filter: '',
|
|
40
|
+
path: [],
|
|
41
|
+
editor
|
|
42
|
+
});
|
|
43
|
+
const result = await request.promise;
|
|
44
|
+
expect(result.commands.map((item)=>item.command.name)).toEqual([
|
|
45
|
+
'paragraph',
|
|
46
|
+
'code'
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
it('resolves static submenu with sync getCommands', async ()=>{
|
|
50
|
+
const resolver = new CommandsResolver();
|
|
51
|
+
const rootCommands = [
|
|
52
|
+
{
|
|
53
|
+
name: 'insert',
|
|
54
|
+
title: 'Insert',
|
|
55
|
+
commandName: 'insert',
|
|
56
|
+
icon: null,
|
|
57
|
+
getCommands: ()=>[
|
|
58
|
+
leaf({
|
|
59
|
+
name: 'heading-1',
|
|
60
|
+
title: 'Heading 1'
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
const request = resolver.resolve({
|
|
66
|
+
rootCommands,
|
|
67
|
+
filter: '',
|
|
68
|
+
path: [
|
|
69
|
+
{
|
|
70
|
+
name: 'insert',
|
|
71
|
+
title: 'Insert',
|
|
72
|
+
commandName: 'insert'
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
editor
|
|
76
|
+
});
|
|
77
|
+
const result = await request.promise;
|
|
78
|
+
expect(result.commands).toHaveLength(1);
|
|
79
|
+
expect(result.commands[0]?.command.name).toBe('heading-1');
|
|
80
|
+
});
|
|
81
|
+
it('resolves async submenu', async ()=>{
|
|
82
|
+
const resolver = new CommandsResolver();
|
|
83
|
+
const rootCommands = [
|
|
84
|
+
{
|
|
85
|
+
name: 'remote',
|
|
86
|
+
title: 'Remote',
|
|
87
|
+
commandName: 'remote',
|
|
88
|
+
icon: null,
|
|
89
|
+
getCommands: async ()=>[
|
|
90
|
+
leaf({
|
|
91
|
+
name: 'code',
|
|
92
|
+
title: 'Code'
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
const request = resolver.resolve({
|
|
98
|
+
rootCommands,
|
|
99
|
+
filter: '',
|
|
100
|
+
path: [
|
|
101
|
+
{
|
|
102
|
+
name: 'remote',
|
|
103
|
+
title: 'Remote',
|
|
104
|
+
commandName: 'remote'
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
editor
|
|
108
|
+
});
|
|
109
|
+
const result = await request.promise;
|
|
110
|
+
expect(result.commands).toHaveLength(1);
|
|
111
|
+
expect(result.commands[0]?.command.name).toBe('code');
|
|
112
|
+
});
|
|
113
|
+
it('returns global leaf query matches with breadcrumbs', async ()=>{
|
|
114
|
+
const resolver = new CommandsResolver();
|
|
115
|
+
const rootCommands = [
|
|
116
|
+
{
|
|
117
|
+
name: 'insert',
|
|
118
|
+
title: 'Insert',
|
|
119
|
+
commandName: 'insert',
|
|
120
|
+
icon: null,
|
|
121
|
+
getCommands: ()=>[
|
|
122
|
+
leaf({
|
|
123
|
+
name: 'heading-1',
|
|
124
|
+
title: 'Heading 1',
|
|
125
|
+
commandName: 'heading1'
|
|
126
|
+
}),
|
|
127
|
+
leaf({
|
|
128
|
+
name: 'code',
|
|
129
|
+
title: 'Code',
|
|
130
|
+
commandName: 'code'
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
const request = resolver.resolve({
|
|
136
|
+
rootCommands,
|
|
137
|
+
filter: 'code',
|
|
138
|
+
path: [],
|
|
139
|
+
editor
|
|
140
|
+
});
|
|
141
|
+
const result = await request.promise;
|
|
142
|
+
expect(result.commands).toHaveLength(1);
|
|
143
|
+
expect(result.commands[0]?.command.name).toBe('code');
|
|
144
|
+
expect(result.commands[0]?.breadcrumb).toBe('Insert');
|
|
145
|
+
});
|
|
146
|
+
it('returns matching submenu commands in query mode', async ()=>{
|
|
147
|
+
const resolver = new CommandsResolver();
|
|
148
|
+
const rootCommands = [
|
|
149
|
+
{
|
|
150
|
+
name: 'advanced',
|
|
151
|
+
title: 'Advanced',
|
|
152
|
+
commandName: 'advanced',
|
|
153
|
+
icon: null,
|
|
154
|
+
getCommands: ()=>[
|
|
155
|
+
leaf({
|
|
156
|
+
name: 'code',
|
|
157
|
+
title: 'Code'
|
|
158
|
+
})
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
];
|
|
162
|
+
const request = resolver.resolve({
|
|
163
|
+
rootCommands,
|
|
164
|
+
filter: 'adv',
|
|
165
|
+
path: [],
|
|
166
|
+
editor
|
|
167
|
+
});
|
|
168
|
+
const result = await request.promise;
|
|
169
|
+
expect(result.commands).toHaveLength(1);
|
|
170
|
+
expect(result.commands[0]?.command.name).toBe('advanced');
|
|
171
|
+
expect(result.commands[0]?.isSubmenu).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it('ignores stale out-of-order async responses', async ()=>{
|
|
174
|
+
const resolver = new CommandsResolver();
|
|
175
|
+
const searchA = deferred();
|
|
176
|
+
const searchB = deferred();
|
|
177
|
+
const rootCommands = [
|
|
178
|
+
{
|
|
179
|
+
name: 'remote',
|
|
180
|
+
title: 'Remote',
|
|
181
|
+
commandName: 'remote',
|
|
182
|
+
icon: null,
|
|
183
|
+
getCommands: ({ query })=>{
|
|
184
|
+
if ('a' === query) return searchA.promise;
|
|
185
|
+
return searchB.promise;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
const firstRequest = resolver.resolve({
|
|
190
|
+
rootCommands,
|
|
191
|
+
filter: 'a',
|
|
192
|
+
path: [],
|
|
193
|
+
editor
|
|
194
|
+
});
|
|
195
|
+
const secondRequest = resolver.resolve({
|
|
196
|
+
rootCommands,
|
|
197
|
+
filter: 'b',
|
|
198
|
+
path: [],
|
|
199
|
+
editor
|
|
200
|
+
});
|
|
201
|
+
searchB.resolve([
|
|
202
|
+
leaf({
|
|
203
|
+
name: 'b',
|
|
204
|
+
title: 'Result B',
|
|
205
|
+
commandName: 'b'
|
|
206
|
+
})
|
|
207
|
+
]);
|
|
208
|
+
const secondResult = await secondRequest.promise;
|
|
209
|
+
expect(secondResult.commands.map((item)=>item.command.name)).toEqual([
|
|
210
|
+
'b'
|
|
211
|
+
]);
|
|
212
|
+
searchA.resolve([
|
|
213
|
+
leaf({
|
|
214
|
+
name: 'a',
|
|
215
|
+
title: 'Result A',
|
|
216
|
+
commandName: 'a'
|
|
217
|
+
})
|
|
218
|
+
]);
|
|
219
|
+
const firstResult = await firstRequest.promise;
|
|
220
|
+
expect(firstResult.commands.map((item)=>item.command.name)).toEqual([
|
|
221
|
+
'b'
|
|
222
|
+
]);
|
|
223
|
+
expect(resolver.getState().commands.map((item)=>item.command.name)).toEqual([
|
|
224
|
+
'b'
|
|
225
|
+
]);
|
|
226
|
+
});
|
|
227
|
+
it('clears visible commands while loading new results', async ()=>{
|
|
228
|
+
const resolver = new CommandsResolver();
|
|
229
|
+
const remoteDeferred = deferred();
|
|
230
|
+
const rootCommands = [
|
|
231
|
+
leaf({
|
|
232
|
+
name: 'paragraph',
|
|
233
|
+
title: 'Paragraph'
|
|
234
|
+
}),
|
|
235
|
+
{
|
|
236
|
+
name: 'remote',
|
|
237
|
+
title: 'Remote',
|
|
238
|
+
commandName: 'remote',
|
|
239
|
+
icon: null,
|
|
240
|
+
getCommands: ({ query })=>{
|
|
241
|
+
if (query) return remoteDeferred.promise;
|
|
242
|
+
return [
|
|
243
|
+
leaf({
|
|
244
|
+
name: 'code',
|
|
245
|
+
title: 'Code'
|
|
246
|
+
})
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
];
|
|
251
|
+
const initial = await resolver.resolve({
|
|
252
|
+
rootCommands,
|
|
253
|
+
filter: '',
|
|
254
|
+
path: [],
|
|
255
|
+
editor
|
|
256
|
+
}).promise;
|
|
257
|
+
const loadingRequest = resolver.resolve({
|
|
258
|
+
rootCommands,
|
|
259
|
+
filter: 'code',
|
|
260
|
+
path: [],
|
|
261
|
+
editor
|
|
262
|
+
});
|
|
263
|
+
expect(loadingRequest.state.isLoading).toBe(true);
|
|
264
|
+
expect(initial.commands.length).toBeGreaterThan(0);
|
|
265
|
+
expect(loadingRequest.state.commands).toEqual([]);
|
|
266
|
+
remoteDeferred.resolve([
|
|
267
|
+
leaf({
|
|
268
|
+
name: 'code',
|
|
269
|
+
title: 'Code'
|
|
270
|
+
})
|
|
271
|
+
]);
|
|
272
|
+
const settled = await loadingRequest.promise;
|
|
273
|
+
expect(settled.isLoading).toBe(false);
|
|
274
|
+
expect(settled.commands).toHaveLength(1);
|
|
275
|
+
expect(settled.commands[0]?.command.name).toBe('code');
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -4,6 +4,11 @@ const styles_module = {
|
|
|
4
4
|
button: "button-yrhMLR",
|
|
5
5
|
active: "active-Is5D6b",
|
|
6
6
|
icon: "icon-Xca6dM",
|
|
7
|
+
content: "content-VZW7lK",
|
|
8
|
+
breadcrumb: "breadcrumb-Jr0wXh",
|
|
9
|
+
submenu: "submenu-zNsqOg",
|
|
10
|
+
path: "path-fYY_14",
|
|
11
|
+
systemRow: "systemRow-ln5twO",
|
|
7
12
|
backdrop: "backdrop-lw7AjW"
|
|
8
13
|
};
|
|
9
14
|
export { styles_module as default };
|
|
@@ -2,20 +2,24 @@
|
|
|
2
2
|
color: var(--kona-editor-text-color);
|
|
3
3
|
z-index: 31;
|
|
4
4
|
opacity: 0;
|
|
5
|
+
transform-origin: 0 0;
|
|
5
6
|
background-color: var(--kona-editor-background-color, #fff);
|
|
6
7
|
border: 1px solid var(--kona-editor-border-color, #ddd);
|
|
8
|
+
will-change: transform, opacity;
|
|
9
|
+
contain: layout paint style;
|
|
10
|
+
backface-visibility: hidden;
|
|
7
11
|
border-radius: 4px;
|
|
8
12
|
flex-direction: column;
|
|
9
13
|
max-height: 200px;
|
|
10
14
|
margin-top: -6px;
|
|
11
15
|
font-size: 12px;
|
|
12
|
-
transition: transform .
|
|
16
|
+
transition: transform .12s, opacity .12s;
|
|
13
17
|
display: flex;
|
|
14
|
-
position:
|
|
15
|
-
top:
|
|
16
|
-
left:
|
|
18
|
+
position: fixed;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: 0;
|
|
17
21
|
overflow-y: auto;
|
|
18
|
-
transform: scale(.
|
|
22
|
+
transform: translate3d(-100000px, -100000px, 0)scale(.95);
|
|
19
23
|
box-shadow: 0 1px 3px #00000006;
|
|
20
24
|
|
|
21
25
|
&::-webkit-scrollbar {
|
|
@@ -35,14 +39,16 @@
|
|
|
35
39
|
.button-yrhMLR {
|
|
36
40
|
box-sizing: border-box;
|
|
37
41
|
cursor: pointer;
|
|
38
|
-
height: 40px;
|
|
42
|
+
min-height: 40px;
|
|
39
43
|
color: var(--kona-editor-text-color, #444);
|
|
44
|
+
text-align: left;
|
|
40
45
|
background: none;
|
|
41
46
|
border: none;
|
|
42
47
|
align-items: center;
|
|
43
48
|
column-gap: 8px;
|
|
49
|
+
width: 100%;
|
|
44
50
|
padding: 8px;
|
|
45
|
-
display:
|
|
51
|
+
display: flex;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
.button-yrhMLR:hover, .active-Is5D6b {
|
|
@@ -63,6 +69,40 @@
|
|
|
63
69
|
display: inline-flex;
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
.content-VZW7lK {
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
flex: 1;
|
|
75
|
+
row-gap: 2px;
|
|
76
|
+
min-width: 0;
|
|
77
|
+
display: flex;
|
|
78
|
+
}
|
|
79
|
+
|
|
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
|
+
.submenu-zNsqOg {
|
|
89
|
+
color: var(--kona-editor-secondary-text-color, #777);
|
|
90
|
+
}
|
|
91
|
+
|
|
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
|
+
.systemRow-ln5twO {
|
|
100
|
+
min-height: 32px;
|
|
101
|
+
color: var(--kona-editor-secondary-text-color, #777);
|
|
102
|
+
padding: 8px;
|
|
103
|
+
font-size: 11px;
|
|
104
|
+
}
|
|
105
|
+
|
|
66
106
|
.backdrop-lw7AjW {
|
|
67
107
|
z-index: 30;
|
|
68
108
|
background-color: #0000;
|
|
@@ -9,14 +9,26 @@ export type Options = {
|
|
|
9
9
|
export type CommandsStore = {
|
|
10
10
|
isOpen: boolean;
|
|
11
11
|
filter: boolean | string;
|
|
12
|
-
|
|
12
|
+
openId: number;
|
|
13
|
+
};
|
|
14
|
+
export type CommandPathEntry = {
|
|
15
|
+
name: string;
|
|
16
|
+
title: string;
|
|
17
|
+
commandName: string;
|
|
18
|
+
};
|
|
19
|
+
export type GetCommandsContext = {
|
|
20
|
+
query: string;
|
|
21
|
+
editor: Editor;
|
|
22
|
+
path: CommandPathEntry[];
|
|
23
|
+
parent: Command;
|
|
13
24
|
};
|
|
14
25
|
export type Command = {
|
|
15
26
|
name: string;
|
|
16
27
|
title: string;
|
|
17
28
|
commandName: string;
|
|
18
29
|
icon: ReactNode;
|
|
19
|
-
action
|
|
30
|
+
action?: (actions: Actions, editor: Editor) => void;
|
|
31
|
+
getCommands?: (context: GetCommandsContext) => Command[] | Promise<Command[]>;
|
|
20
32
|
};
|
|
21
33
|
export type Actions = {
|
|
22
34
|
removeCommand: () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Editor } from 'slate';
|
|
2
|
+
import { type ResolvedCommandsState } from './resolveCommands';
|
|
3
|
+
import type { Command, CommandPathEntry } from './types';
|
|
4
|
+
type Params = {
|
|
5
|
+
rootCommands: Command[];
|
|
6
|
+
filter: boolean | string;
|
|
7
|
+
path: CommandPathEntry[];
|
|
8
|
+
editor: Editor;
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare const useResolvedCommands: (params: Params) => ResolvedCommandsState;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { CommandsResolver } from "./resolveCommands.js";
|
|
3
|
+
const DEBOUNCE_TIMEOUT = 150;
|
|
4
|
+
const EMPTY_STATE = {
|
|
5
|
+
commands: [],
|
|
6
|
+
isLoading: false,
|
|
7
|
+
isError: false
|
|
8
|
+
};
|
|
9
|
+
const useResolvedCommands = (params)=>{
|
|
10
|
+
const { rootCommands, filter, path, editor, isOpen } = params;
|
|
11
|
+
const resolverRef = useRef(new CommandsResolver());
|
|
12
|
+
const [state, setState] = useState(EMPTY_STATE);
|
|
13
|
+
const query = 'string' == typeof filter ? filter : '';
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
if (!isOpen || false === filter) return void setState(EMPTY_STATE);
|
|
16
|
+
setState({
|
|
17
|
+
commands: [],
|
|
18
|
+
isLoading: true,
|
|
19
|
+
isError: false
|
|
20
|
+
});
|
|
21
|
+
const timeout = window.setTimeout(()=>{
|
|
22
|
+
const request = resolverRef.current.resolve({
|
|
23
|
+
rootCommands,
|
|
24
|
+
filter: query,
|
|
25
|
+
path,
|
|
26
|
+
editor
|
|
27
|
+
});
|
|
28
|
+
setState(request.state);
|
|
29
|
+
request.promise.then((resolved)=>{
|
|
30
|
+
setState(resolved);
|
|
31
|
+
});
|
|
32
|
+
}, query ? DEBOUNCE_TIMEOUT : 0);
|
|
33
|
+
return ()=>{
|
|
34
|
+
window.clearTimeout(timeout);
|
|
35
|
+
};
|
|
36
|
+
}, [
|
|
37
|
+
editor,
|
|
38
|
+
filter,
|
|
39
|
+
isOpen,
|
|
40
|
+
path,
|
|
41
|
+
query,
|
|
42
|
+
rootCommands
|
|
43
|
+
]);
|
|
44
|
+
return state;
|
|
45
|
+
};
|
|
46
|
+
export { useResolvedCommands };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MapStore } from 'nanostores';
|
|
1
2
|
import type React from 'react';
|
|
2
3
|
import { type ConnectDragPreview, type ConnectDragSource, type ConnectDropTarget } from 'react-dnd';
|
|
3
4
|
import { Editor, Path } from 'slate';
|
|
@@ -12,18 +13,25 @@ type Options = {
|
|
|
12
13
|
dropRef: ConnectDropTarget;
|
|
13
14
|
previewRef: ConnectDragPreview;
|
|
14
15
|
position: 'top' | 'bottom' | null;
|
|
16
|
+
selected?: boolean;
|
|
17
|
+
onToggleSelected: () => void;
|
|
15
18
|
}) => React.ReactNode;
|
|
16
19
|
ignoreNodes?: string[];
|
|
17
20
|
customTypes?: {
|
|
18
21
|
[type: string]: {
|
|
19
22
|
type: string | symbol;
|
|
20
23
|
getData?: (element: CustomElement) => Record<string, unknown>;
|
|
24
|
+
getDndItem?: (element: CustomElement) => Record<string, unknown>;
|
|
21
25
|
};
|
|
22
26
|
};
|
|
23
27
|
};
|
|
24
28
|
export declare class DnDPlugin implements IPlugin {
|
|
25
29
|
private options;
|
|
30
|
+
store: MapStore<{
|
|
31
|
+
selected: Set<string>;
|
|
32
|
+
}>;
|
|
26
33
|
constructor(options: Options);
|
|
34
|
+
handleToggleSelected: (nodeId: string) => void;
|
|
27
35
|
static DND_BLOCK_ELEMENT: string;
|
|
28
36
|
handlers: {
|
|
29
37
|
onDrop: (event: DragEvent) => void;
|