@use-kona/editor 0.1.24-dnd.6 → 0.1.24-dnd.7

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.
@@ -4,7 +4,7 @@ import { map } from "nanostores";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import { useDrag, useDrop } from "react-dnd";
6
6
  import { NativeTypes } from "react-dnd-html5-backend";
7
- import { Editor, Path, Transforms } from "slate";
7
+ import { Editor, Element, Path, Transforms } from "slate";
8
8
  import { ReactEditor, useReadOnly, useSlate } from "slate-react";
9
9
  import { getNodesByNodeIds } from "./utils.js";
10
10
  class DnDPlugin {
@@ -42,7 +42,9 @@ class DnDPlugin {
42
42
  renderBlock = (props)=>{
43
43
  const editor = useSlate();
44
44
  const $store = useStore(this.store);
45
+ const isCopying = useRef(false);
45
46
  const options = this.options;
47
+ const generateId = this.options?.nodeIdPlugin?.generateId;
46
48
  const isReadOnly = useReadOnly();
47
49
  const [dropPosition, setDropPosition] = useState(null);
48
50
  const dropTargetRef = useRef(null);
@@ -55,16 +57,18 @@ class DnDPlugin {
55
57
  const nodes = selected.length > 0 && selected.includes(currentElement.nodeId) ? getNodesByNodeIds(editor, selected) : [
56
58
  structuredClone(props.element)
57
59
  ];
58
- const item = this.options.customTypes?.[currentElement.type].getData?.(currentElement);
60
+ const item = this.options.customTypes?.[currentElement.type]?.getData?.(currentElement);
59
61
  const items = nodes.map((node)=>{
60
62
  if (this.options.customTypes?.[node.type]) return this.options.customTypes[node.type].getData?.(node);
61
63
  return null;
62
64
  }).filter(Boolean);
63
65
  const dragItem = {
66
+ kind: 'inner',
64
67
  nodes,
65
68
  item,
66
69
  items,
67
70
  source: {
71
+ editor,
68
72
  editorId: this.getEditorId(editor),
69
73
  documentId: options.documentId
70
74
  }
@@ -100,32 +104,47 @@ class DnDPlugin {
100
104
  const hoverClientY = clientOffset.y - hoverBoundingRect.top;
101
105
  hoverClientY < hoverMiddleY ? setDropPosition('top') : setDropPosition('bottom');
102
106
  },
103
- drop: (item, monitor)=>{
107
+ drop: (data, monitor)=>{
104
108
  const itemType = monitor.getItemType();
109
+ const sourceEditor = 'inner' === data.kind ? data.source?.editor : null;
110
+ const isSameDocument = 'inner' === data.kind && data.source?.documentId === this.options.documentId && void 0 !== this.options.documentId;
111
+ const isSameEditor = 'inner' === data.kind && data.source?.editorId === this.editors.get(editor) && void 0 !== this.editors.get(editor);
105
112
  const sourceTo = ReactEditor.findPath(editor, props.element);
106
113
  if (!sourceTo) return;
107
114
  const insertAt = getDropPath(editor, sourceTo, dropPosition || 'bottom');
108
115
  if (!insertAt) return;
109
116
  const customDropHandler = itemType && options.customDropHandlers?.[itemType];
110
- if (customDropHandler) return void customDropHandler.onDrop({
117
+ if (customDropHandler && 'outer' === data.kind) return void customDropHandler.onDrop({
111
118
  editor,
112
- item,
119
+ data,
113
120
  insertAt
114
121
  });
115
122
  switch(itemType){
116
123
  case NativeTypes.FILE:
117
- options.onDropFiles(editor, item.files, insertAt);
124
+ options.onDropFiles(editor, data.files, insertAt);
118
125
  break;
119
126
  default:
120
127
  {
121
- const dragItem = item;
128
+ const dragItem = data;
129
+ if ('outer' === dragItem.kind) return;
122
130
  if (!dropPosition || !dragItem.nodes.length) return;
123
131
  const nodeIds = dragItem.nodes.map((node)=>node.nodeId);
124
- moveNode(editor, sourceTo, nodeIds, dropPosition);
132
+ if (isSameDocument || isSameEditor) if (isCopying.current) {
133
+ if (!generateId) return;
134
+ const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
135
+ insertNodes(editor, insertAt, clonedNodes);
136
+ } else moveNode(editor, sourceTo, nodeIds, dropPosition);
137
+ else {
138
+ if (!generateId) return;
139
+ const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
140
+ insertNodes(editor, insertAt, clonedNodes);
141
+ if (!isCopying.current && sourceEditor) removeNodes(sourceEditor, nodeIds);
142
+ }
125
143
  $store.selected.clear();
126
144
  break;
127
145
  }
128
146
  }
147
+ isCopying.current = false;
129
148
  setDropPosition(null);
130
149
  }
131
150
  });
@@ -149,6 +168,9 @@ class DnDPlugin {
149
168
  selected: 'string' == typeof currentElement.nodeId ? $store.selected.has(currentElement.nodeId) : false,
150
169
  onToggleSelected: ()=>{
151
170
  if ('string' == typeof currentElement.nodeId) this.handleToggleSelected(currentElement.nodeId);
171
+ },
172
+ onNativeDrop: (event)=>{
173
+ isCopying.current = event.altKey;
152
174
  }
153
175
  });
154
176
  };
@@ -182,4 +204,38 @@ const moveNode = (editor, targetNodePath, nodeIds, position)=>{
182
204
  });
183
205
  Editor.normalize(editor);
184
206
  };
207
+ const insertNodes = (editor, path, nodes)=>{
208
+ Editor.withoutNormalizing(editor, ()=>{
209
+ Transforms.insertNodes(editor, nodes, {
210
+ at: path
211
+ });
212
+ });
213
+ };
214
+ const removeNodes = (editor, nodeIds)=>{
215
+ try {
216
+ Transforms.removeNodes(editor, {
217
+ at: [],
218
+ match: (n)=>{
219
+ if (!Element.isElement(n)) return false;
220
+ if (!Editor.isBlock(editor, n)) return false;
221
+ const node = n;
222
+ return 'string' == typeof node.nodeId && nodeIds.includes(node.nodeId);
223
+ },
224
+ mode: 'highest'
225
+ });
226
+ } catch {
227
+ return;
228
+ }
229
+ };
230
+ const cloneWithNewIds = (nodes, generateId)=>{
231
+ const copy = structuredClone(nodes);
232
+ const assignId = (node)=>{
233
+ node.nodeId = generateId();
234
+ node.children.forEach((node)=>{
235
+ if ('children' in node) assignId(node);
236
+ });
237
+ };
238
+ copy.forEach(assignId);
239
+ return copy;
240
+ };
185
241
  export { DnDPlugin };
@@ -0,0 +1,2 @@
1
+ const isNodeWithId = (node)=>'object' == typeof node && null !== node && 'nodeId' in node;
2
+ export { isNodeWithId };
@@ -10,8 +10,10 @@ const assignId = (node, generateId)=>{
10
10
  };
11
11
  class NodeIdPlugin {
12
12
  options;
13
+ generateId;
13
14
  constructor(options){
14
15
  this.options = options;
16
+ this.generateId = options.generateId;
15
17
  }
16
18
  init(editor) {
17
19
  const { apply, normalizeNode } = editor;
@@ -20,22 +22,22 @@ class NodeIdPlugin {
20
22
  const node = Node.get(editor, operation.path);
21
23
  const oldType = operation.properties.type;
22
24
  const newType = operation.newProperties.type;
23
- if (!node || !Text.isText(node) && oldType !== newType) operation.newProperties.nodeId = this.options.generateId();
25
+ if (!node || !Text.isText(node) && oldType !== newType) operation.newProperties.nodeId = this.generateId();
24
26
  return apply(operation);
25
27
  }
26
28
  if ('insert_node' === operation.type) {
27
- assignId(operation.node, this.options.generateId);
29
+ assignId(operation.node, this.generateId);
28
30
  return apply(operation);
29
31
  }
30
32
  if ('split_node' === operation.type) {
31
33
  const node = Node.get(editor, operation.path);
32
- if (!node || !Text.isText(node)) operation.properties.nodeId = this.options.generateId();
34
+ if (!node || !Text.isText(node)) operation.properties.nodeId = this.generateId();
33
35
  }
34
36
  return apply(operation);
35
37
  };
36
38
  editor.normalizeNode = (entry)=>{
37
39
  const [node] = entry;
38
- assignId(node, this.options.generateId);
40
+ assignId(node, this.generateId);
39
41
  normalizeNode(entry);
40
42
  };
41
43
  return editor;
@@ -2,7 +2,7 @@ import { type MapStore } from 'nanostores';
2
2
  import { Editor } from 'slate';
3
3
  import { ReactEditor, type RenderElementProps } from 'slate-react';
4
4
  import type { IPlugin } from '../../types';
5
- import type { Options } from './types';
5
+ import { type Options } from './types';
6
6
  export declare class DnDPlugin implements IPlugin {
7
7
  private options;
8
8
  store: MapStore<{
@@ -2,9 +2,16 @@ import type React from 'react';
2
2
  import type { ConnectDragPreview, ConnectDragSource, ConnectDropTarget } from 'react-dnd';
3
3
  import type { Editor, Path } from 'slate';
4
4
  import type { RenderElementProps } from 'slate-react';
5
- import type { CustomElement } from '../../../types';
6
- export type NodeWithId = CustomElement & {
5
+ import type { CustomElement, CustomText } from '../../../types';
6
+ import type { NodeIdPlugin } from '../NodeIdPlugin';
7
+ export type NodeWithId = Omit<CustomElement, 'children'> & {
7
8
  nodeId: string;
9
+ children: (NodeWithId | CustomText)[];
10
+ };
11
+ export declare const isNodeWithId: (node: unknown) => node is NodeWithId;
12
+ export type CustomDropHandler<T = unknown> = {
13
+ type: string | symbol;
14
+ onDrop: (context: CustomDropContext<T>) => DropResult;
8
15
  };
9
16
  export type Options = {
10
17
  editorId?: string;
@@ -18,6 +25,7 @@ export type Options = {
18
25
  position: 'top' | 'bottom' | null;
19
26
  selected?: boolean;
20
27
  onToggleSelected: () => void;
28
+ onNativeDrop?: (event: React.DragEvent<HTMLDivElement>) => void;
21
29
  }) => React.ReactNode;
22
30
  ignoreNodes?: string[];
23
31
  customTypes?: {
@@ -27,26 +35,23 @@ export type Options = {
27
35
  };
28
36
  };
29
37
  customDropHandlers?: {
30
- [type: string | symbol]: {
31
- type: string | symbol;
32
- onDrop: <T = unknown>(context: CustomDropContext<T>) => DropResult;
33
- };
38
+ [type: string | symbol]: CustomDropHandler;
34
39
  };
40
+ nodeIdPlugin?: NodeIdPlugin;
35
41
  };
36
- type CustomDropContext<T = unknown> = {
42
+ export type CustomDropContext<TItem = unknown> = {
37
43
  editor: Editor;
38
- item: T;
44
+ data: EditorOuterDragItem<TItem>;
39
45
  insertAt: Path;
40
46
  };
41
47
  type DropResult = 'handled' | 'ignored';
42
- /**
43
- * We're using this type to represent a drag item that is being dragged from the editor.
44
- */
45
- export type EditorDragItem = {
48
+ export type EditorInnerDragItem<TItem = unknown> = {
49
+ kind: 'inner';
46
50
  nodes: NodeWithId[];
47
- item?: unknown;
48
- items?: unknown[];
51
+ item?: TItem;
52
+ items?: TItem[];
49
53
  source: {
54
+ editor: Editor;
50
55
  /**
51
56
  * Different editors have different unique identifiers.
52
57
  */
@@ -60,4 +65,13 @@ export type EditorDragItem = {
60
65
  };
61
66
  data?: Record<string, unknown>;
62
67
  };
68
+ export type EditorOuterDragItem<TItem = unknown> = {
69
+ kind: 'outer';
70
+ item: TItem;
71
+ items: TItem[];
72
+ };
73
+ /**
74
+ * We're using this type to represent a drag item that is being dragged from the editor.
75
+ */
76
+ export type EditorDragItem<TItem = unknown> = EditorInnerDragItem<TItem> | EditorOuterDragItem<TItem>;
63
77
  export {};
@@ -9,6 +9,7 @@ type Options = {
9
9
  };
10
10
  export declare class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
11
11
  private options;
12
+ generateId: () => string;
12
13
  constructor(options: Options);
13
14
  init(editor: Editor): import("slate").BaseEditor & import("slate-react").ReactEditor & {
14
15
  getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-kona/editor",
3
- "version": "0.1.24-dnd.6",
3
+ "version": "0.1.24-dnd.7",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -4,7 +4,7 @@ import { type MapStore, map } from 'nanostores';
4
4
  import { useEffect, useRef, useState } from 'react';
5
5
  import { type ConnectDropTarget, useDrag, useDrop } from 'react-dnd';
6
6
  import { NativeTypes } from 'react-dnd-html5-backend';
7
- import { Editor, Path, Transforms } from 'slate';
7
+ import { Editor, Element, Path, Transforms } from 'slate';
8
8
  import {
9
9
  ReactEditor,
10
10
  type RenderElementProps,
@@ -13,7 +13,12 @@ import {
13
13
  } from 'slate-react';
14
14
  import type { CustomElement } from '../../../types';
15
15
  import type { IPlugin } from '../../types';
16
- import type { EditorDragItem, NodeWithId, Options } from './types';
16
+ import {
17
+ type EditorDragItem,
18
+ isNodeWithId,
19
+ type NodeWithId,
20
+ type Options,
21
+ } from './types';
17
22
  import { getNodesByNodeIds } from './utils';
18
23
 
19
24
  export class DnDPlugin implements IPlugin {
@@ -63,7 +68,9 @@ export class DnDPlugin implements IPlugin {
63
68
 
64
69
  const $store = useStore(this.store);
65
70
 
71
+ const isCopying = useRef(false);
66
72
  const options = this.options;
73
+ const generateId = this.options?.nodeIdPlugin?.generateId;
67
74
  const isReadOnly = useReadOnly();
68
75
 
69
76
  const [dropPosition, setDropPosition] = useState<'top' | 'bottom' | null>(
@@ -91,7 +98,7 @@ export class DnDPlugin implements IPlugin {
91
98
  : [structuredClone(props.element as NodeWithId)];
92
99
 
93
100
  const item =
94
- this.options.customTypes?.[currentElement.type].getData?.(
101
+ this.options.customTypes?.[currentElement.type]?.getData?.(
95
102
  currentElement,
96
103
  );
97
104
 
@@ -105,10 +112,12 @@ export class DnDPlugin implements IPlugin {
105
112
  .filter(Boolean);
106
113
 
107
114
  const dragItem: EditorDragItem = {
115
+ kind: 'inner',
108
116
  nodes,
109
117
  item,
110
118
  items,
111
119
  source: {
120
+ editor,
112
121
  editorId: this.getEditorId(editor),
113
122
  documentId: options.documentId,
114
123
  },
@@ -126,7 +135,11 @@ export class DnDPlugin implements IPlugin {
126
135
  (customType) => customType.type,
127
136
  );
128
137
 
129
- const [{ isOver }, drop] = useDrop({
138
+ const [{ isOver }, drop] = useDrop<
139
+ EditorDragItem,
140
+ void,
141
+ { isOver: boolean }
142
+ >({
130
143
  accept: [
131
144
  DnDPlugin.DND_BLOCK_ELEMENT,
132
145
  NativeTypes.FILE,
@@ -157,8 +170,18 @@ export class DnDPlugin implements IPlugin {
157
170
  setDropPosition('bottom');
158
171
  }
159
172
  },
160
- drop: (item, monitor) => {
173
+ drop: (data, monitor) => {
161
174
  const itemType = monitor.getItemType();
175
+ const sourceEditor = data.kind === 'inner' ? data.source?.editor : null;
176
+
177
+ const isSameDocument =
178
+ data.kind === 'inner' &&
179
+ data.source?.documentId === this.options.documentId &&
180
+ this.options.documentId !== undefined;
181
+ const isSameEditor =
182
+ data.kind === 'inner' &&
183
+ data.source?.editorId === this.editors.get(editor) &&
184
+ this.editors.get(editor) !== undefined;
162
185
 
163
186
  const sourceTo = ReactEditor.findPath(editor, props.element);
164
187
 
@@ -175,8 +198,8 @@ export class DnDPlugin implements IPlugin {
175
198
  const customDropHandler =
176
199
  itemType && options.customDropHandlers?.[itemType];
177
200
 
178
- if (customDropHandler) {
179
- customDropHandler.onDrop({ editor, item, insertAt });
201
+ if (customDropHandler && data.kind === 'outer') {
202
+ customDropHandler.onDrop({ editor, data, insertAt });
180
203
  return;
181
204
  }
182
205
 
@@ -184,12 +207,17 @@ export class DnDPlugin implements IPlugin {
184
207
  case NativeTypes.FILE:
185
208
  options.onDropFiles(
186
209
  editor,
187
- (item as { files: File[] }).files,
210
+ (data as unknown as { files: File[] }).files,
188
211
  insertAt,
189
212
  );
190
213
  break;
191
214
  default: {
192
- const dragItem = item as EditorDragItem;
215
+ const dragItem = data as EditorDragItem;
216
+
217
+ // We don't know what "default" behavior for the outer items is
218
+ if (dragItem.kind === 'outer') {
219
+ return;
220
+ }
193
221
 
194
222
  if (!dropPosition || !dragItem.nodes.length) {
195
223
  return;
@@ -197,12 +225,35 @@ export class DnDPlugin implements IPlugin {
197
225
 
198
226
  const nodeIds = dragItem.nodes.map((node) => node.nodeId);
199
227
 
200
- moveNode(editor, sourceTo, nodeIds, dropPosition);
228
+ if (isSameDocument || isSameEditor) {
229
+ if (!isCopying.current) {
230
+ moveNode(editor, sourceTo, nodeIds, dropPosition);
231
+ } else {
232
+ if (!generateId) {
233
+ return;
234
+ }
235
+
236
+ const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
237
+ insertNodes(editor, insertAt, clonedNodes);
238
+ }
239
+ } else {
240
+ if (!generateId) {
241
+ return;
242
+ }
243
+
244
+ const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
245
+
246
+ insertNodes(editor, insertAt, clonedNodes);
247
+ if (!isCopying.current && sourceEditor) {
248
+ removeNodes(sourceEditor, nodeIds);
249
+ }
250
+ }
201
251
  $store.selected.clear();
202
252
  break;
203
253
  }
204
254
  }
205
255
 
256
+ isCopying.current = false;
206
257
  setDropPosition(null);
207
258
  },
208
259
  });
@@ -240,6 +291,9 @@ export class DnDPlugin implements IPlugin {
240
291
  this.handleToggleSelected(currentElement.nodeId);
241
292
  }
242
293
  },
294
+ onNativeDrop: (event) => {
295
+ isCopying.current = event.altKey;
296
+ },
243
297
  });
244
298
  };
245
299
  }
@@ -295,3 +349,52 @@ const moveNode = (
295
349
  });
296
350
  Editor.normalize(editor);
297
351
  };
352
+
353
+ const insertNodes = (editor: Editor, path: Path, nodes: NodeWithId[]) => {
354
+ Editor.withoutNormalizing(editor, () => {
355
+ Transforms.insertNodes(editor, nodes, { at: path });
356
+ });
357
+ };
358
+
359
+ const removeNodes = (editor: Editor, nodeIds: string[]) => {
360
+ try {
361
+ Transforms.removeNodes(editor, {
362
+ at: [],
363
+ match: (n) => {
364
+ if (!Element.isElement(n)) {
365
+ return false;
366
+ }
367
+
368
+ if (!Editor.isBlock(editor, n)) {
369
+ return false;
370
+ }
371
+
372
+ const node = n as NodeWithId;
373
+ return typeof node.nodeId === 'string' && nodeIds.includes(node.nodeId);
374
+ },
375
+ mode: 'highest',
376
+ });
377
+ } catch {
378
+ return;
379
+ }
380
+ };
381
+
382
+ const cloneWithNewIds = (
383
+ nodes: NodeWithId[],
384
+ generateId: () => string,
385
+ ): NodeWithId[] => {
386
+ const copy = structuredClone(nodes);
387
+
388
+ const assignId = (node: NodeWithId): void => {
389
+ node.nodeId = generateId();
390
+ node.children.forEach((node) => {
391
+ if ('children' in node) {
392
+ assignId(node);
393
+ }
394
+ });
395
+ };
396
+
397
+ copy.forEach(assignId);
398
+
399
+ return copy;
400
+ };
@@ -7,15 +7,25 @@ import type {
7
7
  } from 'react-dnd';
8
8
  import type { Editor, Path } from 'slate';
9
9
  import type { RenderElementProps } from 'slate-react';
10
- import type { CustomElement } from '../../../types';
10
+ import type { CustomElement, CustomText } from '../../../types';
11
+ import type { NodeIdPlugin } from '../NodeIdPlugin';
11
12
 
12
13
  type DropParams = {
13
14
  editor: Editor;
14
15
  item: unknown;
15
16
  };
16
17
 
17
- export type NodeWithId = CustomElement & {
18
+ export type NodeWithId = Omit<CustomElement, 'children'> & {
18
19
  nodeId: string;
20
+ children: (NodeWithId | CustomText)[];
21
+ };
22
+
23
+ export const isNodeWithId = (node: unknown): node is NodeWithId =>
24
+ typeof node === 'object' && node !== null && 'nodeId' in node;
25
+
26
+ export type CustomDropHandler<T = unknown> = {
27
+ type: string | symbol;
28
+ onDrop: (context: CustomDropContext<T>) => DropResult;
19
29
  };
20
30
 
21
31
  export type Options = {
@@ -30,6 +40,7 @@ export type Options = {
30
40
  position: 'top' | 'bottom' | null;
31
41
  selected?: boolean;
32
42
  onToggleSelected: () => void;
43
+ onNativeDrop?: (event: React.DragEvent<HTMLDivElement>) => void;
33
44
  }) => React.ReactNode;
34
45
  ignoreNodes?: string[];
35
46
  customTypes?: {
@@ -39,29 +50,26 @@ export type Options = {
39
50
  };
40
51
  };
41
52
  customDropHandlers?: {
42
- [type: string | symbol]: {
43
- type: string | symbol;
44
- onDrop: <T = unknown>(context: CustomDropContext<T>) => DropResult;
45
- };
53
+ [type: string | symbol]: CustomDropHandler;
46
54
  };
55
+ nodeIdPlugin?: NodeIdPlugin;
47
56
  };
48
57
 
49
- type CustomDropContext<T = unknown> = {
58
+ export type CustomDropContext<TItem = unknown> = {
50
59
  editor: Editor;
51
- item: T;
60
+ data: EditorOuterDragItem<TItem>;
52
61
  insertAt: Path;
53
62
  };
54
63
 
55
64
  type DropResult = 'handled' | 'ignored';
56
65
 
57
- /**
58
- * We're using this type to represent a drag item that is being dragged from the editor.
59
- */
60
- export type EditorDragItem = {
66
+ export type EditorInnerDragItem<TItem = unknown> = {
67
+ kind: 'inner';
61
68
  nodes: NodeWithId[];
62
- item?: unknown;
63
- items?: unknown[];
69
+ item?: TItem;
70
+ items?: TItem[];
64
71
  source: {
72
+ editor: Editor;
65
73
  /**
66
74
  * Different editors have different unique identifiers.
67
75
  */
@@ -75,3 +83,16 @@ export type EditorDragItem = {
75
83
  };
76
84
  data?: Record<string, unknown>;
77
85
  };
86
+
87
+ export type EditorOuterDragItem<TItem = unknown> = {
88
+ kind: 'outer';
89
+ item: TItem;
90
+ items: TItem[];
91
+ };
92
+
93
+ /**
94
+ * We're using this type to represent a drag item that is being dragged from the editor.
95
+ */
96
+ export type EditorDragItem<TItem = unknown> =
97
+ | EditorInnerDragItem<TItem>
98
+ | EditorOuterDragItem<TItem>;
@@ -26,7 +26,11 @@ type Options = {
26
26
  };
27
27
 
28
28
  export class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
29
- constructor(private options: Options) {}
29
+ generateId: () => string;
30
+
31
+ constructor(private options: Options) {
32
+ this.generateId = options.generateId;
33
+ }
30
34
 
31
35
  init(editor: Editor) {
32
36
  const { apply, normalizeNode } = editor;
@@ -39,14 +43,13 @@ export class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
39
43
  const newType = (operation.newProperties as CustomElement).type;
40
44
 
41
45
  if (!node || (!Text.isText(node) && oldType !== newType)) {
42
- (operation.newProperties as NodeIdBlock).nodeId =
43
- this.options.generateId();
46
+ (operation.newProperties as NodeIdBlock).nodeId = this.generateId();
44
47
  }
45
48
  return apply(operation);
46
49
  }
47
50
 
48
51
  if (operation.type === 'insert_node') {
49
- assignId(operation.node, this.options.generateId);
52
+ assignId(operation.node, this.generateId);
50
53
  return apply(operation);
51
54
  }
52
55
 
@@ -54,8 +57,7 @@ export class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
54
57
  const node = Node.get(editor, operation.path);
55
58
 
56
59
  if (!node || !Text.isText(node)) {
57
- (operation.properties as NodeIdBlock).nodeId =
58
- this.options.generateId();
60
+ (operation.properties as NodeIdBlock).nodeId = this.generateId();
59
61
  }
60
62
  return apply(operation);
61
63
  }
@@ -66,7 +68,7 @@ export class NodeIdPlugin implements IPlugin<Editor, NodeIdBlock> {
66
68
  editor.normalizeNode = (entry: NodeEntry) => {
67
69
  const [node] = entry;
68
70
 
69
- assignId(node, this.options.generateId);
71
+ assignId(node, this.generateId);
70
72
 
71
73
  normalizeNode(entry);
72
74
  };