@blocknote/core 0.1.0 → 0.1.2
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/README.md +1 -1
- package/dist/blocknote.js +3454 -2426
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +35 -71
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +7 -6
- package/src/BlockNoteTheme.ts +150 -0
- package/src/extensions/Blocks/BlockAttributes.ts +12 -0
- package/src/extensions/Blocks/MultipleNodeSelection.ts +87 -0
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +8 -2
- package/src/extensions/Blocks/nodes/Block.module.css +37 -37
- package/src/extensions/Blocks/nodes/Block.ts +79 -32
- package/src/extensions/Blocks/nodes/BlockGroup.ts +18 -1
- package/src/extensions/Blocks/nodes/Content.ts +14 -1
- package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +8 -1
- package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +116 -88
- package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +8 -8
- package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +143 -33
- package/src/extensions/DraggableBlocks/components/DragHandle.tsx +14 -19
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +8 -7
- package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +31 -66
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +44 -0
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +34 -0
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +31 -0
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +40 -0
- package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +37 -0
- package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +63 -0
- package/src/extensions/SlashMenu/SlashMenuItem.ts +3 -1
- package/src/extensions/SlashMenu/defaultCommands.tsx +4 -4
- package/src/extensions/UniqueID/UniqueID.ts +0 -11
- package/src/shared/components/toolbar/Toolbar.tsx +8 -3
- package/src/shared/components/toolbar/ToolbarButton.tsx +57 -0
- package/src/shared/components/toolbar/ToolbarDropdown.tsx +35 -0
- package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +35 -0
- package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +31 -0
- package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -1
- package/src/shared/plugins/suggestion/{SuggestionListReactRenderer.ts → SuggestionListReactRenderer.tsx} +13 -4
- package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +6 -93
- package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +82 -0
- package/src/shared/plugins/suggestion/components/SuggestionList.tsx +24 -23
- package/src/utils.ts +12 -0
- package/types/src/BlockNoteTheme.d.ts +2 -0
- package/types/src/commands/indentation.d.ts +2 -0
- package/types/src/extensions/Blocks/BlockAttributes.d.ts +2 -0
- package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +24 -0
- package/types/src/extensions/Blocks/nodes/Block.d.ts +1 -1
- package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +2 -2
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +11 -0
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +13 -0
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +8 -0
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +9 -0
- package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +12 -0
- package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +21 -0
- package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +39 -0
- package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +3 -0
- package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +13 -0
- package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +4 -7
- package/types/src/nodes/ChildgroupNode.d.ts +28 -0
- package/types/src/nodes/patchNodes.d.ts +1 -0
- package/types/src/plugins/TreeViewPlugin/index.d.ts +2 -0
- package/types/src/plugins/animation.d.ts +2 -0
- package/types/src/react/BlockNoteComposer.d.ts +17 -0
- package/types/src/react/BlockNotePlugin.d.ts +1 -0
- package/types/src/react/index.d.ts +3 -0
- package/types/src/react/useBlockNoteSetup.d.ts +2 -0
- package/types/src/registerBlockNote.d.ts +2 -0
- package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +2 -3
- package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +11 -0
- package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +11 -0
- package/types/src/shared/components/toolbar/Toolbar.d.ts +2 -2
- package/types/src/shared/components/toolbar/ToolbarButton.d.ts +15 -0
- package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +17 -0
- package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +11 -0
- package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +8 -0
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -4
- package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +9 -0
- package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -15
- package/types/src/themes/BlockNoteEditorTheme.d.ts +11 -0
- package/types/src/utils.d.ts +2 -0
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +0 -13
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +0 -25
- package/src/extensions/DraggableBlocks/components/DragHandle.module.css +0 -33
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +0 -10
- package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +0 -59
- package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +0 -72
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +0 -173
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +0 -36
- package/src/extensions/Hyperlinks/menus/atlaskit/README.md +0 -1
- package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +0 -61
- package/src/extensions/helpers/formatKeyboardShortcut.ts +0 -9
- package/src/lib/atlaskit/browser.ts +0 -47
- package/src/shared/components/toolbar/SimpleToolbarButton.module.css +0 -13
- package/src/shared/components/toolbar/SimpleToolbarButton.tsx +0 -56
- package/src/shared/components/toolbar/Toolbar.module.css +0 -10
- package/src/shared/components/toolbar/ToolbarSeparator.module.css +0 -13
- package/src/shared/components/toolbar/ToolbarSeparator.tsx +0 -7
- package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +0 -45
- package/src/shared/plugins/suggestion/components/SuggestionList.module.css +0 -10
|
@@ -13,7 +13,23 @@ export const BlockGroup = Node.create({
|
|
|
13
13
|
content: "block+",
|
|
14
14
|
|
|
15
15
|
parseHTML() {
|
|
16
|
-
return [
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
tag: "div",
|
|
19
|
+
getAttrs: (element) => {
|
|
20
|
+
if(typeof element === "string") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if(element.getAttribute("data-node-type") === "block-group") {
|
|
25
|
+
// Null means the element matches, but we don't want to add any attributes to the node.
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
];
|
|
17
33
|
},
|
|
18
34
|
|
|
19
35
|
renderHTML({ HTMLAttributes }) {
|
|
@@ -21,6 +37,7 @@ export const BlockGroup = Node.create({
|
|
|
21
37
|
"div",
|
|
22
38
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
23
39
|
class: styles.blockGroup,
|
|
40
|
+
"data-node-type": "block-group"
|
|
24
41
|
}),
|
|
25
42
|
0,
|
|
26
43
|
];
|
|
@@ -32,7 +32,19 @@ export const ContentBlock = Node.create<IBlock>({
|
|
|
32
32
|
return [
|
|
33
33
|
{
|
|
34
34
|
tag: "div",
|
|
35
|
-
|
|
35
|
+
getAttrs: (element) => {
|
|
36
|
+
if(typeof element === "string") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if(element.getAttribute("data-node-type") === "block-content") {
|
|
41
|
+
// Null means the element matches, but we don't want to add any attributes to the node.
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
36
48
|
];
|
|
37
49
|
},
|
|
38
50
|
|
|
@@ -41,6 +53,7 @@ export const ContentBlock = Node.create<IBlock>({
|
|
|
41
53
|
"div",
|
|
42
54
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
43
55
|
class: styles.blockContent,
|
|
56
|
+
"data-node-type": "block-content"
|
|
44
57
|
}),
|
|
45
58
|
// TODO: The extra nested div is only needed for placeholders, different solution (without extra div) would be preferable
|
|
46
59
|
// We can't use the other div because the ::before attribute on that one is already reserved for list-bullets
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { MantineProvider } from "@mantine/core";
|
|
1
2
|
import { Extension } from "@tiptap/core";
|
|
2
3
|
import { PluginKey } from "prosemirror-state";
|
|
3
4
|
import ReactDOM from "react-dom";
|
|
5
|
+
import { BlockNoteTheme } from "../../BlockNoteTheme";
|
|
4
6
|
import rootStyles from "../../root.module.css";
|
|
5
7
|
import { createBubbleMenuPlugin } from "./BubbleMenuPlugin";
|
|
6
8
|
import { BubbleMenu } from "./component/BubbleMenu";
|
|
@@ -14,7 +16,12 @@ export const BubbleMenuExtension = Extension.create<{}>({
|
|
|
14
16
|
addProseMirrorPlugins() {
|
|
15
17
|
const element = document.createElement("div");
|
|
16
18
|
element.className = rootStyles.bnRoot;
|
|
17
|
-
ReactDOM.render(
|
|
19
|
+
ReactDOM.render(
|
|
20
|
+
<MantineProvider theme={BlockNoteTheme}>
|
|
21
|
+
<BubbleMenu editor={this.editor} />
|
|
22
|
+
</MantineProvider>,
|
|
23
|
+
element
|
|
24
|
+
);
|
|
18
25
|
return [
|
|
19
26
|
createBubbleMenuPlugin({
|
|
20
27
|
editor: this.editor,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
|
|
2
1
|
import { Editor } from "@tiptap/core";
|
|
3
2
|
import {
|
|
4
3
|
RiBold,
|
|
@@ -15,13 +14,14 @@ import {
|
|
|
15
14
|
RiText,
|
|
16
15
|
RiUnderline,
|
|
17
16
|
} from "react-icons/ri";
|
|
18
|
-
import {
|
|
17
|
+
import { ToolbarButton } from "../../../shared/components/toolbar/ToolbarButton";
|
|
18
|
+
import { ToolbarDropdown } from "../../../shared/components/toolbar/ToolbarDropdown";
|
|
19
19
|
import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
|
|
20
20
|
import { useEditorForceUpdate } from "../../../shared/hooks/useEditorForceUpdate";
|
|
21
21
|
import { findBlock } from "../../Blocks/helpers/findBlock";
|
|
22
|
-
import formatKeyboardShortcut from "
|
|
23
|
-
import DropdownBlockItem from "./DropdownBlockItem";
|
|
22
|
+
import { formatKeyboardShortcut } from "../../../utils";
|
|
24
23
|
import LinkToolbarButton from "./LinkToolbarButton";
|
|
24
|
+
import { IconType } from "react-icons";
|
|
25
25
|
|
|
26
26
|
type ListType = "li" | "oli";
|
|
27
27
|
|
|
@@ -59,123 +59,151 @@ export const BubbleMenu = (props: { editor: Editor }) => {
|
|
|
59
59
|
currentBlockListType
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
+
const blockIconMap: Record<string, IconType> = {
|
|
63
|
+
Text: RiText,
|
|
64
|
+
"Heading 1": RiH1,
|
|
65
|
+
"Heading 2": RiH2,
|
|
66
|
+
"Heading 3": RiH3,
|
|
67
|
+
"Bullet List": RiListUnordered,
|
|
68
|
+
"Numbered List": RiListOrdered,
|
|
69
|
+
};
|
|
70
|
+
|
|
62
71
|
return (
|
|
63
72
|
<Toolbar>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
props.editor.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
<ToolbarDropdown
|
|
74
|
+
text={currentBlockName}
|
|
75
|
+
icon={blockIconMap[currentBlockName]}
|
|
76
|
+
items={[
|
|
77
|
+
{
|
|
78
|
+
onClick: () => {
|
|
79
|
+
// Setting editor focus using a chained command instead causes bubble menu to flicker on click.
|
|
80
|
+
props.editor.view.focus();
|
|
81
|
+
props.editor.chain().unsetBlockHeading().unsetList().run();
|
|
82
|
+
},
|
|
83
|
+
text: "Text",
|
|
84
|
+
icon: RiText,
|
|
85
|
+
isSelected: currentBlockName === "Text",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
onClick: () => {
|
|
89
|
+
props.editor.view.focus();
|
|
79
90
|
props.editor
|
|
80
91
|
.chain()
|
|
81
|
-
.focus()
|
|
82
92
|
.unsetList()
|
|
83
|
-
.setBlockHeading({ level: 1 })
|
|
84
|
-
.run()
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
onClick
|
|
93
|
+
.setBlockHeading({ level: "1" })
|
|
94
|
+
.run();
|
|
95
|
+
},
|
|
96
|
+
text: "Heading 1",
|
|
97
|
+
icon: RiH1,
|
|
98
|
+
isSelected: currentBlockName === "Heading 1",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
onClick: () => {
|
|
102
|
+
props.editor.view.focus();
|
|
92
103
|
props.editor
|
|
93
104
|
.chain()
|
|
94
|
-
.focus()
|
|
95
105
|
.unsetList()
|
|
96
|
-
.setBlockHeading({ level: 2 })
|
|
97
|
-
.run()
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
onClick
|
|
106
|
+
.setBlockHeading({ level: "2" })
|
|
107
|
+
.run();
|
|
108
|
+
},
|
|
109
|
+
text: "Heading 2",
|
|
110
|
+
icon: RiH2,
|
|
111
|
+
isSelected: currentBlockName === "Heading 2",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
onClick: () => {
|
|
115
|
+
props.editor.view.focus();
|
|
105
116
|
props.editor
|
|
106
117
|
.chain()
|
|
107
|
-
.focus()
|
|
108
118
|
.unsetList()
|
|
109
|
-
.setBlockHeading({ level: 3 })
|
|
110
|
-
.run()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
onClick
|
|
118
|
-
props.editor
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
icon={RiListOrdered}
|
|
129
|
-
isSelected={currentBlockName === "Numbered List"}
|
|
130
|
-
onClick={() =>
|
|
119
|
+
.setBlockHeading({ level: "3" })
|
|
120
|
+
.run();
|
|
121
|
+
},
|
|
122
|
+
text: "Heading 3",
|
|
123
|
+
icon: RiH3,
|
|
124
|
+
isSelected: currentBlockName === "Heading 3",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
onClick: () => {
|
|
128
|
+
props.editor.view.focus();
|
|
129
|
+
props.editor.chain().unsetBlockHeading().setBlockList("li").run();
|
|
130
|
+
},
|
|
131
|
+
text: "Bullet List",
|
|
132
|
+
icon: RiListUnordered,
|
|
133
|
+
isSelected: currentBlockName === "Bullet List",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
onClick: () => {
|
|
137
|
+
props.editor.view.focus();
|
|
131
138
|
props.editor
|
|
132
139
|
.chain()
|
|
133
|
-
.focus()
|
|
134
140
|
.unsetBlockHeading()
|
|
135
141
|
.setBlockList("oli")
|
|
136
|
-
.run()
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
.run();
|
|
143
|
+
},
|
|
144
|
+
text: "Numbered List",
|
|
145
|
+
icon: RiListOrdered,
|
|
146
|
+
isSelected: currentBlockName === "Numbered List",
|
|
147
|
+
},
|
|
148
|
+
]}
|
|
149
|
+
/>
|
|
150
|
+
<ToolbarButton
|
|
151
|
+
onClick={() => {
|
|
152
|
+
// Setting editor focus using a chained command instead causes bubble menu to flicker on click.
|
|
153
|
+
props.editor.view.focus();
|
|
154
|
+
props.editor.commands.toggleBold();
|
|
155
|
+
}}
|
|
143
156
|
isSelected={props.editor.isActive("bold")}
|
|
144
157
|
mainTooltip="Bold"
|
|
145
158
|
secondaryTooltip={formatKeyboardShortcut("Mod+B")}
|
|
146
159
|
icon={RiBold}
|
|
147
160
|
/>
|
|
148
|
-
<
|
|
149
|
-
onClick={() =>
|
|
161
|
+
<ToolbarButton
|
|
162
|
+
onClick={() => {
|
|
163
|
+
props.editor.view.focus();
|
|
164
|
+
props.editor.commands.toggleItalic();
|
|
165
|
+
}}
|
|
150
166
|
isSelected={props.editor.isActive("italic")}
|
|
151
167
|
mainTooltip="Italic"
|
|
152
168
|
secondaryTooltip={formatKeyboardShortcut("Mod+I")}
|
|
153
169
|
icon={RiItalic}
|
|
154
170
|
/>
|
|
155
|
-
<
|
|
156
|
-
onClick={() =>
|
|
171
|
+
<ToolbarButton
|
|
172
|
+
onClick={() => {
|
|
173
|
+
props.editor.view.focus();
|
|
174
|
+
props.editor.commands.toggleUnderline();
|
|
175
|
+
}}
|
|
157
176
|
isSelected={props.editor.isActive("underline")}
|
|
158
177
|
mainTooltip="Underline"
|
|
159
178
|
secondaryTooltip={formatKeyboardShortcut("Mod+U")}
|
|
160
179
|
icon={RiUnderline}
|
|
161
180
|
/>
|
|
162
|
-
<
|
|
163
|
-
onClick={() =>
|
|
164
|
-
|
|
181
|
+
<ToolbarButton
|
|
182
|
+
onClick={() => {
|
|
183
|
+
props.editor.view.focus();
|
|
184
|
+
props.editor.commands.toggleStrike();
|
|
185
|
+
}}
|
|
186
|
+
isSelected={props.editor.isActive("strike")}
|
|
165
187
|
mainTooltip="Strike-through"
|
|
166
188
|
secondaryTooltip={formatKeyboardShortcut("Mod+Shift+X")}
|
|
167
189
|
icon={RiStrikethrough}
|
|
168
190
|
/>
|
|
169
|
-
<
|
|
170
|
-
onClick={() =>
|
|
191
|
+
<ToolbarButton
|
|
192
|
+
onClick={() => {
|
|
193
|
+
props.editor.view.focus();
|
|
194
|
+
props.editor.commands.sinkListItem("block");
|
|
195
|
+
}}
|
|
171
196
|
isDisabled={!props.editor.can().sinkListItem("block")}
|
|
172
197
|
mainTooltip="Indent"
|
|
173
198
|
secondaryTooltip={formatKeyboardShortcut("Tab")}
|
|
174
199
|
icon={RiIndentIncrease}
|
|
175
200
|
/>
|
|
176
201
|
|
|
177
|
-
<
|
|
178
|
-
onClick={() =>
|
|
202
|
+
<ToolbarButton
|
|
203
|
+
onClick={() => {
|
|
204
|
+
props.editor.view.focus();
|
|
205
|
+
props.editor.commands.liftListItem("block");
|
|
206
|
+
}}
|
|
179
207
|
isDisabled={
|
|
180
208
|
!props.editor.can().command(({ state }) => {
|
|
181
209
|
const block = findBlock(state.selection);
|
|
@@ -200,13 +228,13 @@ export const BubbleMenu = (props: { editor: Editor }) => {
|
|
|
200
228
|
editor={props.editor}
|
|
201
229
|
/>
|
|
202
230
|
{/* <SimpleBubbleMenuButton
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
editor={props.editor}
|
|
232
|
+
onClick={() => {
|
|
233
|
+
const comment = this.props.commentStore.createComment();
|
|
234
|
+
props.editor.chain().focus().setComment(comment.id).run();
|
|
235
|
+
}}
|
|
236
|
+
styleDetails={comment}
|
|
237
|
+
/> */}
|
|
210
238
|
</Toolbar>
|
|
211
239
|
);
|
|
212
240
|
};
|
|
@@ -2,12 +2,12 @@ import Tippy from "@tippyjs/react";
|
|
|
2
2
|
import { Editor } from "@tiptap/core";
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "../../../shared/components/toolbar/
|
|
8
|
-
import {
|
|
5
|
+
ToolbarButton,
|
|
6
|
+
ToolbarButtonProps,
|
|
7
|
+
} from "../../../shared/components/toolbar/ToolbarButton";
|
|
8
|
+
import { EditHyperlinkMenu } from "../../Hyperlinks/menus/EditHyperlinkMenu";
|
|
9
9
|
|
|
10
|
-
type Props =
|
|
10
|
+
type Props = ToolbarButtonProps & {
|
|
11
11
|
editor: Editor;
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -41,11 +41,11 @@ export const LinkToolbarButton = (props: Props) => {
|
|
|
41
41
|
: "";
|
|
42
42
|
|
|
43
43
|
setCreationMenu(
|
|
44
|
-
<
|
|
44
|
+
<EditHyperlinkMenu
|
|
45
45
|
key={Math.random() + ""} // Math.random to prevent old element from being re-used
|
|
46
46
|
url={activeUrl}
|
|
47
47
|
text={selectedText}
|
|
48
|
-
|
|
48
|
+
update={onSubmit}
|
|
49
49
|
/>
|
|
50
50
|
);
|
|
51
51
|
}, [props.editor]);
|
|
@@ -59,7 +59,7 @@ export const LinkToolbarButton = (props: Props) => {
|
|
|
59
59
|
}}
|
|
60
60
|
interactive={true}
|
|
61
61
|
maxWidth={500}>
|
|
62
|
-
<
|
|
62
|
+
<ToolbarButton {...props} />
|
|
63
63
|
</Tippy>
|
|
64
64
|
);
|
|
65
65
|
};
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { NodeSelection, Plugin, PluginKey } from "prosemirror-state";
|
|
1
|
+
import { NodeSelection, Plugin, PluginKey, Selection } from "prosemirror-state";
|
|
2
|
+
import { Node } from "prosemirror-model";
|
|
2
3
|
import * as pv from "prosemirror-view";
|
|
3
4
|
import { EditorView } from "prosemirror-view";
|
|
4
5
|
import ReactDOM from "react-dom";
|
|
5
6
|
import { DragHandle } from "./components/DragHandle";
|
|
7
|
+
import { MantineProvider } from "@mantine/core";
|
|
8
|
+
import { BlockNoteTheme } from "../../BlockNoteTheme";
|
|
9
|
+
import { MultipleNodeSelection } from "../Blocks/MultipleNodeSelection";
|
|
6
10
|
|
|
7
11
|
const serializeForClipboard = (pv as any).__serializeForClipboard;
|
|
8
12
|
// code based on https://github.com/ueberdosis/tiptap/issues/323#issuecomment-506637799
|
|
9
13
|
|
|
10
14
|
let horizontalAnchor: number;
|
|
15
|
+
let dragImageElement: Element | undefined;
|
|
16
|
+
|
|
11
17
|
function getHorizontalAnchor() {
|
|
12
18
|
if (!horizontalAnchor) {
|
|
13
19
|
const firstBlockGroup = document.querySelector(
|
|
@@ -38,24 +44,6 @@ export function absoluteRect(element: HTMLElement) {
|
|
|
38
44
|
return createRect(element.getBoundingClientRect());
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
function blockPosAtCoords(
|
|
42
|
-
coords: { left: number; top: number },
|
|
43
|
-
view: EditorView
|
|
44
|
-
) {
|
|
45
|
-
let block = getDraggableBlockFromCoords(coords, view);
|
|
46
|
-
|
|
47
|
-
if (block && block.node.nodeType === 1) {
|
|
48
|
-
// TODO: this uses undocumented PM APIs? do we need this / let's add docs?
|
|
49
|
-
const docView = (view as any).docView;
|
|
50
|
-
let desc = docView.nearestDesc(block.node, true);
|
|
51
|
-
if (!desc || desc === docView) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
return desc.posBefore;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
47
|
function getDraggableBlockFromCoords(
|
|
60
48
|
coords: { left: number; top: number },
|
|
61
49
|
view: EditorView
|
|
@@ -85,6 +73,107 @@ function getDraggableBlockFromCoords(
|
|
|
85
73
|
return { node, id: node.getAttribute("data-id")! };
|
|
86
74
|
}
|
|
87
75
|
|
|
76
|
+
function blockPositionFromCoords(
|
|
77
|
+
coords: { left: number; top: number },
|
|
78
|
+
view: EditorView
|
|
79
|
+
) {
|
|
80
|
+
let block = getDraggableBlockFromCoords(coords, view);
|
|
81
|
+
|
|
82
|
+
if (block && block.node.nodeType === 1) {
|
|
83
|
+
// TODO: this uses undocumented PM APIs? do we need this / let's add docs?
|
|
84
|
+
const docView = (view as any).docView;
|
|
85
|
+
let desc = docView.nearestDesc(block.node, true);
|
|
86
|
+
if (!desc || desc === docView) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return desc.posBefore;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function blockPositionsFromSelection(
|
|
95
|
+
selection: Selection,
|
|
96
|
+
doc: Node
|
|
97
|
+
) {
|
|
98
|
+
// Absolute positions just before the first block spanned by the selection, and just after the last block. Having the
|
|
99
|
+
// selection start and end just before and just after the target blocks ensures no whitespace/line breaks are left
|
|
100
|
+
// behind after dragging & dropping them.
|
|
101
|
+
let beforeFirstBlockPos: number;
|
|
102
|
+
let afterLastBlockPos: number;
|
|
103
|
+
|
|
104
|
+
// Even the user starts dragging blocks but drops them in the same place, the selection will still be moved just
|
|
105
|
+
// before & just after the blocks spanned by the selection, and therefore doesn't need to change if they try to drag
|
|
106
|
+
// the same blocks again. If this happens, the anchor & head move out of the block content node they were originally
|
|
107
|
+
// in. If the anchor should update but the head shouldn't and vice versa, it means the user selection is outside a
|
|
108
|
+
// block content node, which should never happen.
|
|
109
|
+
const selectionStartInBlockContent =
|
|
110
|
+
doc.resolve(selection.from).node().type.name === "content";
|
|
111
|
+
const selectionEndInBlockContent =
|
|
112
|
+
doc.resolve(selection.to).node().type.name === "content";
|
|
113
|
+
|
|
114
|
+
// Ensures that entire outermost nodes are selected if the selection spans multiple nesting levels.
|
|
115
|
+
const minDepth = Math.min(selection.$anchor.depth, selection.$head.depth);
|
|
116
|
+
|
|
117
|
+
if (selectionStartInBlockContent && selectionEndInBlockContent) {
|
|
118
|
+
// Absolute positions at the start of the first block in the selection and at the end of the last block. User
|
|
119
|
+
// selections will always start and end in block content nodes, but we want the start and end positions of their
|
|
120
|
+
// parent block nodes, which is why minDepth - 1 is used.
|
|
121
|
+
const startFirstBlockPos = selection.$from.start(minDepth - 1);
|
|
122
|
+
const endLastBlockPos = selection.$to.end(minDepth - 1);
|
|
123
|
+
|
|
124
|
+
// Shifting start and end positions by one moves them just outside the first and last selected blocks.
|
|
125
|
+
beforeFirstBlockPos = doc.resolve(startFirstBlockPos - 1).pos;
|
|
126
|
+
afterLastBlockPos = doc.resolve(endLastBlockPos + 1).pos;
|
|
127
|
+
} else {
|
|
128
|
+
beforeFirstBlockPos = selection.from;
|
|
129
|
+
afterLastBlockPos = selection.to;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { from: beforeFirstBlockPos, to: afterLastBlockPos };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function setDragImage(view: EditorView, from: number, to = from) {
|
|
136
|
+
if (from === to) {
|
|
137
|
+
// Moves to position to be just after the first (and only) selected block.
|
|
138
|
+
to += view.state.doc.resolve(from + 1).node().nodeSize;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Parent element is cloned to remove all unselected children without affecting the editor content.
|
|
142
|
+
const parentClone = view.domAtPos(from).node.cloneNode(true) as Element;
|
|
143
|
+
const parent = view.domAtPos(from).node as Element;
|
|
144
|
+
|
|
145
|
+
const getElementIndex = (parentElement: Element, targetElement: Element) =>
|
|
146
|
+
Array.prototype.indexOf.call(parentElement.children, targetElement);
|
|
147
|
+
|
|
148
|
+
const firstSelectedBlockIndex = getElementIndex(
|
|
149
|
+
parent,
|
|
150
|
+
// Expects from position to be just before the first selected block.
|
|
151
|
+
view.domAtPos(from + 1).node.parentElement!
|
|
152
|
+
);
|
|
153
|
+
const lastSelectedBlockIndex = getElementIndex(
|
|
154
|
+
parent,
|
|
155
|
+
// Expects to position to be just after the last selected block.
|
|
156
|
+
view.domAtPos(to - 1).node.parentElement!
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
for (let i = parent.childElementCount - 1; i >= 0; i--) {
|
|
160
|
+
if (i > lastSelectedBlockIndex || i < firstSelectedBlockIndex) {
|
|
161
|
+
parentClone.removeChild(parentClone.children[i]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// dataTransfer.setDragImage(element) only works if element is attached to the DOM.
|
|
166
|
+
dragImageElement = parentClone;
|
|
167
|
+
document.body.appendChild(dragImageElement);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function unsetDragImage() {
|
|
171
|
+
if (dragImageElement !== undefined) {
|
|
172
|
+
document.body.removeChild(dragImageElement);
|
|
173
|
+
dragImageElement = undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
88
177
|
function dragStart(e: DragEvent, view: EditorView) {
|
|
89
178
|
if (!e.dataTransfer) {
|
|
90
179
|
return;
|
|
@@ -94,11 +183,30 @@ function dragStart(e: DragEvent, view: EditorView) {
|
|
|
94
183
|
left: view.dom.clientWidth / 2, // take middle of editor
|
|
95
184
|
top: e.clientY,
|
|
96
185
|
};
|
|
97
|
-
|
|
186
|
+
|
|
187
|
+
let pos = blockPositionFromCoords(coords, view);
|
|
98
188
|
if (pos != null) {
|
|
99
|
-
view.
|
|
100
|
-
|
|
101
|
-
|
|
189
|
+
const selection = view.state.selection;
|
|
190
|
+
const doc = view.state.doc;
|
|
191
|
+
|
|
192
|
+
const { from, to } = blockPositionsFromSelection(selection, doc);
|
|
193
|
+
|
|
194
|
+
const draggedBlockInSelection = from <= pos && pos < to;
|
|
195
|
+
const multipleBlocksSelected = !selection.$anchor
|
|
196
|
+
.node()
|
|
197
|
+
.eq(selection.$head.node());
|
|
198
|
+
|
|
199
|
+
if (draggedBlockInSelection && multipleBlocksSelected) {
|
|
200
|
+
view.dispatch(
|
|
201
|
+
view.state.tr.setSelection(MultipleNodeSelection.create(doc, from, to))
|
|
202
|
+
);
|
|
203
|
+
setDragImage(view, from, to);
|
|
204
|
+
} else {
|
|
205
|
+
view.dispatch(
|
|
206
|
+
view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos))
|
|
207
|
+
);
|
|
208
|
+
setDragImage(view, pos);
|
|
209
|
+
}
|
|
102
210
|
|
|
103
211
|
let slice = view.state.selection.content();
|
|
104
212
|
let { dom, text } = serializeForClipboard(view, slice);
|
|
@@ -107,8 +215,7 @@ function dragStart(e: DragEvent, view: EditorView) {
|
|
|
107
215
|
e.dataTransfer.setData("text/html", dom.innerHTML);
|
|
108
216
|
e.dataTransfer.setData("text/plain", text);
|
|
109
217
|
e.dataTransfer.effectAllowed = "move";
|
|
110
|
-
|
|
111
|
-
e.dataTransfer.setDragImage(block?.node as any, 0, 0);
|
|
218
|
+
e.dataTransfer.setDragImage(dragImageElement!, 0, 0);
|
|
112
219
|
view.dragging = { slice, move: true };
|
|
113
220
|
}
|
|
114
221
|
}
|
|
@@ -147,6 +254,7 @@ export const createDraggableBlocksPlugin = () => {
|
|
|
147
254
|
dropElement.addEventListener("dragstart", (e) =>
|
|
148
255
|
dragStart(e, editorView)
|
|
149
256
|
);
|
|
257
|
+
dropElement.addEventListener("dragend", () => unsetDragImage());
|
|
150
258
|
|
|
151
259
|
return {
|
|
152
260
|
// update(view, prevState) {},
|
|
@@ -248,14 +356,16 @@ export const createDraggableBlocksPlugin = () => {
|
|
|
248
356
|
dropElement.style.top = rect.top + "px";
|
|
249
357
|
|
|
250
358
|
ReactDOM.render(
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
359
|
+
<MantineProvider theme={BlockNoteTheme}>
|
|
360
|
+
<DragHandle
|
|
361
|
+
onShow={onShow}
|
|
362
|
+
onHide={onHide}
|
|
363
|
+
onAddClicked={onAddClicked}
|
|
364
|
+
key={block.id + ""}
|
|
365
|
+
view={view}
|
|
366
|
+
coords={coords}
|
|
367
|
+
/>
|
|
368
|
+
</MantineProvider>,
|
|
259
369
|
dropElement
|
|
260
370
|
);
|
|
261
371
|
return true;
|