@use-kona/editor 0.1.24 → 0.1.25
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/core/createEditable.js +146 -15
- package/dist/core/createEditor.js +1 -1
- package/dist/core/styles.module.js +3 -1
- package/dist/core/styles_module.css +13 -0
- package/dist/elements/Block.js +25 -0
- package/dist/index.js +2 -1
- package/dist/plugins/CodeBlockPlugin/CodeBlockPlugin.js +1 -1
- package/dist/plugins/DnDPlugin/DnDPlugin.js +93 -45
- package/dist/plugins/DnDPlugin/types.js +2 -0
- package/dist/plugins/HeadingsPlugin/HeadingsPlugin.js +1 -1
- package/dist/plugins/ListsPlugin/ListsPlugin.js +1 -1
- package/dist/plugins/NodeIdPlugin/NodeIdPlugin.js +6 -4
- package/dist/provider.js +15 -0
- package/dist/src/core/createEditable.d.ts +1 -1
- package/dist/src/elements/Block.d.ts +2 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/plugins/DnDPlugin/DnDPlugin.d.ts +0 -7
- package/dist/src/plugins/DnDPlugin/types.d.ts +29 -15
- package/dist/src/plugins/NodeIdPlugin/NodeIdPlugin.d.ts +1 -0
- package/dist/src/provider.d.ts +8 -0
- package/dist/src/types.d.ts +4 -0
- package/package.json +9 -8
- package/src/core/createEditable.tsx +170 -18
- package/src/core/createEditor.ts +1 -1
- package/src/core/styles.module.css +12 -0
- package/src/editor.tsx +4 -3
- package/src/elements/Block.tsx +31 -0
- package/src/index.ts +1 -0
- package/src/plugins/CodeBlockPlugin/CodeBlockPlugin.tsx +1 -3
- package/src/plugins/DnDPlugin/DnDPlugin.tsx +147 -62
- package/src/plugins/DnDPlugin/types.ts +35 -20
- package/src/plugins/HeadingsPlugin/HeadingsPlugin.tsx +1 -2
- package/src/plugins/ListsPlugin/ListsPlugin.tsx +5 -5
- package/src/plugins/NodeIdPlugin/NodeIdPlugin.ts +9 -7
- package/src/provider.tsx +28 -0
- package/src/types.ts +5 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useStore } from "@nanostores/react";
|
|
2
3
|
import is_hotkey from "is-hotkey";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { atom } from "nanostores";
|
|
5
|
+
import react, { isValidElement, useCallback, useState } from "react";
|
|
6
|
+
import { Editor, Element, Path, Transforms } from "slate";
|
|
7
|
+
import { Editable, ReactEditor, useReadOnly } from "slate-react";
|
|
6
8
|
import { BaseElement } from "../elements/BaseElement.js";
|
|
9
|
+
import { Block } from "../elements/Block.js";
|
|
10
|
+
import { EditorProvider } from "../provider.js";
|
|
7
11
|
import { deserialize } from "./deserialize.js";
|
|
8
12
|
import styles_module from "./styles.module.js";
|
|
9
13
|
const SUPPORTED_HANDLERS = [
|
|
@@ -13,6 +17,8 @@ const SUPPORTED_HANDLERS = [
|
|
|
13
17
|
'onPaste'
|
|
14
18
|
];
|
|
15
19
|
const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
20
|
+
const [selectedNodes] = useState(()=>atom([]));
|
|
21
|
+
const $selectedNodes = useStore(selectedNodes);
|
|
16
22
|
const renderElement = (props)=>{
|
|
17
23
|
let result = null;
|
|
18
24
|
for (const plugin of plugins)for (const element of plugin.blocks || []){
|
|
@@ -24,13 +30,20 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
24
30
|
...props,
|
|
25
31
|
children: result || props.children
|
|
26
32
|
});
|
|
27
|
-
return result || /*#__PURE__*/ jsx(BaseElement, {
|
|
33
|
+
if (Editor.isInline(editor, props.element)) return result || /*#__PURE__*/ jsx(BaseElement, {
|
|
28
34
|
...props
|
|
29
35
|
});
|
|
36
|
+
return /*#__PURE__*/ jsx(Block, {
|
|
37
|
+
element: props.element,
|
|
38
|
+
attributes: props.attributes,
|
|
39
|
+
children: result || /*#__PURE__*/ jsx(BaseElement, {
|
|
40
|
+
...props
|
|
41
|
+
})
|
|
42
|
+
});
|
|
30
43
|
};
|
|
31
44
|
const renderLeaf = (props)=>{
|
|
32
45
|
let result;
|
|
33
|
-
for (const plugin of plugins)plugin.leafs?.
|
|
46
|
+
for (const plugin of plugins)plugin.leafs?.forEach((element)=>{
|
|
34
47
|
const newElement = element.render({
|
|
35
48
|
...props,
|
|
36
49
|
children: result || props.children
|
|
@@ -41,6 +54,121 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
41
54
|
};
|
|
42
55
|
const handleHotkey = (event)=>{
|
|
43
56
|
if ('Tab' === event.key) event.preventDefault();
|
|
57
|
+
if ('Escape' === event.key) {
|
|
58
|
+
const currentBlock = Editor.above(editor, {
|
|
59
|
+
at: editor.selection?.focus,
|
|
60
|
+
match: (n)=>Element.isElement(n) && Editor.isBlock(editor, n)
|
|
61
|
+
});
|
|
62
|
+
if (!currentBlock) return;
|
|
63
|
+
if (0 === $selectedNodes.length) selectedNodes.set([
|
|
64
|
+
currentBlock[0]
|
|
65
|
+
]);
|
|
66
|
+
else {
|
|
67
|
+
selectedNodes.set([]);
|
|
68
|
+
ReactEditor.blur(editor);
|
|
69
|
+
}
|
|
70
|
+
} else if ('ArrowDown' === event.key) {
|
|
71
|
+
const currentBlock = Editor.above(editor, {
|
|
72
|
+
at: editor.selection?.focus,
|
|
73
|
+
match: (n)=>Element.isElement(n)
|
|
74
|
+
});
|
|
75
|
+
if (!currentBlock) return;
|
|
76
|
+
const nextBlock = Editor.next(editor, {
|
|
77
|
+
at: currentBlock[1],
|
|
78
|
+
match: (n)=>Element.isElement(n),
|
|
79
|
+
voids: true
|
|
80
|
+
});
|
|
81
|
+
if (nextBlock) {
|
|
82
|
+
if (Editor.isVoid(editor, nextBlock[0])) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
selectedNodes.set([
|
|
85
|
+
nextBlock[0]
|
|
86
|
+
]);
|
|
87
|
+
} else selectedNodes.set([]);
|
|
88
|
+
if (Editor.isVoid(editor, currentBlock[0]) || Editor.isVoid(editor, nextBlock[0])) {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
Transforms.select(editor, nextBlock[1]);
|
|
91
|
+
Transforms.collapse(editor, {
|
|
92
|
+
edge: 'start'
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if ('ArrowUp' === event.key) {
|
|
97
|
+
const currentBlock = Editor.above(editor, {
|
|
98
|
+
at: editor.selection?.focus,
|
|
99
|
+
match: (n)=>Element.isElement(n)
|
|
100
|
+
});
|
|
101
|
+
if (!currentBlock) return;
|
|
102
|
+
const prevBlock = Editor.previous(editor, {
|
|
103
|
+
at: currentBlock[1],
|
|
104
|
+
match: (n)=>Element.isElement(n),
|
|
105
|
+
voids: true
|
|
106
|
+
});
|
|
107
|
+
if (prevBlock) {
|
|
108
|
+
if (Editor.isVoid(editor, prevBlock[0])) {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
selectedNodes.set([
|
|
111
|
+
prevBlock[0]
|
|
112
|
+
]);
|
|
113
|
+
} else selectedNodes.set([]);
|
|
114
|
+
if (Editor.isVoid(editor, currentBlock[0]) || Editor.isVoid(editor, prevBlock[0])) {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
Transforms.select(editor, prevBlock[1]);
|
|
117
|
+
Transforms.collapse(editor, {
|
|
118
|
+
edge: 'end'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else if ('Backspace' === event.key) {
|
|
123
|
+
const currentBlock = Editor.above(editor, {
|
|
124
|
+
at: editor.selection?.focus,
|
|
125
|
+
match: (n)=>Element.isElement(n) && Editor.isBlock(editor, n)
|
|
126
|
+
});
|
|
127
|
+
if (!currentBlock) return;
|
|
128
|
+
if (editor.selection?.focus.offset !== 0) return;
|
|
129
|
+
const prevBlock = Editor.previous(editor, {
|
|
130
|
+
at: currentBlock[1],
|
|
131
|
+
match: (n)=>Element.isElement(n),
|
|
132
|
+
voids: true
|
|
133
|
+
});
|
|
134
|
+
if (prevBlock && Editor.isVoid(editor, prevBlock[0])) {
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
Transforms.removeNodes(editor, {
|
|
137
|
+
at: currentBlock[1]
|
|
138
|
+
});
|
|
139
|
+
Transforms.select(editor, prevBlock[1]);
|
|
140
|
+
selectedNodes.set([
|
|
141
|
+
prevBlock[0]
|
|
142
|
+
]);
|
|
143
|
+
Transforms.collapse(editor, {
|
|
144
|
+
edge: 'start'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} else if ('Enter' === event.key) {
|
|
148
|
+
const currentBlock = Editor.above(editor, {
|
|
149
|
+
at: editor.selection?.focus,
|
|
150
|
+
match: (n)=>Element.isElement(n)
|
|
151
|
+
});
|
|
152
|
+
if (!currentBlock) return;
|
|
153
|
+
if (Editor.isVoid(editor, currentBlock[0])) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
Transforms.insertNodes(editor, {
|
|
156
|
+
type: 'paragraph',
|
|
157
|
+
children: [
|
|
158
|
+
{
|
|
159
|
+
text: ''
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}, {
|
|
163
|
+
at: Path.next(currentBlock[1])
|
|
164
|
+
});
|
|
165
|
+
selectedNodes.set([]);
|
|
166
|
+
Transforms.select(editor, Path.next(currentBlock[1]));
|
|
167
|
+
Transforms.collapse(editor, {
|
|
168
|
+
edge: 'start'
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
44
172
|
for (const plugin of plugins)if (plugin.hotkeys) {
|
|
45
173
|
for (const hotkey of plugin.hotkeys)if (is_hotkey(hotkey[0], event) && hotkey[1]) {
|
|
46
174
|
event.preventDefault();
|
|
@@ -95,7 +223,7 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
95
223
|
},
|
|
96
224
|
children: [
|
|
97
225
|
ui.map((p, index)=>/*#__PURE__*/ jsx(react.Fragment, {
|
|
98
|
-
children: p.ui({
|
|
226
|
+
children: p.ui?.({
|
|
99
227
|
readOnly,
|
|
100
228
|
children,
|
|
101
229
|
editor
|
|
@@ -111,15 +239,18 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
111
239
|
editor,
|
|
112
240
|
plugins.filter
|
|
113
241
|
]);
|
|
114
|
-
return /*#__PURE__*/ jsx(
|
|
115
|
-
|
|
116
|
-
children: /*#__PURE__*/ jsx(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
242
|
+
return /*#__PURE__*/ jsx(EditorProvider, {
|
|
243
|
+
selectedNodes: selectedNodes,
|
|
244
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
245
|
+
className: styles_module.root,
|
|
246
|
+
children: /*#__PURE__*/ jsx(Ui, {
|
|
247
|
+
children: /*#__PURE__*/ jsx(Editable, {
|
|
248
|
+
decorate: decorate,
|
|
249
|
+
renderElement: renderElement,
|
|
250
|
+
renderLeaf: renderLeaf,
|
|
251
|
+
readOnly: readOnly,
|
|
252
|
+
...handlers
|
|
253
|
+
})
|
|
123
254
|
})
|
|
124
255
|
})
|
|
125
256
|
});
|
|
@@ -136,7 +136,7 @@ const createEditor_createEditor = (plugins)=>()=>{
|
|
|
136
136
|
const hasOnBeforeDelete = match?.onBeforeDelete;
|
|
137
137
|
if (!hasOnBeforeDelete) return void deleteFragment(options);
|
|
138
138
|
{
|
|
139
|
-
const result = await match.onBeforeDelete([
|
|
139
|
+
const result = await match.onBeforeDelete?.([
|
|
140
140
|
node
|
|
141
141
|
]);
|
|
142
142
|
if (!result) return;
|
|
@@ -33,3 +33,16 @@
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
.block-sBBbL1 {
|
|
37
|
+
transition: background-color .2s ease-in-out;
|
|
38
|
+
|
|
39
|
+
&.selected-dNsAIz {
|
|
40
|
+
background-color: var(--kona-editor-selected-color);
|
|
41
|
+
|
|
42
|
+
& > * {
|
|
43
|
+
transition: transform .2s ease-in-out;
|
|
44
|
+
transform: scale(.98);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useStore } from "@nanostores/react";
|
|
3
|
+
import { Editor } from "slate";
|
|
4
|
+
import { useSlateStatic } from "slate-react";
|
|
5
|
+
import styles_module from "../core/styles.module.js";
|
|
6
|
+
import { useEditorContext } from "../provider.js";
|
|
7
|
+
const Block = (props)=>{
|
|
8
|
+
const editor = useSlateStatic();
|
|
9
|
+
const { selectedNodes } = useEditorContext();
|
|
10
|
+
const $selectedNodes = useStore(selectedNodes);
|
|
11
|
+
const isSelected = $selectedNodes.some((node)=>node === props.element);
|
|
12
|
+
const classes = isSelected ? `${styles_module.block} ${styles_module.selected}` : styles_module.block;
|
|
13
|
+
const handleClick = ()=>{
|
|
14
|
+
if (Editor.isVoid(editor, props.element)) selectedNodes.set([
|
|
15
|
+
props.element
|
|
16
|
+
]);
|
|
17
|
+
else selectedNodes.set([]);
|
|
18
|
+
};
|
|
19
|
+
return /*#__PURE__*/ jsx("div", {
|
|
20
|
+
className: classes,
|
|
21
|
+
onClick: handleClick,
|
|
22
|
+
children: props.children
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export { Block };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { deserialize } from "./core/deserialize.js";
|
|
2
2
|
import { serialize } from "./core/serialize.js";
|
|
3
3
|
import { defaultValue } from "./defaultValue.js";
|
|
4
|
+
import { useEditorContext } from "./provider.js";
|
|
4
5
|
import { isEmpty } from "./utils.js";
|
|
5
6
|
export * from "./editor.js";
|
|
6
7
|
export * from "./plugins/index.js";
|
|
7
|
-
export { defaultValue, deserialize, isEmpty, serialize };
|
|
8
|
+
export { defaultValue, deserialize, isEmpty, serialize, useEditorContext };
|
|
@@ -78,7 +78,7 @@ class CodeBlockPlugin {
|
|
|
78
78
|
if ('a' === event.key && (event.ctrlKey || event.metaKey) && node && node.type === CodeBlockPlugin.CODE_ELEMENT) {
|
|
79
79
|
event.preventDefault();
|
|
80
80
|
const entry = Editor.above(editor, {
|
|
81
|
-
match: (n)
|
|
81
|
+
match: (n)=>Element.isElement(n) && n.type === CodeBlockPlugin.CODE_ELEMENT
|
|
82
82
|
});
|
|
83
83
|
if (entry) {
|
|
84
84
|
const [, codeBlockPath] = entry;
|
|
@@ -1,38 +1,20 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
1
2
|
import { useStore } from "@nanostores/react";
|
|
2
|
-
import { nanoid } from "nanoid";
|
|
3
|
-
import { map } from "nanostores";
|
|
4
3
|
import { useEffect, useRef, useState } from "react";
|
|
5
4
|
import { useDrag, useDrop } from "react-dnd";
|
|
6
5
|
import { NativeTypes } from "react-dnd-html5-backend";
|
|
7
|
-
import { Editor, Path, Transforms } from "slate";
|
|
6
|
+
import { Editor, Element, Path, Transforms } from "slate";
|
|
8
7
|
import { ReactEditor, useReadOnly, useSlate } from "slate-react";
|
|
8
|
+
import { useEditorContext } from "../../provider.js";
|
|
9
9
|
import { getNodesByNodeIds } from "./utils.js";
|
|
10
10
|
class DnDPlugin {
|
|
11
11
|
options;
|
|
12
|
-
store;
|
|
13
|
-
editors;
|
|
14
12
|
constructor(options){
|
|
15
13
|
this.options = options;
|
|
16
|
-
this.store = map({
|
|
17
|
-
selected: new Set()
|
|
18
|
-
});
|
|
19
|
-
this.editors = new WeakMap();
|
|
20
14
|
}
|
|
21
15
|
init(editor) {
|
|
22
|
-
this.editors.set(editor, nanoid());
|
|
23
16
|
return editor;
|
|
24
17
|
}
|
|
25
|
-
getEditorId(editor) {
|
|
26
|
-
return this.editors.get(editor);
|
|
27
|
-
}
|
|
28
|
-
handleToggleSelected = (nodeId)=>{
|
|
29
|
-
const selected = new Set(this.store.get().selected);
|
|
30
|
-
if (selected.has(nodeId)) selected.delete(nodeId);
|
|
31
|
-
else selected.add(nodeId);
|
|
32
|
-
this.store.set({
|
|
33
|
-
selected
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
18
|
static DND_BLOCK_ELEMENT = 'block';
|
|
37
19
|
handlers = {
|
|
38
20
|
onDrop: (event)=>{
|
|
@@ -41,29 +23,35 @@ class DnDPlugin {
|
|
|
41
23
|
};
|
|
42
24
|
renderBlock = (props)=>{
|
|
43
25
|
const editor = useSlate();
|
|
44
|
-
const
|
|
26
|
+
const { selectedNodes } = useEditorContext();
|
|
27
|
+
const $selectedNodes = useStore(selectedNodes);
|
|
28
|
+
const isCopying = useRef(false);
|
|
45
29
|
const options = this.options;
|
|
30
|
+
const generateId = this.options?.nodeIdPlugin?.generateId;
|
|
46
31
|
const isReadOnly = useReadOnly();
|
|
47
32
|
const [dropPosition, setDropPosition] = useState(null);
|
|
48
33
|
const dropTargetRef = useRef(null);
|
|
49
|
-
options.customTypes?.[props.element.type];
|
|
34
|
+
const customType = options.customTypes?.[props.element.type];
|
|
50
35
|
const currentElement = props.element;
|
|
51
36
|
const [, drag, preview] = useDrag({
|
|
52
|
-
type: DnDPlugin.DND_BLOCK_ELEMENT,
|
|
37
|
+
type: customType?.type || DnDPlugin.DND_BLOCK_ELEMENT,
|
|
53
38
|
item: ()=>{
|
|
54
|
-
const selected =
|
|
55
|
-
const nodes = selected.length > 0 && selected.
|
|
39
|
+
const selected = $selectedNodes;
|
|
40
|
+
const nodes = selected.length > 0 && selected.find((node)=>node.nodeId === currentElement.nodeId) ? getNodesByNodeIds(editor, selected.map((node)=>node.nodeId)) : [
|
|
56
41
|
structuredClone(props.element)
|
|
57
42
|
];
|
|
43
|
+
const item = this.options.customTypes?.[currentElement.type]?.getData?.(currentElement);
|
|
58
44
|
const items = nodes.map((node)=>{
|
|
59
45
|
if (this.options.customTypes?.[node.type]) return this.options.customTypes[node.type].getData?.(node);
|
|
60
46
|
return null;
|
|
61
47
|
}).filter(Boolean);
|
|
62
48
|
const dragItem = {
|
|
49
|
+
kind: 'inner',
|
|
63
50
|
nodes,
|
|
51
|
+
item,
|
|
64
52
|
items,
|
|
65
53
|
source: {
|
|
66
|
-
|
|
54
|
+
editor,
|
|
67
55
|
documentId: options.documentId
|
|
68
56
|
}
|
|
69
57
|
};
|
|
@@ -71,7 +59,7 @@ class DnDPlugin {
|
|
|
71
59
|
},
|
|
72
60
|
canDrag: !isReadOnly,
|
|
73
61
|
end: ()=>{
|
|
74
|
-
|
|
62
|
+
selectedNodes.set([]);
|
|
75
63
|
}
|
|
76
64
|
});
|
|
77
65
|
const allCustomTypes = Object.values(options.customTypes || {}).map((customType)=>customType.type);
|
|
@@ -98,32 +86,47 @@ class DnDPlugin {
|
|
|
98
86
|
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
|
99
87
|
hoverClientY < hoverMiddleY ? setDropPosition('top') : setDropPosition('bottom');
|
|
100
88
|
},
|
|
101
|
-
drop (
|
|
89
|
+
drop: (data, monitor)=>{
|
|
102
90
|
const itemType = monitor.getItemType();
|
|
91
|
+
const sourceEditor = 'inner' === data.kind ? data.source?.editor : null;
|
|
92
|
+
const isSameDocument = 'inner' === data.kind && data.source?.documentId === this.options.documentId && void 0 !== this.options.documentId;
|
|
93
|
+
const isSameEditor = 'inner' === data.kind && data.source?.editor === editor;
|
|
103
94
|
const sourceTo = ReactEditor.findPath(editor, props.element);
|
|
104
95
|
if (!sourceTo) return;
|
|
105
96
|
const insertAt = getDropPath(editor, sourceTo, dropPosition || 'bottom');
|
|
106
97
|
if (!insertAt) return;
|
|
107
98
|
const customDropHandler = itemType && options.customDropHandlers?.[itemType];
|
|
108
|
-
if (customDropHandler) return void customDropHandler.onDrop({
|
|
99
|
+
if (customDropHandler && 'outer' === data.kind) return void customDropHandler.onDrop({
|
|
109
100
|
editor,
|
|
110
|
-
|
|
101
|
+
data,
|
|
111
102
|
insertAt
|
|
112
103
|
});
|
|
113
104
|
switch(itemType){
|
|
114
105
|
case NativeTypes.FILE:
|
|
115
|
-
options.onDropFiles(editor,
|
|
106
|
+
options.onDropFiles(editor, data.files, insertAt);
|
|
116
107
|
break;
|
|
117
108
|
default:
|
|
118
109
|
{
|
|
119
|
-
const dragItem =
|
|
110
|
+
const dragItem = data;
|
|
111
|
+
if ('outer' === dragItem.kind) return;
|
|
120
112
|
if (!dropPosition || !dragItem.nodes.length) return;
|
|
121
113
|
const nodeIds = dragItem.nodes.map((node)=>node.nodeId);
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
if (isSameDocument || isSameEditor) if (isCopying.current) {
|
|
115
|
+
if (!generateId) return;
|
|
116
|
+
const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
|
|
117
|
+
insertNodes(editor, insertAt, clonedNodes);
|
|
118
|
+
} else moveNode(editor, sourceTo, nodeIds, dropPosition);
|
|
119
|
+
else {
|
|
120
|
+
if (!generateId) return;
|
|
121
|
+
const clonedNodes = cloneWithNewIds(dragItem.nodes, generateId);
|
|
122
|
+
insertNodes(editor, insertAt, clonedNodes);
|
|
123
|
+
if (!isCopying.current && sourceEditor) removeNodes(sourceEditor, nodeIds);
|
|
124
|
+
}
|
|
125
|
+
selectedNodes.set([]);
|
|
124
126
|
break;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
129
|
+
isCopying.current = false;
|
|
127
130
|
setDropPosition(null);
|
|
128
131
|
}
|
|
129
132
|
});
|
|
@@ -138,16 +141,27 @@ class DnDPlugin {
|
|
|
138
141
|
]);
|
|
139
142
|
if (this.options.ignoreNodes?.includes(props.element.type)) return props.children;
|
|
140
143
|
if (editor.isInline(props.element)) return props.children;
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
return /*#__PURE__*/ jsx("div", {
|
|
145
|
+
children: options.renderBlock?.({
|
|
146
|
+
props,
|
|
147
|
+
dragRef: drag,
|
|
148
|
+
dropRef: connectDropRef,
|
|
149
|
+
previewRef: preview,
|
|
150
|
+
position: dropPosition,
|
|
151
|
+
onNativeDrop: (event)=>{
|
|
152
|
+
isCopying.current = event.altKey;
|
|
153
|
+
},
|
|
154
|
+
onToggleSelected: (event)=>{
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
if ($selectedNodes.includes(props.element)) selectedNodes.set([
|
|
157
|
+
...$selectedNodes
|
|
158
|
+
].filter((element)=>element !== props.element));
|
|
159
|
+
else selectedNodes.set([
|
|
160
|
+
...$selectedNodes,
|
|
161
|
+
props.element
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
})
|
|
151
165
|
});
|
|
152
166
|
};
|
|
153
167
|
}
|
|
@@ -180,4 +194,38 @@ const moveNode = (editor, targetNodePath, nodeIds, position)=>{
|
|
|
180
194
|
});
|
|
181
195
|
Editor.normalize(editor);
|
|
182
196
|
};
|
|
197
|
+
const insertNodes = (editor, path, nodes)=>{
|
|
198
|
+
Editor.withoutNormalizing(editor, ()=>{
|
|
199
|
+
Transforms.insertNodes(editor, nodes, {
|
|
200
|
+
at: path
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
const removeNodes = (editor, nodeIds)=>{
|
|
205
|
+
try {
|
|
206
|
+
Transforms.removeNodes(editor, {
|
|
207
|
+
at: [],
|
|
208
|
+
match: (n)=>{
|
|
209
|
+
if (!Element.isElement(n)) return false;
|
|
210
|
+
if (!Editor.isBlock(editor, n)) return false;
|
|
211
|
+
const node = n;
|
|
212
|
+
return 'string' == typeof node.nodeId && nodeIds.includes(node.nodeId);
|
|
213
|
+
},
|
|
214
|
+
mode: 'highest'
|
|
215
|
+
});
|
|
216
|
+
} catch {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const cloneWithNewIds = (nodes, generateId)=>{
|
|
221
|
+
const copy = structuredClone(nodes);
|
|
222
|
+
const assignId = (node)=>{
|
|
223
|
+
node.nodeId = generateId();
|
|
224
|
+
node.children.forEach((node)=>{
|
|
225
|
+
if ('children' in node) assignId(node);
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
copy.forEach(assignId);
|
|
229
|
+
return copy;
|
|
230
|
+
};
|
|
183
231
|
export { DnDPlugin };
|
|
@@ -85,7 +85,7 @@ const isBlockActive = (editor, type)=>{
|
|
|
85
85
|
if (!selection) return false;
|
|
86
86
|
const [match] = Array.from(Editor.nodes(editor, {
|
|
87
87
|
at: Editor.unhangRange(editor, selection),
|
|
88
|
-
match: (node)
|
|
88
|
+
match: (node)=>Element.isElement(node) && node.type === type
|
|
89
89
|
}));
|
|
90
90
|
return !!match;
|
|
91
91
|
};
|
|
@@ -195,7 +195,7 @@ class ListsPlugin {
|
|
|
195
195
|
if (!selection) return false;
|
|
196
196
|
const [match] = Array.from(Editor.nodes(editor, {
|
|
197
197
|
at: Editor.unhangRange(editor, selection),
|
|
198
|
-
match: (node)
|
|
198
|
+
match: (node)=>Element.isElement(node) && node.type === type
|
|
199
199
|
}));
|
|
200
200
|
return !!match;
|
|
201
201
|
};
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
40
|
+
assignId(node, this.generateId);
|
|
39
41
|
normalizeNode(entry);
|
|
40
42
|
};
|
|
41
43
|
return editor;
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const Context = /*#__PURE__*/ createContext(null);
|
|
4
|
+
const EditorProvider = ({ children, selectedNodes })=>/*#__PURE__*/ jsx(Context.Provider, {
|
|
5
|
+
value: {
|
|
6
|
+
selectedNodes
|
|
7
|
+
},
|
|
8
|
+
children: children
|
|
9
|
+
});
|
|
10
|
+
const useEditorContext = ()=>{
|
|
11
|
+
const context = useContext(Context);
|
|
12
|
+
if (!context) throw new Error('useEditorContext must be used within an EditorProvider');
|
|
13
|
+
return context;
|
|
14
|
+
};
|
|
15
|
+
export { EditorProvider, useEditorContext };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export { serialize } from './core/serialize';
|
|
|
4
4
|
export { defaultValue } from './defaultValue';
|
|
5
5
|
export * from './editor';
|
|
6
6
|
export * from './plugins';
|
|
7
|
+
export { useEditorContext } from './provider';
|
|
7
8
|
export type { EditorRef, IPlugin } from './types';
|
|
8
9
|
export { isEmpty } from './utils';
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import { type MapStore } from 'nanostores';
|
|
2
1
|
import { Editor } from 'slate';
|
|
3
2
|
import { ReactEditor, type RenderElementProps } from 'slate-react';
|
|
4
3
|
import type { IPlugin } from '../../types';
|
|
5
4
|
import type { Options } from './types';
|
|
6
5
|
export declare class DnDPlugin implements IPlugin {
|
|
7
6
|
private options;
|
|
8
|
-
store: MapStore<{
|
|
9
|
-
selected: Set<string>;
|
|
10
|
-
}>;
|
|
11
|
-
editors: WeakMap<Editor, string>;
|
|
12
7
|
constructor(options: Options);
|
|
13
8
|
init(editor: Editor): import("slate").BaseEditor & ReactEditor & {
|
|
14
9
|
getCommands: <T extends IPlugin>(plugin: new () => T) => T["commands"];
|
|
15
10
|
};
|
|
16
|
-
getEditorId(editor: Editor): string | undefined;
|
|
17
|
-
handleToggleSelected: (nodeId: string) => void;
|
|
18
11
|
static DND_BLOCK_ELEMENT: string;
|
|
19
12
|
handlers: {
|
|
20
13
|
onDrop: (event: DragEvent) => void;
|