@blocknote/core 0.1.0-alpha.3
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 +99 -0
- package/dist/blocknote.js +4485 -0
- package/dist/blocknote.js.map +1 -0
- package/dist/blocknote.umd.cjs +90 -0
- package/dist/blocknote.umd.cjs.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +109 -0
- package/src/BlockNoteExtensions.ts +90 -0
- package/src/EditorContent.tsx +1 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-100.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-100.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-200.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-200.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-300.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-300.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-500.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-500.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-600.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-600.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-700.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-700.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-800.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-800.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-900.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-900.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff2 +0 -0
- package/src/editor.module.css +3 -0
- package/src/extensions/Blocks/OrderedListPlugin.ts +46 -0
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +146 -0
- package/src/extensions/Blocks/commands/joinBackward.ts +274 -0
- package/src/extensions/Blocks/helpers/findBlock.ts +3 -0
- package/src/extensions/Blocks/helpers/setBlockHeading.ts +30 -0
- package/src/extensions/Blocks/index.ts +15 -0
- package/src/extensions/Blocks/nodes/Block.module.css +226 -0
- package/src/extensions/Blocks/nodes/Block.ts +390 -0
- package/src/extensions/Blocks/nodes/BlockGroup.ts +28 -0
- package/src/extensions/Blocks/nodes/Content.ts +50 -0
- package/src/extensions/Blocks/nodes/README.md +26 -0
- package/src/extensions/Blocks/rule.ts +48 -0
- package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +28 -0
- package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +245 -0
- package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +216 -0
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +13 -0
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +25 -0
- package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +67 -0
- package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +15 -0
- package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +266 -0
- package/src/extensions/DraggableBlocks/components/DragHandle.module.css +33 -0
- package/src/extensions/DraggableBlocks/components/DragHandle.tsx +108 -0
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +10 -0
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +18 -0
- package/src/extensions/Hyperlinks/HyperlinkMark.tsx +16 -0
- package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +200 -0
- package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +59 -0
- package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +72 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +173 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +36 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/README.md +1 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +61 -0
- package/src/extensions/Paragraph/FixedParagraph.ts +12 -0
- package/src/extensions/Placeholder/PlaceholderExtension.ts +127 -0
- package/src/extensions/SlashMenu/SlashMenuExtension.ts +43 -0
- package/src/extensions/SlashMenu/SlashMenuItem.ts +56 -0
- package/src/extensions/SlashMenu/defaultCommands.tsx +229 -0
- package/src/extensions/SlashMenu/index.ts +11 -0
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +70 -0
- package/src/extensions/UniqueID/UniqueID.ts +281 -0
- package/src/extensions/helpers/formatKeyboardShortcut.ts +9 -0
- package/src/fonts-inter.css +94 -0
- package/src/globals.css +28 -0
- package/src/index.ts +5 -0
- package/src/lib/atlaskit/browser.ts +47 -0
- package/src/root.module.css +19 -0
- package/src/shared/components/toolbar/SimpleToolbarButton.module.css +13 -0
- package/src/shared/components/toolbar/SimpleToolbarButton.tsx +56 -0
- package/src/shared/components/toolbar/Toolbar.module.css +10 -0
- package/src/shared/components/toolbar/Toolbar.tsx +5 -0
- package/src/shared/components/toolbar/ToolbarSeparator.module.css +13 -0
- package/src/shared/components/toolbar/ToolbarSeparator.tsx +7 -0
- package/src/shared/components/tooltip/TooltipContent.module.css +15 -0
- package/src/shared/components/tooltip/TooltipContent.tsx +23 -0
- package/src/shared/hooks/useEditorForceUpdate.tsx +30 -0
- package/src/shared/plugins/suggestion/SuggestionItem.ts +31 -0
- package/src/shared/plugins/suggestion/SuggestionListReactRenderer.ts +227 -0
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +365 -0
- package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +45 -0
- package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +134 -0
- package/src/shared/plugins/suggestion/components/SuggestionList.module.css +10 -0
- package/src/shared/plugins/suggestion/components/SuggestionList.tsx +91 -0
- package/src/style.css +7 -0
- package/src/useEditor.ts +47 -0
- package/src/vite-env.d.ts +1 -0
- package/types/src/BlockNoteExtensions.d.ts +4 -0
- package/types/src/EditorContent.d.ts +1 -0
- package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +2 -0
- package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +13 -0
- package/types/src/extensions/Blocks/commands/joinBackward.d.ts +14 -0
- package/types/src/extensions/Blocks/helpers/findBlock.d.ts +6 -0
- package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +5 -0
- package/types/src/extensions/Blocks/index.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/Block.d.ts +32 -0
- package/types/src/extensions/Blocks/nodes/BlockGroup.d.ts +2 -0
- package/types/src/extensions/Blocks/nodes/Content.d.ts +5 -0
- package/types/src/extensions/Blocks/rule.d.ts +16 -0
- package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +5 -0
- package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +46 -0
- package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +5 -0
- package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +10 -0
- package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +11 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +7 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +18 -0
- package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +12 -0
- package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +6 -0
- package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +7 -0
- package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +2 -0
- package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +12 -0
- package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +10 -0
- package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +39 -0
- package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +1 -0
- package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +11 -0
- package/types/src/extensions/Paragraph/FixedParagraph.d.ts +1 -0
- package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +25 -0
- package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +10 -0
- package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +43 -0
- package/types/src/extensions/SlashMenu/defaultCommands.d.ts +8 -0
- package/types/src/extensions/SlashMenu/index.d.ts +5 -0
- package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +10 -0
- package/types/src/extensions/UniqueID/UniqueID.d.ts +3 -0
- package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +1 -0
- package/types/src/index.d.ts +4 -0
- package/types/src/lib/atlaskit/browser.d.ts +12 -0
- package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +16 -0
- package/types/src/shared/components/toolbar/Toolbar.d.ts +4 -0
- package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +2 -0
- package/types/src/shared/components/tooltip/TooltipContent.d.ts +15 -0
- package/types/src/shared/hooks/useEditorForceUpdate.d.ts +2 -0
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +29 -0
- package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +71 -0
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +74 -0
- package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +23 -0
- package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +26 -0
- package/types/src/useEditor.d.ts +8 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Editor,
|
|
3
|
+
isNodeSelection,
|
|
4
|
+
isTextSelection,
|
|
5
|
+
posToDOMRect,
|
|
6
|
+
} from "@tiptap/core";
|
|
7
|
+
import { EditorState, Plugin, PluginKey } from "prosemirror-state";
|
|
8
|
+
import { EditorView } from "prosemirror-view";
|
|
9
|
+
import tippy, { Instance, Props } from "tippy.js";
|
|
10
|
+
|
|
11
|
+
// Same as TipTap bubblemenu plugin, but with these changes:
|
|
12
|
+
// https://github.com/ueberdosis/tiptap/pull/2596/files
|
|
13
|
+
export interface BubbleMenuPluginProps {
|
|
14
|
+
pluginKey: PluginKey | string;
|
|
15
|
+
editor: Editor;
|
|
16
|
+
element: HTMLElement;
|
|
17
|
+
tippyOptions?: Partial<Props>;
|
|
18
|
+
shouldShow?:
|
|
19
|
+
| ((props: {
|
|
20
|
+
editor: Editor;
|
|
21
|
+
view: EditorView;
|
|
22
|
+
state: EditorState;
|
|
23
|
+
oldState?: EditorState;
|
|
24
|
+
from: number;
|
|
25
|
+
to: number;
|
|
26
|
+
}) => boolean)
|
|
27
|
+
| null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type BubbleMenuViewProps = BubbleMenuPluginProps & {
|
|
31
|
+
view: EditorView;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class BubbleMenuView {
|
|
35
|
+
public editor: Editor;
|
|
36
|
+
|
|
37
|
+
public element: HTMLElement;
|
|
38
|
+
|
|
39
|
+
public view: EditorView;
|
|
40
|
+
|
|
41
|
+
public preventHide = false;
|
|
42
|
+
|
|
43
|
+
public preventShow = false;
|
|
44
|
+
|
|
45
|
+
public tippy: Instance | undefined;
|
|
46
|
+
|
|
47
|
+
public tippyOptions?: Partial<Props>;
|
|
48
|
+
|
|
49
|
+
public shouldShow: Exclude<BubbleMenuPluginProps["shouldShow"], null> = ({
|
|
50
|
+
view,
|
|
51
|
+
state,
|
|
52
|
+
from,
|
|
53
|
+
to,
|
|
54
|
+
}) => {
|
|
55
|
+
const { doc, selection } = state;
|
|
56
|
+
const { empty } = selection;
|
|
57
|
+
|
|
58
|
+
// Sometime check for `empty` is not enough.
|
|
59
|
+
// Doubleclick an empty paragraph returns a node size of 2.
|
|
60
|
+
// So we check also for an empty text size.
|
|
61
|
+
const isEmptyTextBlock =
|
|
62
|
+
!doc.textBetween(from, to).length && isTextSelection(state.selection);
|
|
63
|
+
|
|
64
|
+
if (!view.hasFocus() || empty || isEmptyTextBlock) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
constructor({
|
|
72
|
+
editor,
|
|
73
|
+
element,
|
|
74
|
+
view,
|
|
75
|
+
tippyOptions = {},
|
|
76
|
+
shouldShow,
|
|
77
|
+
}: BubbleMenuViewProps) {
|
|
78
|
+
this.editor = editor;
|
|
79
|
+
this.element = element;
|
|
80
|
+
this.view = view;
|
|
81
|
+
|
|
82
|
+
if (shouldShow) {
|
|
83
|
+
this.shouldShow = shouldShow;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.element.addEventListener("mousedown", this.mousedownHandler, {
|
|
87
|
+
capture: true,
|
|
88
|
+
});
|
|
89
|
+
this.view.dom.addEventListener("mousedown", this.viewMousedownHandler);
|
|
90
|
+
this.view.dom.addEventListener("mouseup", this.viewMouseupHandler);
|
|
91
|
+
this.view.dom.addEventListener("dragstart", this.dragstartHandler);
|
|
92
|
+
|
|
93
|
+
this.editor.on("focus", this.focusHandler);
|
|
94
|
+
this.editor.on("blur", this.blurHandler);
|
|
95
|
+
this.tippyOptions = tippyOptions;
|
|
96
|
+
// Detaches menu content from its current parent
|
|
97
|
+
this.element.remove();
|
|
98
|
+
this.element.style.visibility = "visible";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
mousedownHandler = () => {
|
|
102
|
+
this.preventHide = true;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
viewMousedownHandler = () => {
|
|
106
|
+
this.preventShow = true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
viewMouseupHandler = () => {
|
|
110
|
+
this.preventShow = false;
|
|
111
|
+
setTimeout(() => this.update(this.editor.view));
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
dragstartHandler = () => {
|
|
115
|
+
this.hide();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
focusHandler = () => {
|
|
119
|
+
// we use `setTimeout` to make sure `selection` is already updated
|
|
120
|
+
setTimeout(() => this.update(this.editor.view));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
blurHandler = ({ event }: { event: FocusEvent }) => {
|
|
124
|
+
if (this.preventHide) {
|
|
125
|
+
this.preventHide = false;
|
|
126
|
+
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
event?.relatedTarget &&
|
|
132
|
+
this.element.parentNode?.contains(event.relatedTarget as Node)
|
|
133
|
+
) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.hide();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
createTooltip() {
|
|
141
|
+
const { element: editorElement } = this.editor.options;
|
|
142
|
+
const editorIsAttached = !!editorElement.parentElement;
|
|
143
|
+
|
|
144
|
+
if (this.tippy || !editorIsAttached) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.tippy = tippy(editorElement, {
|
|
149
|
+
duration: 0,
|
|
150
|
+
getReferenceClientRect: null,
|
|
151
|
+
content: this.element,
|
|
152
|
+
interactive: true,
|
|
153
|
+
trigger: "manual",
|
|
154
|
+
placement: "top",
|
|
155
|
+
hideOnClick: "toggle",
|
|
156
|
+
...this.tippyOptions,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// maybe we have to hide tippy on its own blur event as well
|
|
160
|
+
if (this.tippy.popper.firstChild) {
|
|
161
|
+
(this.tippy.popper.firstChild as HTMLElement).addEventListener(
|
|
162
|
+
"blur",
|
|
163
|
+
(event) => {
|
|
164
|
+
this.blurHandler({ event });
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
update(view: EditorView, oldState?: EditorState) {
|
|
171
|
+
const { state, composing } = view;
|
|
172
|
+
const { doc, selection } = state;
|
|
173
|
+
const isSame =
|
|
174
|
+
oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
|
|
175
|
+
|
|
176
|
+
if (composing || isSame) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.createTooltip();
|
|
181
|
+
|
|
182
|
+
// support for CellSelections
|
|
183
|
+
const { ranges } = selection;
|
|
184
|
+
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
|
185
|
+
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
|
186
|
+
|
|
187
|
+
const shouldShow = this.shouldShow?.({
|
|
188
|
+
editor: this.editor,
|
|
189
|
+
view,
|
|
190
|
+
state,
|
|
191
|
+
oldState,
|
|
192
|
+
from,
|
|
193
|
+
to,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!shouldShow || this.preventShow) {
|
|
197
|
+
this.hide();
|
|
198
|
+
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.tippy?.setProps({
|
|
203
|
+
getReferenceClientRect: () => {
|
|
204
|
+
if (isNodeSelection(state.selection)) {
|
|
205
|
+
const node = view.nodeDOM(from) as HTMLElement;
|
|
206
|
+
|
|
207
|
+
if (node) {
|
|
208
|
+
return node.getBoundingClientRect();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return posToDOMRect(view, from, to);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
this.show();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
show() {
|
|
220
|
+
this.tippy?.show();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
hide() {
|
|
224
|
+
this.tippy?.hide();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
destroy() {
|
|
228
|
+
this.tippy?.destroy();
|
|
229
|
+
this.element.removeEventListener("mousedown", this.mousedownHandler, {
|
|
230
|
+
capture: true,
|
|
231
|
+
});
|
|
232
|
+
this.view.dom.removeEventListener("mousedown", this.viewMousedownHandler);
|
|
233
|
+
this.view.dom.removeEventListener("mouseup", this.viewMouseupHandler);
|
|
234
|
+
this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
|
|
235
|
+
this.editor.off("focus", this.focusHandler);
|
|
236
|
+
this.editor.off("blur", this.blurHandler);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const createBubbleMenuPlugin = (options: BubbleMenuPluginProps) => {
|
|
241
|
+
return new Plugin({
|
|
242
|
+
key: new PluginKey("BubbleMenuPlugin"),
|
|
243
|
+
view: (view) => new BubbleMenuView({ view, ...options }),
|
|
244
|
+
});
|
|
245
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Editor } from "@tiptap/core";
|
|
2
|
+
import {
|
|
3
|
+
RiBold,
|
|
4
|
+
RiH1,
|
|
5
|
+
RiH2,
|
|
6
|
+
RiH3,
|
|
7
|
+
RiItalic,
|
|
8
|
+
RiLink,
|
|
9
|
+
RiStrikethrough,
|
|
10
|
+
RiUnderline,
|
|
11
|
+
RiIndentIncrease,
|
|
12
|
+
RiIndentDecrease,
|
|
13
|
+
RiText,
|
|
14
|
+
RiListOrdered,
|
|
15
|
+
RiListUnordered,
|
|
16
|
+
} from "react-icons/ri";
|
|
17
|
+
import { SimpleToolbarButton } from "../../../shared/components/toolbar/SimpleToolbarButton";
|
|
18
|
+
import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
|
|
19
|
+
import { useEditorForceUpdate } from "../../../shared/hooks/useEditorForceUpdate";
|
|
20
|
+
import { findBlock } from "../../Blocks/helpers/findBlock";
|
|
21
|
+
import formatKeyboardShortcut from "../../helpers/formatKeyboardShortcut";
|
|
22
|
+
import LinkToolbarButton from "./LinkToolbarButton";
|
|
23
|
+
import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
|
|
24
|
+
import DropdownBlockItem from "./DropdownBlockItem";
|
|
25
|
+
|
|
26
|
+
type ListType = "li" | "oli";
|
|
27
|
+
|
|
28
|
+
function getBlockName(
|
|
29
|
+
currentBlockHeading: number | undefined,
|
|
30
|
+
currentBlockListType: ListType | undefined
|
|
31
|
+
) {
|
|
32
|
+
const headings = ["Heading 1", "Heading 2", "Heading 3"];
|
|
33
|
+
const lists = {
|
|
34
|
+
li: "Bullet List",
|
|
35
|
+
oli: "Numbered List",
|
|
36
|
+
};
|
|
37
|
+
// A heading that's also a list, should show as Heading
|
|
38
|
+
if (currentBlockHeading) {
|
|
39
|
+
return headings[currentBlockHeading - 1];
|
|
40
|
+
} else if (currentBlockListType) {
|
|
41
|
+
return lists[currentBlockListType];
|
|
42
|
+
} else {
|
|
43
|
+
return "Text";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// TODO: add list options, indentation
|
|
48
|
+
export const BubbleMenu = (props: { editor: Editor }) => {
|
|
49
|
+
useEditorForceUpdate(props.editor);
|
|
50
|
+
|
|
51
|
+
const currentBlock = findBlock(props.editor.state.selection);
|
|
52
|
+
const currentBlockHeading: number | undefined =
|
|
53
|
+
currentBlock?.node.attrs.headingType;
|
|
54
|
+
const currentBlockListType: ListType | undefined =
|
|
55
|
+
currentBlock?.node.attrs.listType;
|
|
56
|
+
|
|
57
|
+
const currentBlockName = getBlockName(
|
|
58
|
+
currentBlockHeading,
|
|
59
|
+
currentBlockListType
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Toolbar>
|
|
64
|
+
<DropdownMenu trigger={currentBlockName}>
|
|
65
|
+
<DropdownItemGroup>
|
|
66
|
+
<DropdownBlockItem
|
|
67
|
+
title="Text"
|
|
68
|
+
icon={RiText}
|
|
69
|
+
isSelected={currentBlockName === "Paragraph"}
|
|
70
|
+
onClick={() =>
|
|
71
|
+
props.editor.chain().focus().unsetBlockHeading().unsetList().run()
|
|
72
|
+
}
|
|
73
|
+
/>
|
|
74
|
+
<DropdownBlockItem
|
|
75
|
+
title="Heading 1"
|
|
76
|
+
icon={RiH1}
|
|
77
|
+
isSelected={currentBlockName === "Heading 1"}
|
|
78
|
+
onClick={() =>
|
|
79
|
+
props.editor
|
|
80
|
+
.chain()
|
|
81
|
+
.focus()
|
|
82
|
+
.unsetList()
|
|
83
|
+
.setBlockHeading({ level: 1 })
|
|
84
|
+
.run()
|
|
85
|
+
}
|
|
86
|
+
/>
|
|
87
|
+
<DropdownBlockItem
|
|
88
|
+
title="Heading 2"
|
|
89
|
+
icon={RiH2}
|
|
90
|
+
isSelected={currentBlockName === "Heading 2"}
|
|
91
|
+
onClick={() =>
|
|
92
|
+
props.editor
|
|
93
|
+
.chain()
|
|
94
|
+
.focus()
|
|
95
|
+
.unsetList()
|
|
96
|
+
.setBlockHeading({ level: 2 })
|
|
97
|
+
.run()
|
|
98
|
+
}
|
|
99
|
+
/>
|
|
100
|
+
<DropdownBlockItem
|
|
101
|
+
title="Heading 3"
|
|
102
|
+
icon={RiH3}
|
|
103
|
+
isSelected={currentBlockName === "Heading 3"}
|
|
104
|
+
onClick={() =>
|
|
105
|
+
props.editor
|
|
106
|
+
.chain()
|
|
107
|
+
.focus()
|
|
108
|
+
.unsetList()
|
|
109
|
+
.setBlockHeading({ level: 3 })
|
|
110
|
+
.run()
|
|
111
|
+
}
|
|
112
|
+
/>
|
|
113
|
+
<DropdownBlockItem
|
|
114
|
+
title="Bullet List"
|
|
115
|
+
icon={RiListUnordered}
|
|
116
|
+
isSelected={currentBlockName === "Bullet List"}
|
|
117
|
+
onClick={() =>
|
|
118
|
+
props.editor
|
|
119
|
+
.chain()
|
|
120
|
+
.focus()
|
|
121
|
+
.unsetBlockHeading()
|
|
122
|
+
.setBlockList("li")
|
|
123
|
+
.run()
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
<DropdownBlockItem
|
|
127
|
+
title="Numbered List"
|
|
128
|
+
icon={RiListOrdered}
|
|
129
|
+
isSelected={currentBlockName === "Numbered List"}
|
|
130
|
+
onClick={() =>
|
|
131
|
+
props.editor
|
|
132
|
+
.chain()
|
|
133
|
+
.focus()
|
|
134
|
+
.unsetBlockHeading()
|
|
135
|
+
.setBlockList("oli")
|
|
136
|
+
.run()
|
|
137
|
+
}
|
|
138
|
+
/>
|
|
139
|
+
</DropdownItemGroup>
|
|
140
|
+
</DropdownMenu>
|
|
141
|
+
<SimpleToolbarButton
|
|
142
|
+
onClick={() => props.editor.chain().focus().toggleBold().run()}
|
|
143
|
+
isSelected={props.editor.isActive("bold")}
|
|
144
|
+
mainTooltip="Bold"
|
|
145
|
+
secondaryTooltip={formatKeyboardShortcut("Mod+B")}
|
|
146
|
+
icon={RiBold}
|
|
147
|
+
/>
|
|
148
|
+
<SimpleToolbarButton
|
|
149
|
+
onClick={() => props.editor.chain().focus().toggleItalic().run()}
|
|
150
|
+
isSelected={props.editor.isActive("italic")}
|
|
151
|
+
mainTooltip="Italic"
|
|
152
|
+
secondaryTooltip={formatKeyboardShortcut("Mod+I")}
|
|
153
|
+
icon={RiItalic}
|
|
154
|
+
/>
|
|
155
|
+
<SimpleToolbarButton
|
|
156
|
+
onClick={() => props.editor.chain().focus().toggleUnderline().run()}
|
|
157
|
+
isSelected={props.editor.isActive("underline")}
|
|
158
|
+
mainTooltip="Underline"
|
|
159
|
+
secondaryTooltip={formatKeyboardShortcut("Mod+U")}
|
|
160
|
+
icon={RiUnderline}
|
|
161
|
+
/>
|
|
162
|
+
<SimpleToolbarButton
|
|
163
|
+
onClick={() => props.editor.chain().focus().toggleStrike().run()}
|
|
164
|
+
isDisabled={props.editor.isActive("strike")}
|
|
165
|
+
mainTooltip="Strike-through"
|
|
166
|
+
secondaryTooltip={formatKeyboardShortcut("Mod+Shift+X")}
|
|
167
|
+
icon={RiStrikethrough}
|
|
168
|
+
/>
|
|
169
|
+
<SimpleToolbarButton
|
|
170
|
+
onClick={() =>
|
|
171
|
+
props.editor.chain().focus().sinkListItem("tcblock").run()
|
|
172
|
+
}
|
|
173
|
+
isDisabled={!props.editor.can().sinkListItem("tcblock")}
|
|
174
|
+
mainTooltip="Indent"
|
|
175
|
+
secondaryTooltip={formatKeyboardShortcut("Tab")}
|
|
176
|
+
icon={RiIndentIncrease}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
<SimpleToolbarButton
|
|
180
|
+
onClick={() =>
|
|
181
|
+
props.editor.chain().focus().liftListItem("tcblock").run()
|
|
182
|
+
}
|
|
183
|
+
isDisabled={
|
|
184
|
+
!props.editor.can().command(({ state }) => {
|
|
185
|
+
const block = findBlock(state.selection);
|
|
186
|
+
if (!block) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
// If the depth is greater than 2 you can lift
|
|
190
|
+
return block.depth > 2;
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
mainTooltip="Decrease Indent"
|
|
194
|
+
secondaryTooltip={formatKeyboardShortcut("Shift+Tab")}
|
|
195
|
+
icon={RiIndentDecrease}
|
|
196
|
+
/>
|
|
197
|
+
|
|
198
|
+
<LinkToolbarButton
|
|
199
|
+
// editor={props.editor}
|
|
200
|
+
isSelected={props.editor.isActive("link")}
|
|
201
|
+
mainTooltip="Link"
|
|
202
|
+
secondaryTooltip={formatKeyboardShortcut("Mod+K")}
|
|
203
|
+
icon={RiLink}
|
|
204
|
+
editor={props.editor}
|
|
205
|
+
/>
|
|
206
|
+
{/* <SimpleBubbleMenuButton
|
|
207
|
+
editor={props.editor}
|
|
208
|
+
onClick={() => {
|
|
209
|
+
const comment = this.props.commentStore.createComment();
|
|
210
|
+
props.editor.chain().focus().setComment(comment.id).run();
|
|
211
|
+
}}
|
|
212
|
+
styleDetails={comment}
|
|
213
|
+
/> */}
|
|
214
|
+
</Toolbar>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DropdownItem } from "@atlaskit/dropdown-menu";
|
|
2
|
+
import { IconType } from "react-icons/lib";
|
|
3
|
+
import { TiTick } from "react-icons/ti";
|
|
4
|
+
import styles from "./DropdownBlockItem.module.css";
|
|
5
|
+
|
|
6
|
+
interface DropdownBlockItemProps {
|
|
7
|
+
title: string;
|
|
8
|
+
icon: IconType;
|
|
9
|
+
isSelected?: boolean;
|
|
10
|
+
onClick?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export default function DropdownBlockItem(props: DropdownBlockItemProps) {
|
|
13
|
+
const ItemIcon = props.icon;
|
|
14
|
+
return (
|
|
15
|
+
<DropdownItem onClick={props.onClick}>
|
|
16
|
+
<div className={`${styles.item_container}`}>
|
|
17
|
+
<div className={`${styles.logo_and_item_container}`}>
|
|
18
|
+
<ItemIcon />
|
|
19
|
+
{props.title}
|
|
20
|
+
</div>
|
|
21
|
+
{props.isSelected && <TiTick />}
|
|
22
|
+
</div>
|
|
23
|
+
</DropdownItem>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Tippy from "@tippyjs/react";
|
|
2
|
+
import { Editor } from "@tiptap/core";
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
SimpleToolbarButton,
|
|
6
|
+
SimpleToolbarButtonProps,
|
|
7
|
+
} from "../../../shared/components/toolbar/SimpleToolbarButton";
|
|
8
|
+
import { HyperlinkEditMenu } from "../../Hyperlinks/menus/HyperlinkEditMenu";
|
|
9
|
+
|
|
10
|
+
type Props = SimpleToolbarButtonProps & {
|
|
11
|
+
editor: Editor;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The link menu button opens a tooltip on click
|
|
16
|
+
*/
|
|
17
|
+
export const LinkToolbarButton = (props: Props) => {
|
|
18
|
+
const [creationMenu, setCreationMenu] = useState<any>();
|
|
19
|
+
|
|
20
|
+
// TODO: review code; does this pattern still make sense?
|
|
21
|
+
const updateCreationMenu = useCallback(() => {
|
|
22
|
+
const onSubmit = (url: string, text: string) => {
|
|
23
|
+
if (url === "") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const mark = props.editor.schema.mark("link", { href: url });
|
|
27
|
+
let { from, to } = props.editor.state.selection;
|
|
28
|
+
props.editor.view.dispatch(
|
|
29
|
+
props.editor.view.state.tr
|
|
30
|
+
.insertText(text, from, to)
|
|
31
|
+
.addMark(from, from + text.length, mark)
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// get the currently selected text and url from the document, and use it to
|
|
36
|
+
// create a new creation menu
|
|
37
|
+
const { from, to } = props.editor.state.selection;
|
|
38
|
+
const selectedText = props.editor.state.doc.textBetween(from, to);
|
|
39
|
+
const activeUrl = props.editor.isActive("link")
|
|
40
|
+
? props.editor.getAttributes("link").href || ""
|
|
41
|
+
: "";
|
|
42
|
+
|
|
43
|
+
setCreationMenu(
|
|
44
|
+
<HyperlinkEditMenu
|
|
45
|
+
key={Math.random() + ""} // Math.random to prevent old element from being re-used
|
|
46
|
+
url={activeUrl}
|
|
47
|
+
text={selectedText}
|
|
48
|
+
onSubmit={onSubmit}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}, [props.editor]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Tippy
|
|
55
|
+
content={creationMenu}
|
|
56
|
+
trigger={"click"}
|
|
57
|
+
onShow={(_) => {
|
|
58
|
+
updateCreationMenu();
|
|
59
|
+
}}
|
|
60
|
+
interactive={true}
|
|
61
|
+
maxWidth={500}>
|
|
62
|
+
<SimpleToolbarButton {...props} />
|
|
63
|
+
</Tippy>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default LinkToolbarButton;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { createDraggableBlocksPlugin } from "./DraggableBlocksPlugin";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This extension adds a drag handle in front of all nodes with a "data-id" attribute
|
|
6
|
+
*
|
|
7
|
+
* code based on https://github.com/ueberdosis/tiptap/issues/323#issuecomment-506637799
|
|
8
|
+
*/
|
|
9
|
+
export const DraggableBlocksExtension = Extension.create<{}>({
|
|
10
|
+
name: "DraggableBlocksExtension",
|
|
11
|
+
priority: 1000, // Need to be high, in order to hide draghandle when typing slash
|
|
12
|
+
addProseMirrorPlugins() {
|
|
13
|
+
return [createDraggableBlocksPlugin()];
|
|
14
|
+
},
|
|
15
|
+
});
|