@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.
Files changed (30) hide show
  1. package/dist/plugins/CommandsPlugin/CommandsPlugin.d.ts +2 -3
  2. package/dist/plugins/CommandsPlugin/CommandsPlugin.js +5 -15
  3. package/dist/plugins/CommandsPlugin/Menu.d.ts +3 -3
  4. package/dist/plugins/CommandsPlugin/Menu.js +240 -69
  5. package/dist/plugins/CommandsPlugin/index.d.ts +1 -1
  6. package/dist/plugins/CommandsPlugin/resolveCommands.d.ts +34 -0
  7. package/dist/plugins/CommandsPlugin/resolveCommands.js +150 -0
  8. package/dist/plugins/CommandsPlugin/resolveCommands.spec.d.ts +1 -0
  9. package/dist/plugins/CommandsPlugin/resolveCommands.spec.js +277 -0
  10. package/dist/plugins/CommandsPlugin/styles.module.js +5 -0
  11. package/dist/plugins/CommandsPlugin/styles_module.css +47 -7
  12. package/dist/plugins/CommandsPlugin/types.d.ts +14 -2
  13. package/dist/plugins/CommandsPlugin/useResolvedCommands.d.ts +12 -0
  14. package/dist/plugins/CommandsPlugin/useResolvedCommands.js +46 -0
  15. package/dist/plugins/DnDPlugin/DnDPlugin.d.ts +7 -0
  16. package/dist/plugins/DnDPlugin/DnDPlugin.js +34 -7
  17. package/dist/plugins/NodeIdPlugin/NodeIdPlugin.js +6 -1
  18. package/dist/plugins/index.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/plugins/CommandsPlugin/CommandsPlugin.tsx +5 -25
  21. package/src/plugins/CommandsPlugin/Menu.tsx +260 -86
  22. package/src/plugins/CommandsPlugin/index.ts +1 -1
  23. package/src/plugins/CommandsPlugin/resolveCommands.spec.ts +261 -0
  24. package/src/plugins/CommandsPlugin/resolveCommands.ts +275 -0
  25. package/src/plugins/CommandsPlugin/styles.module.css +49 -7
  26. package/src/plugins/CommandsPlugin/types.ts +17 -3
  27. package/src/plugins/CommandsPlugin/useResolvedCommands.ts +67 -0
  28. package/src/plugins/DnDPlugin/DnDPlugin.tsx +64 -9
  29. package/src/plugins/NodeIdPlugin/NodeIdPlugin.ts +10 -2
  30. 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
- constructor(private options: Options) {}
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 targetTo = ReactEditor.findPath(
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, targetTo, dropPosition);
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
- sourceNodePath: Path,
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
  }
@@ -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 { type Command, CommandsPlugin } from './CommandsPlugin';
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';