@use-kona/editor 0.1.24-dnd.5 → 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,14 +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
  ];
60
+ const item = this.options.customTypes?.[currentElement.type]?.getData?.(currentElement);
58
61
  const items = nodes.map((node)=>{
59
62
  if (this.options.customTypes?.[node.type]) return this.options.customTypes[node.type].getData?.(node);
60
63
  return null;
61
64
  }).filter(Boolean);
62
65
  const dragItem = {
66
+ kind: 'inner',
63
67
  nodes,
68
+ item,
64
69
  items,
65
70
  source: {
71
+ editor,
66
72
  editorId: this.getEditorId(editor),
67
73
  documentId: options.documentId
68
74
  }
@@ -98,32 +104,47 @@ class DnDPlugin {
98
104
  const hoverClientY = clientOffset.y - hoverBoundingRect.top;
99
105
  hoverClientY < hoverMiddleY ? setDropPosition('top') : setDropPosition('bottom');
100
106
  },
101
- drop: (item, monitor)=>{
107
+ drop: (data, monitor)=>{
102
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);
103
112
  const sourceTo = ReactEditor.findPath(editor, props.element);
104
113
  if (!sourceTo) return;
105
114
  const insertAt = getDropPath(editor, sourceTo, dropPosition || 'bottom');
106
115
  if (!insertAt) return;
107
116
  const customDropHandler = itemType && options.customDropHandlers?.[itemType];
108
- if (customDropHandler) return void customDropHandler.onDrop({
117
+ if (customDropHandler && 'outer' === data.kind) return void customDropHandler.onDrop({
109
118
  editor,
110
- item,
119
+ data,
111
120
  insertAt
112
121
  });
113
122
  switch(itemType){
114
123
  case NativeTypes.FILE:
115
- options.onDropFiles(editor, item.files, insertAt);
124
+ options.onDropFiles(editor, data.files, insertAt);
116
125
  break;
117
126
  default:
118
127
  {
119
- const dragItem = item;
128
+ const dragItem = data;
129
+ if ('outer' === dragItem.kind) return;
120
130
  if (!dropPosition || !dragItem.nodes.length) return;
121
131
  const nodeIds = dragItem.nodes.map((node)=>node.nodeId);
122
- 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
+ }
123
143
  $store.selected.clear();
124
144
  break;
125
145
  }
126
146
  }
147
+ isCopying.current = false;
127
148
  setDropPosition(null);
128
149
  }
129
150
  });
@@ -147,6 +168,9 @@ class DnDPlugin {
147
168
  selected: 'string' == typeof currentElement.nodeId ? $store.selected.has(currentElement.nodeId) : false,
148
169
  onToggleSelected: ()=>{
149
170
  if ('string' == typeof currentElement.nodeId) this.handleToggleSelected(currentElement.nodeId);
171
+ },
172
+ onNativeDrop: (event)=>{
173
+ isCopying.current = event.altKey;
150
174
  }
151
175
  });
152
176
  };
@@ -180,4 +204,38 @@ const moveNode = (editor, targetNodePath, nodeIds, position)=>{
180
204
  });
181
205
  Editor.normalize(editor);
182
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
+ };
183
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,25 +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
- items?: unknown[];
51
+ item?: TItem;
52
+ items?: TItem[];
48
53
  source: {
54
+ editor: Editor;
49
55
  /**
50
56
  * Different editors have different unique identifiers.
51
57
  */
@@ -59,4 +65,13 @@ export type EditorDragItem = {
59
65
  };
60
66
  data?: Record<string, unknown>;
61
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>;
62
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.5",
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>(
@@ -90,6 +97,11 @@ export class DnDPlugin implements IPlugin {
90
97
  ? getNodesByNodeIds(editor, selected)
91
98
  : [structuredClone(props.element as NodeWithId)];
92
99
 
100
+ const item =
101
+ this.options.customTypes?.[currentElement.type]?.getData?.(
102
+ currentElement,
103
+ );
104
+
93
105
  const items = nodes
94
106
  .map((node) => {
95
107
  if (this.options.customTypes?.[node.type]) {
@@ -100,9 +112,12 @@ export class DnDPlugin implements IPlugin {
100
112
  .filter(Boolean);
101
113
 
102
114
  const dragItem: EditorDragItem = {
115
+ kind: 'inner',
103
116
  nodes,
117
+ item,
104
118
  items,
105
119
  source: {
120
+ editor,
106
121
  editorId: this.getEditorId(editor),
107
122
  documentId: options.documentId,
108
123
  },
@@ -120,7 +135,11 @@ export class DnDPlugin implements IPlugin {
120
135
  (customType) => customType.type,
121
136
  );
122
137
 
123
- const [{ isOver }, drop] = useDrop({
138
+ const [{ isOver }, drop] = useDrop<
139
+ EditorDragItem,
140
+ void,
141
+ { isOver: boolean }
142
+ >({
124
143
  accept: [
125
144
  DnDPlugin.DND_BLOCK_ELEMENT,
126
145
  NativeTypes.FILE,
@@ -151,8 +170,18 @@ export class DnDPlugin implements IPlugin {
151
170
  setDropPosition('bottom');
152
171
  }
153
172
  },
154
- drop: (item, monitor) => {
173
+ drop: (data, monitor) => {
155
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;
156
185
 
157
186
  const sourceTo = ReactEditor.findPath(editor, props.element);
158
187
 
@@ -169,8 +198,8 @@ export class DnDPlugin implements IPlugin {
169
198
  const customDropHandler =
170
199
  itemType && options.customDropHandlers?.[itemType];
171
200
 
172
- if (customDropHandler) {
173
- customDropHandler.onDrop({ editor, item, insertAt });
201
+ if (customDropHandler && data.kind === 'outer') {
202
+ customDropHandler.onDrop({ editor, data, insertAt });
174
203
  return;
175
204
  }
176
205
 
@@ -178,12 +207,17 @@ export class DnDPlugin implements IPlugin {
178
207
  case NativeTypes.FILE:
179
208
  options.onDropFiles(
180
209
  editor,
181
- (item as { files: File[] }).files,
210
+ (data as unknown as { files: File[] }).files,
182
211
  insertAt,
183
212
  );
184
213
  break;
185
214
  default: {
186
- 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
+ }
187
221
 
188
222
  if (!dropPosition || !dragItem.nodes.length) {
189
223
  return;
@@ -191,12 +225,35 @@ export class DnDPlugin implements IPlugin {
191
225
 
192
226
  const nodeIds = dragItem.nodes.map((node) => node.nodeId);
193
227
 
194
- 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
+ }
195
251
  $store.selected.clear();
196
252
  break;
197
253
  }
198
254
  }
199
255
 
256
+ isCopying.current = false;
200
257
  setDropPosition(null);
201
258
  },
202
259
  });
@@ -234,6 +291,9 @@ export class DnDPlugin implements IPlugin {
234
291
  this.handleToggleSelected(currentElement.nodeId);
235
292
  }
236
293
  },
294
+ onNativeDrop: (event) => {
295
+ isCopying.current = event.altKey;
296
+ },
237
297
  });
238
298
  };
239
299
  }
@@ -289,3 +349,52 @@ const moveNode = (
289
349
  });
290
350
  Editor.normalize(editor);
291
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,28 +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
- items?: unknown[];
69
+ item?: TItem;
70
+ items?: TItem[];
63
71
  source: {
72
+ editor: Editor;
64
73
  /**
65
74
  * Different editors have different unique identifiers.
66
75
  */
@@ -74,3 +83,16 @@ export type EditorDragItem = {
74
83
  };
75
84
  data?: Record<string, unknown>;
76
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
  };