@use-kona/editor 0.1.16 → 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/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 +7 -0
- package/dist/plugins/DnDPlugin/DnDPlugin.js +34 -7
- package/dist/plugins/NodeIdPlugin/NodeIdPlugin.js +6 -1
- package/dist/plugins/index.d.ts +1 -1
- package/package.json +1 -1
- 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 +64 -9
- package/src/plugins/NodeIdPlugin/NodeIdPlugin.ts +10 -2
- package/src/plugins/index.ts +6 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { Editor } from 'slate';
|
|
3
|
+
import {
|
|
4
|
+
CommandsResolver,
|
|
5
|
+
type ResolvedCommandsState,
|
|
6
|
+
} from './resolveCommands';
|
|
7
|
+
import type { Command, CommandPathEntry } from './types';
|
|
8
|
+
|
|
9
|
+
const DEBOUNCE_TIMEOUT = 150;
|
|
10
|
+
|
|
11
|
+
type Params = {
|
|
12
|
+
rootCommands: Command[];
|
|
13
|
+
filter: boolean | string;
|
|
14
|
+
path: CommandPathEntry[];
|
|
15
|
+
editor: Editor;
|
|
16
|
+
isOpen: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const EMPTY_STATE: ResolvedCommandsState = {
|
|
20
|
+
commands: [],
|
|
21
|
+
isLoading: false,
|
|
22
|
+
isError: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const useResolvedCommands = (params: Params) => {
|
|
26
|
+
const { rootCommands, filter, path, editor, isOpen } = params;
|
|
27
|
+
const resolverRef = useRef(new CommandsResolver());
|
|
28
|
+
const [state, setState] = useState<ResolvedCommandsState>(EMPTY_STATE);
|
|
29
|
+
const query = typeof filter === 'string' ? filter : '';
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!isOpen || filter === false) {
|
|
33
|
+
setState(EMPTY_STATE);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setState({
|
|
38
|
+
commands: [],
|
|
39
|
+
isLoading: true,
|
|
40
|
+
isError: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const timeout = window.setTimeout(
|
|
44
|
+
() => {
|
|
45
|
+
const request = resolverRef.current.resolve({
|
|
46
|
+
rootCommands,
|
|
47
|
+
filter: query,
|
|
48
|
+
path,
|
|
49
|
+
editor,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
setState(request.state);
|
|
53
|
+
|
|
54
|
+
request.promise.then((resolved) => {
|
|
55
|
+
setState(resolved);
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
query ? DEBOUNCE_TIMEOUT : 0,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
window.clearTimeout(timeout);
|
|
63
|
+
};
|
|
64
|
+
}, [editor, filter, isOpen, path, query, rootCommands]);
|
|
65
|
+
|
|
66
|
+
return state;
|
|
67
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useStore } from '@nanostores/react';
|
|
2
|
+
import { type MapStore, map } from 'nanostores';
|
|
1
3
|
import type React from 'react';
|
|
2
4
|
import { useEffect, useState } from 'react';
|
|
3
5
|
import {
|
|
@@ -26,6 +28,8 @@ type Options = {
|
|
|
26
28
|
dropRef: ConnectDropTarget;
|
|
27
29
|
previewRef: ConnectDragPreview;
|
|
28
30
|
position: 'top' | 'bottom' | null;
|
|
31
|
+
selected?: boolean;
|
|
32
|
+
onToggleSelected: () => void;
|
|
29
33
|
}) => React.ReactNode;
|
|
30
34
|
ignoreNodes?: string[];
|
|
31
35
|
customTypes?: {
|
|
@@ -37,8 +41,36 @@ type Options = {
|
|
|
37
41
|
};
|
|
38
42
|
};
|
|
39
43
|
|
|
44
|
+
type DnDItem = {
|
|
45
|
+
element: CustomElement;
|
|
46
|
+
nodeIds?: string[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type DnDNode = CustomElement & {
|
|
50
|
+
nodeId?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
40
53
|
export class DnDPlugin implements IPlugin {
|
|
41
|
-
|
|
54
|
+
store: MapStore<{ selected: Set<string> }>;
|
|
55
|
+
|
|
56
|
+
constructor(private options: Options) {
|
|
57
|
+
this.store = map({
|
|
58
|
+
selected: new Set(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handleToggleSelected = (nodeId: string) => {
|
|
63
|
+
const selected = new Set(this.store.get().selected);
|
|
64
|
+
if (selected.has(nodeId)) {
|
|
65
|
+
selected.delete(nodeId);
|
|
66
|
+
} else {
|
|
67
|
+
selected.add(nodeId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.store.set({
|
|
71
|
+
selected,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
42
74
|
|
|
43
75
|
static DND_BLOCK_ELEMENT = 'block';
|
|
44
76
|
|
|
@@ -51,6 +83,8 @@ export class DnDPlugin implements IPlugin {
|
|
|
51
83
|
renderBlock = (props: RenderElementProps) => {
|
|
52
84
|
const editor = useSlate();
|
|
53
85
|
|
|
86
|
+
const $store = useStore(this.store);
|
|
87
|
+
|
|
54
88
|
const options = this.options;
|
|
55
89
|
const isReadOnly = useReadOnly();
|
|
56
90
|
|
|
@@ -59,12 +93,14 @@ export class DnDPlugin implements IPlugin {
|
|
|
59
93
|
);
|
|
60
94
|
|
|
61
95
|
const customType = options.customTypes?.[props.element.type];
|
|
96
|
+
const currentElement = props.element as DnDNode;
|
|
62
97
|
|
|
63
98
|
const [, drag, preview] = useDrag({
|
|
64
99
|
type: customType?.type || DnDPlugin.DND_BLOCK_ELEMENT,
|
|
65
100
|
item: {
|
|
66
101
|
...(customType?.getData?.(props.element) || {}),
|
|
67
102
|
element: props.element,
|
|
103
|
+
nodeIds: Array.from($store.selected.values()),
|
|
68
104
|
},
|
|
69
105
|
...(customType?.getDndItem?.(props.element) || {}),
|
|
70
106
|
canDrag: !isReadOnly,
|
|
@@ -128,16 +164,13 @@ export class DnDPlugin implements IPlugin {
|
|
|
128
164
|
);
|
|
129
165
|
break;
|
|
130
166
|
default: {
|
|
131
|
-
const
|
|
132
|
-
editor,
|
|
133
|
-
(item as { element: CustomElement }).element,
|
|
134
|
-
);
|
|
167
|
+
const dragItem = item as DnDItem;
|
|
135
168
|
|
|
136
|
-
if (!dropPosition) {
|
|
169
|
+
if (!dropPosition || !dragItem.nodeIds?.length) {
|
|
137
170
|
return;
|
|
138
171
|
}
|
|
139
172
|
|
|
140
|
-
moveNode(editor, sourceTo,
|
|
173
|
+
moveNode(editor, sourceTo, dragItem.nodeIds, dropPosition);
|
|
141
174
|
break;
|
|
142
175
|
}
|
|
143
176
|
}
|
|
@@ -165,6 +198,15 @@ export class DnDPlugin implements IPlugin {
|
|
|
165
198
|
dropRef: drop,
|
|
166
199
|
previewRef: preview,
|
|
167
200
|
position: dropPosition,
|
|
201
|
+
selected:
|
|
202
|
+
typeof currentElement.nodeId === 'string'
|
|
203
|
+
? $store.selected.has(currentElement.nodeId)
|
|
204
|
+
: false,
|
|
205
|
+
onToggleSelected: () => {
|
|
206
|
+
if (typeof currentElement.nodeId === 'string') {
|
|
207
|
+
this.handleToggleSelected(currentElement.nodeId);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
168
210
|
});
|
|
169
211
|
};
|
|
170
212
|
}
|
|
@@ -187,16 +229,29 @@ const getDropPath = (
|
|
|
187
229
|
const moveNode = (
|
|
188
230
|
editor: Editor,
|
|
189
231
|
targetNodePath: Path,
|
|
190
|
-
|
|
232
|
+
nodeIds: string[],
|
|
191
233
|
position: 'top' | 'bottom',
|
|
192
234
|
) => {
|
|
193
235
|
const dropPath = getDropPath(editor, targetNodePath, position);
|
|
194
236
|
|
|
195
237
|
if (!dropPath) return;
|
|
196
238
|
|
|
239
|
+
// nodeIds.reverse().forEach((nodeId) => {
|
|
197
240
|
Transforms.moveNodes(editor, {
|
|
198
|
-
at: sourceNodePath,
|
|
241
|
+
// at: sourceNodePath,
|
|
242
|
+
at: [],
|
|
243
|
+
match: (n) => {
|
|
244
|
+
if (!Editor.isBlock(editor, n as CustomElement)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const node = n as DnDNode;
|
|
249
|
+
const shouldMove =
|
|
250
|
+
typeof node.nodeId === 'string' && nodeIds.includes(node.nodeId);
|
|
251
|
+
return shouldMove;
|
|
252
|
+
},
|
|
199
253
|
to: dropPath,
|
|
200
254
|
mode: 'highest',
|
|
201
255
|
});
|
|
256
|
+
// });
|
|
202
257
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Editor, Element, Node, Text } from 'slate';
|
|
1
|
+
import { type Editor, Element, Node, type NodeEntry, Text } from 'slate';
|
|
2
2
|
import type { CustomElement } from '../../../types';
|
|
3
3
|
import type { IPlugin } from '../../types';
|
|
4
4
|
|
|
@@ -29,7 +29,7 @@ export class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
|
|
|
29
29
|
constructor(private options: Options) {}
|
|
30
30
|
|
|
31
31
|
init(editor: Editor) {
|
|
32
|
-
const { apply } = editor;
|
|
32
|
+
const { apply, normalizeNode } = editor;
|
|
33
33
|
|
|
34
34
|
editor.apply = (operation) => {
|
|
35
35
|
if (operation.type === 'set_node') {
|
|
@@ -63,6 +63,14 @@ export class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
|
|
|
63
63
|
return apply(operation);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
editor.normalizeNode = (entry: NodeEntry) => {
|
|
67
|
+
const [node] = entry;
|
|
68
|
+
|
|
69
|
+
assignId(node, this.options.generateId);
|
|
70
|
+
|
|
71
|
+
normalizeNode(entry);
|
|
72
|
+
};
|
|
73
|
+
|
|
66
74
|
return editor;
|
|
67
75
|
}
|
|
68
76
|
}
|
package/src/plugins/index.ts
CHANGED
|
@@ -2,7 +2,12 @@ export { AttachmentsPlugin } from './AttachmentsPlugin';
|
|
|
2
2
|
export { BasicFormattingPlugin } from './BasicFormattingPlugin';
|
|
3
3
|
export { BreaksPlugin } from './BreaksPlugin';
|
|
4
4
|
export { CodeBlockPlugin, type CodeElement } from './CodeBlockPlugin';
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
type Command,
|
|
7
|
+
type CommandPathEntry,
|
|
8
|
+
CommandsPlugin,
|
|
9
|
+
type GetCommandsContext,
|
|
10
|
+
} from './CommandsPlugin';
|
|
6
11
|
export { DnDPlugin } from './DnDPlugin';
|
|
7
12
|
export { EmojiPlugin } from './EmojiPlugin';
|
|
8
13
|
export { type Commands, FloatingMenuPlugin } from './FloatingMenuPlugin';
|