@blocknote/core 0.1.1 → 0.2.0

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.
Files changed (171) hide show
  1. package/README.md +12 -6
  2. package/dist/blocknote.js +1425 -5114
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +1 -53
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +3 -16
  8. package/src/BlockNoteEditor.ts +54 -0
  9. package/src/BlockNoteExtensions.ts +52 -7
  10. package/src/assets/fonts-inter.css +92 -0
  11. package/src/editor.module.css +37 -0
  12. package/src/extensions/Blocks/BlockAttributes.ts +1 -3
  13. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +71 -18
  14. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +66 -0
  15. package/src/extensions/Blocks/index.ts +7 -3
  16. package/src/extensions/Blocks/nodes/Block.module.css +116 -74
  17. package/src/extensions/Blocks/nodes/Block.ts +415 -292
  18. package/src/extensions/Blocks/nodes/BlockGroup.ts +6 -6
  19. package/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.ts +84 -0
  20. package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.ts +177 -0
  21. package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.ts +77 -0
  22. package/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.ts +34 -0
  23. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +20 -0
  24. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +27 -9
  25. package/src/extensions/DraggableBlocks/{DraggableBlocksPlugin.tsx → DraggableBlocksPlugin.ts} +227 -147
  26. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +29 -0
  27. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +35 -0
  28. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +308 -0
  29. package/src/extensions/HyperlinkToolbar/HyperlinkMark.ts +28 -0
  30. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +19 -0
  31. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +251 -0
  32. package/src/extensions/Placeholder/PlaceholderExtension.ts +2 -2
  33. package/src/extensions/SlashMenu/SlashMenuExtension.ts +9 -1
  34. package/src/extensions/SlashMenu/SlashMenuItem.ts +1 -3
  35. package/src/extensions/SlashMenu/defaultCommands.tsx +33 -22
  36. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +4 -4
  37. package/src/extensions/UniqueID/UniqueID.ts +14 -12
  38. package/src/index.ts +8 -4
  39. package/src/shared/EditorElement.ts +10 -0
  40. package/src/shared/plugins/suggestion/SuggestionItem.ts +1 -8
  41. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +222 -101
  42. package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +21 -0
  43. package/src/{utils.ts → shared/utils.ts} +0 -0
  44. package/types/src/BlockNoteEditor.d.ts +13 -0
  45. package/types/src/BlockNoteExtensions.d.ts +12 -1
  46. package/types/src/EditorElement.d.ts +7 -0
  47. package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +1 -1
  48. package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +19 -0
  49. package/types/src/extensions/Blocks/nodes/Block.d.ts +11 -19
  50. package/types/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.d.ts +8 -0
  51. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.d.ts +8 -0
  52. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.d.ts +2 -0
  53. package/types/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.d.ts +6 -0
  54. package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +4 -1
  55. package/types/src/extensions/BubbleMenu/BubbleMenuFactoryTypes.d.ts +27 -0
  56. package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +10 -12
  57. package/types/src/extensions/DraggableBlocks/BlockMenuFactoryTypes.d.ts +12 -0
  58. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +14 -0
  59. package/types/src/extensions/DraggableBlocks/DragMenuFactoryTypes.d.ts +18 -0
  60. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +9 -3
  61. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +23 -1
  62. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +8 -0
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +23 -0
  64. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +43 -0
  65. package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +8 -0
  66. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +12 -0
  67. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +11 -0
  68. package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +2 -1
  69. package/types/src/extensions/Hyperlinks/HyperlinkMenuFactoryTypes.d.ts +11 -0
  70. package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +10 -1
  71. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +3 -1
  72. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +2 -4
  73. package/types/src/index.d.ts +8 -3
  74. package/types/src/shared/EditorElement.d.ts +6 -0
  75. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +1 -6
  76. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +15 -10
  77. package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +12 -0
  78. package/types/src/shared/utils.d.ts +2 -0
  79. package/types/src/utils.d.ts +2 -2
  80. package/src/BlockNoteTheme.ts +0 -150
  81. package/src/EditorContent.tsx +0 -2
  82. package/src/extensions/Blocks/OrderedListPlugin.ts +0 -46
  83. package/src/extensions/Blocks/commands/joinBackward.ts +0 -274
  84. package/src/extensions/Blocks/helpers/setBlockHeading.ts +0 -30
  85. package/src/extensions/Blocks/nodes/Content.ts +0 -63
  86. package/src/extensions/Blocks/rule.ts +0 -48
  87. package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +0 -36
  88. package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +0 -245
  89. package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +0 -240
  90. package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +0 -67
  91. package/src/extensions/DraggableBlocks/components/DragHandle.tsx +0 -102
  92. package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +0 -19
  93. package/src/extensions/Hyperlinks/HyperlinkMark.tsx +0 -16
  94. package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +0 -165
  95. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +0 -44
  96. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +0 -34
  97. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +0 -31
  98. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +0 -40
  99. package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +0 -37
  100. package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +0 -63
  101. package/src/fonts-inter.css +0 -94
  102. package/src/globals.css +0 -28
  103. package/src/root.module.css +0 -19
  104. package/src/shared/components/toolbar/Toolbar.tsx +0 -10
  105. package/src/shared/components/toolbar/ToolbarButton.tsx +0 -57
  106. package/src/shared/components/toolbar/ToolbarDropdown.tsx +0 -35
  107. package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +0 -35
  108. package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +0 -31
  109. package/src/shared/components/tooltip/TooltipContent.module.css +0 -15
  110. package/src/shared/components/tooltip/TooltipContent.tsx +0 -23
  111. package/src/shared/hooks/useEditorForceUpdate.tsx +0 -30
  112. package/src/shared/plugins/suggestion/SuggestionListReactRenderer.tsx +0 -236
  113. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +0 -47
  114. package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +0 -82
  115. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +0 -92
  116. package/src/useEditor.ts +0 -51
  117. package/types/src/BlockNoteTheme.d.ts +0 -2
  118. package/types/src/EditorContent.d.ts +0 -1
  119. package/types/src/commands/indentation.d.ts +0 -2
  120. package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +0 -2
  121. package/types/src/extensions/Blocks/commands/joinBackward.d.ts +0 -14
  122. package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +0 -5
  123. package/types/src/extensions/Blocks/nodes/Content.d.ts +0 -5
  124. package/types/src/extensions/Blocks/rule.d.ts +0 -16
  125. package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +0 -5
  126. package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +0 -10
  127. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +0 -11
  128. package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +0 -12
  129. package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +0 -6
  130. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +0 -11
  131. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +0 -13
  132. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +0 -8
  133. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +0 -9
  134. package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +0 -12
  135. package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +0 -12
  136. package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +0 -10
  137. package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +0 -21
  138. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +0 -39
  139. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +0 -1
  140. package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +0 -11
  141. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +0 -39
  142. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +0 -3
  143. package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +0 -13
  144. package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +0 -1
  145. package/types/src/lib/atlaskit/browser.d.ts +0 -12
  146. package/types/src/nodes/ChildgroupNode.d.ts +0 -28
  147. package/types/src/nodes/patchNodes.d.ts +0 -1
  148. package/types/src/plugins/TreeViewPlugin/index.d.ts +0 -2
  149. package/types/src/plugins/animation.d.ts +0 -2
  150. package/types/src/react/BlockNoteComposer.d.ts +0 -17
  151. package/types/src/react/BlockNotePlugin.d.ts +0 -1
  152. package/types/src/react/index.d.ts +0 -3
  153. package/types/src/react/useBlockNoteSetup.d.ts +0 -2
  154. package/types/src/registerBlockNote.d.ts +0 -2
  155. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +0 -15
  156. package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +0 -11
  157. package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +0 -11
  158. package/types/src/shared/components/toolbar/Toolbar.d.ts +0 -4
  159. package/types/src/shared/components/toolbar/ToolbarButton.d.ts +0 -15
  160. package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +0 -17
  161. package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +0 -11
  162. package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +0 -8
  163. package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +0 -2
  164. package/types/src/shared/components/tooltip/TooltipContent.d.ts +0 -15
  165. package/types/src/shared/hooks/useEditorForceUpdate.d.ts +0 -2
  166. package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +0 -71
  167. package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +0 -23
  168. package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +0 -9
  169. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -11
  170. package/types/src/themes/BlockNoteEditorTheme.d.ts +0 -11
  171. package/types/src/useEditor.d.ts +0 -11
@@ -0,0 +1,308 @@
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 {
10
+ FormattingToolbar,
11
+ FormattingToolbarDynamicParams,
12
+ FormattingToolbarFactory,
13
+ FormattingToolbarStaticParams,
14
+ } from "./FormattingToolbarFactoryTypes";
15
+ import { BlockContentType } from "../Blocks/nodes/Block";
16
+
17
+ // Same as TipTap bubblemenu plugin, but with these changes:
18
+ // https://github.com/ueberdosis/tiptap/pull/2596/files
19
+ export interface FormattingToolbarPluginProps {
20
+ pluginKey: PluginKey;
21
+ editor: Editor;
22
+ formattingToolbarFactory: FormattingToolbarFactory;
23
+ shouldShow?:
24
+ | ((props: {
25
+ editor: Editor;
26
+ view: EditorView;
27
+ state: EditorState;
28
+ oldState?: EditorState;
29
+ from: number;
30
+ to: number;
31
+ }) => boolean)
32
+ | null;
33
+ }
34
+
35
+ export type FormattingToolbarViewProps = FormattingToolbarPluginProps & {
36
+ view: EditorView;
37
+ };
38
+
39
+ export class FormattingToolbarView {
40
+ public editor: Editor;
41
+
42
+ public view: EditorView;
43
+
44
+ public formattingToolbar: FormattingToolbar;
45
+
46
+ public preventHide = false;
47
+
48
+ public preventShow = false;
49
+
50
+ public toolbarIsOpen = false;
51
+
52
+ public shouldShow: Exclude<FormattingToolbarPluginProps["shouldShow"], null> =
53
+ ({ view, state, from, to }) => {
54
+ const { doc, selection } = state;
55
+ const { empty } = selection;
56
+
57
+ // Sometime check for `empty` is not enough.
58
+ // Doubleclick an empty paragraph returns a node size of 2.
59
+ // So we check also for an empty text size.
60
+ const isEmptyTextBlock =
61
+ !doc.textBetween(from, to).length && isTextSelection(state.selection);
62
+
63
+ return !(!view.hasFocus() || empty || isEmptyTextBlock);
64
+ };
65
+
66
+ constructor({
67
+ editor,
68
+ formattingToolbarFactory,
69
+ view,
70
+ shouldShow,
71
+ }: FormattingToolbarViewProps) {
72
+ this.editor = editor;
73
+ this.view = view;
74
+
75
+ this.formattingToolbar = formattingToolbarFactory(this.getStaticParams());
76
+
77
+ if (shouldShow) {
78
+ this.shouldShow = shouldShow;
79
+ }
80
+
81
+ this.view.dom.addEventListener("mousedown", this.viewMousedownHandler);
82
+ this.view.dom.addEventListener("mouseup", this.viewMouseupHandler);
83
+ this.view.dom.addEventListener("dragstart", this.dragstartHandler);
84
+
85
+ this.editor.on("focus", this.focusHandler);
86
+ this.editor.on("blur", this.blurHandler);
87
+ }
88
+
89
+ viewMousedownHandler = () => {
90
+ this.preventShow = true;
91
+ };
92
+
93
+ viewMouseupHandler = () => {
94
+ this.preventShow = false;
95
+ setTimeout(() => this.update(this.editor.view));
96
+ };
97
+
98
+ dragstartHandler = () => {
99
+ this.formattingToolbar.hide();
100
+ this.toolbarIsOpen = false;
101
+ };
102
+
103
+ focusHandler = () => {
104
+ // we use `setTimeout` to make sure `selection` is already updated
105
+ setTimeout(() => this.update(this.editor.view));
106
+ };
107
+
108
+ blurHandler = ({ event }: { event: FocusEvent }) => {
109
+ if (this.preventHide) {
110
+ this.preventHide = false;
111
+
112
+ return;
113
+ }
114
+
115
+ if (
116
+ event?.relatedTarget &&
117
+ this.formattingToolbar.element?.parentNode?.contains(
118
+ event.relatedTarget as Node
119
+ )
120
+ ) {
121
+ return;
122
+ }
123
+
124
+ if (this.toolbarIsOpen) {
125
+ this.formattingToolbar.hide();
126
+ this.toolbarIsOpen = false;
127
+ }
128
+ };
129
+
130
+ update(view: EditorView, oldState?: EditorState) {
131
+ const { state, composing } = view;
132
+ const { doc, selection } = state;
133
+ const isSame =
134
+ oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
135
+
136
+ if (composing || isSame) {
137
+ return;
138
+ }
139
+
140
+ // support for CellSelections
141
+ const { ranges } = selection;
142
+ const from = Math.min(...ranges.map((range) => range.$from.pos));
143
+ const to = Math.max(...ranges.map((range) => range.$to.pos));
144
+
145
+ const shouldShow = this.shouldShow?.({
146
+ editor: this.editor,
147
+ view,
148
+ state,
149
+ oldState,
150
+ from,
151
+ to,
152
+ });
153
+
154
+ // Checks if menu should be shown.
155
+ if (
156
+ !this.toolbarIsOpen &&
157
+ !this.preventShow &&
158
+ (shouldShow || this.preventHide)
159
+ ) {
160
+ this.formattingToolbar.render(this.getDynamicParams(), true);
161
+ this.toolbarIsOpen = true;
162
+
163
+ // TODO: Is this necessary? Also for other menu plugins.
164
+ // Listener stops focus moving to the menu on click.
165
+ this.formattingToolbar.element!.addEventListener("mousedown", (event) =>
166
+ event.preventDefault()
167
+ );
168
+
169
+ return;
170
+ }
171
+
172
+ // Checks if menu should be updated.
173
+ if (
174
+ this.toolbarIsOpen &&
175
+ !this.preventShow &&
176
+ (shouldShow || this.preventHide)
177
+ ) {
178
+ this.formattingToolbar.render(this.getDynamicParams(), false);
179
+ return;
180
+ }
181
+
182
+ // Checks if menu should be hidden.
183
+ if (
184
+ this.toolbarIsOpen &&
185
+ !this.preventHide &&
186
+ (!shouldShow || this.preventShow)
187
+ ) {
188
+ this.formattingToolbar.hide();
189
+ this.toolbarIsOpen = false;
190
+
191
+ // Listener stops focus moving to the menu on click.
192
+ this.formattingToolbar.element!.removeEventListener(
193
+ "mousedown",
194
+ (event) => event.preventDefault()
195
+ );
196
+
197
+ return;
198
+ }
199
+ }
200
+
201
+ destroy() {
202
+ this.view.dom.removeEventListener("mousedown", this.viewMousedownHandler);
203
+ this.view.dom.removeEventListener("mouseup", this.viewMouseupHandler);
204
+ this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
205
+
206
+ this.editor.off("focus", this.focusHandler);
207
+ this.editor.off("blur", this.blurHandler);
208
+ }
209
+
210
+ getSelectionBoundingBox() {
211
+ const { state } = this.editor.view;
212
+ const { selection } = state;
213
+
214
+ // support for CellSelections
215
+ const { ranges } = selection;
216
+ const from = Math.min(...ranges.map((range) => range.$from.pos));
217
+ const to = Math.max(...ranges.map((range) => range.$to.pos));
218
+
219
+ if (isNodeSelection(selection)) {
220
+ const node = this.editor.view.nodeDOM(from) as HTMLElement;
221
+
222
+ if (node) {
223
+ return node.getBoundingClientRect();
224
+ }
225
+ }
226
+
227
+ return posToDOMRect(this.editor.view, from, to);
228
+ }
229
+
230
+ getStaticParams(): FormattingToolbarStaticParams {
231
+ return {
232
+ toggleBold: () => {
233
+ this.editor.view.focus();
234
+ this.editor.commands.toggleBold();
235
+ },
236
+ toggleItalic: () => {
237
+ this.editor.view.focus();
238
+ this.editor.commands.toggleItalic();
239
+ },
240
+ toggleUnderline: () => {
241
+ this.editor.view.focus();
242
+ this.editor.commands.toggleUnderline();
243
+ },
244
+ toggleStrike: () => {
245
+ this.editor.view.focus();
246
+ this.editor.commands.toggleStrike();
247
+ },
248
+ setHyperlink: (url: string, text?: string) => {
249
+ if (url === "") {
250
+ return;
251
+ }
252
+
253
+ let { from, to } = this.editor.state.selection;
254
+
255
+ if (!text) {
256
+ text = this.editor.state.doc.textBetween(from, to);
257
+ }
258
+
259
+ const mark = this.editor.schema.mark("link", { href: url });
260
+
261
+ this.editor.view.dispatch(
262
+ this.editor.view.state.tr
263
+ .insertText(text, from, to)
264
+ .addMark(from, from + text.length, mark)
265
+ );
266
+ this.editor.view.focus();
267
+ },
268
+ setBlockType: (type: BlockContentType) => {
269
+ this.editor.view.focus();
270
+ this.editor.commands.BNSetContentType(
271
+ this.editor.state.selection.from,
272
+ type
273
+ );
274
+ },
275
+ };
276
+ }
277
+
278
+ getDynamicParams(): FormattingToolbarDynamicParams {
279
+ return {
280
+ boldIsActive: this.editor.isActive("bold"),
281
+ italicIsActive: this.editor.isActive("italic"),
282
+ underlineIsActive: this.editor.isActive("underline"),
283
+ strikeIsActive: this.editor.isActive("strike"),
284
+ hyperlinkIsActive: this.editor.isActive("link"),
285
+ activeHyperlinkUrl: this.editor.getAttributes("link").href
286
+ ? this.editor.getAttributes("link").href
287
+ : "",
288
+ activeHyperlinkText: this.editor.state.doc.textBetween(
289
+ this.editor.state.selection.from,
290
+ this.editor.state.selection.to
291
+ ),
292
+ activeBlockType: {
293
+ name: this.editor.state.selection.$from.node().type.name,
294
+ attrs: this.editor.state.selection.$from.node().attrs,
295
+ } as Required<BlockContentType>,
296
+ selectionBoundingBox: this.getSelectionBoundingBox(),
297
+ };
298
+ }
299
+ }
300
+
301
+ export const createFormattingToolbarPlugin = (
302
+ options: FormattingToolbarPluginProps
303
+ ) => {
304
+ return new Plugin({
305
+ key: new PluginKey("FormattingToolbarPlugin"),
306
+ view: (view) => new FormattingToolbarView({ view, ...options }),
307
+ });
308
+ };
@@ -0,0 +1,28 @@
1
+ import { Link } from "@tiptap/extension-link";
2
+ import {
3
+ createHyperlinkToolbarPlugin,
4
+ HyperlinkToolbarPluginProps,
5
+ } from "./HyperlinkToolbarPlugin";
6
+
7
+ /**
8
+ * This custom link includes a special menu for editing/deleting/opening the link.
9
+ * The menu will be triggered by hovering over the link with the mouse,
10
+ * or by moving the cursor inside the link text
11
+ */
12
+ const Hyperlink = Link.extend<HyperlinkToolbarPluginProps>({
13
+ priority: 500,
14
+ addProseMirrorPlugins() {
15
+ if (!this.options.hyperlinkToolbarFactory) {
16
+ throw new Error("UI Element factory not defined for HyperlinkMark");
17
+ }
18
+
19
+ return [
20
+ ...(this.parent?.() || []),
21
+ createHyperlinkToolbarPlugin(this.editor, {
22
+ hyperlinkToolbarFactory: this.options.hyperlinkToolbarFactory,
23
+ }),
24
+ ];
25
+ },
26
+ });
27
+
28
+ export default Hyperlink;
@@ -0,0 +1,19 @@
1
+ import { EditorElement, ElementFactory } from "../../shared/EditorElement";
2
+
3
+ export type HyperlinkToolbarStaticParams = {
4
+ editHyperlink: (url: string, text: string) => void;
5
+ deleteHyperlink: () => void;
6
+ };
7
+
8
+ export type HyperlinkToolbarDynamicParams = {
9
+ url: string;
10
+ text: string;
11
+
12
+ boundingBox: DOMRect;
13
+ };
14
+
15
+ export type HyperlinkToolbar = EditorElement<HyperlinkToolbarDynamicParams>;
16
+ export type HyperlinkToolbarFactory = ElementFactory<
17
+ HyperlinkToolbarStaticParams,
18
+ HyperlinkToolbarDynamicParams
19
+ >;
@@ -0,0 +1,251 @@
1
+ import { Editor, getMarkRange, posToDOMRect, Range } from "@tiptap/core";
2
+ import { Mark } from "prosemirror-model";
3
+ import { Plugin, PluginKey } from "prosemirror-state";
4
+ import {
5
+ HyperlinkToolbar,
6
+ HyperlinkToolbarDynamicParams,
7
+ HyperlinkToolbarFactory,
8
+ HyperlinkToolbarStaticParams,
9
+ } from "./HyperlinkToolbarFactoryTypes";
10
+ const PLUGIN_KEY = new PluginKey("HyperlinkToolbarPlugin");
11
+
12
+ export type HyperlinkToolbarPluginProps = {
13
+ hyperlinkToolbarFactory: HyperlinkToolbarFactory;
14
+ };
15
+
16
+ export type HyperlinkToolbarViewProps = {
17
+ editor: Editor;
18
+ hyperlinkToolbarFactory: HyperlinkToolbarFactory;
19
+ };
20
+
21
+ class HyperlinkToolbarView {
22
+ editor: Editor;
23
+
24
+ hyperlinkToolbar: HyperlinkToolbar;
25
+
26
+ menuUpdateTimer: NodeJS.Timeout | undefined;
27
+ startMenuUpdateTimer: () => void;
28
+ stopMenuUpdateTimer: () => void;
29
+
30
+ mouseHoveredHyperlinkMark: Mark | undefined;
31
+ mouseHoveredHyperlinkMarkRange: Range | undefined;
32
+
33
+ keyboardHoveredHyperlinkMark: Mark | undefined;
34
+ keyboardHoveredHyperlinkMarkRange: Range | undefined;
35
+
36
+ hyperlinkMark: Mark | undefined;
37
+ hyperlinkMarkRange: Range | undefined;
38
+
39
+ constructor({ editor, hyperlinkToolbarFactory }: HyperlinkToolbarViewProps) {
40
+ this.editor = editor;
41
+
42
+ this.hyperlinkToolbar = hyperlinkToolbarFactory(this.getStaticParams());
43
+
44
+ this.startMenuUpdateTimer = () => {
45
+ this.menuUpdateTimer = setTimeout(() => {
46
+ this.update();
47
+ }, 250);
48
+ };
49
+
50
+ this.stopMenuUpdateTimer = () => {
51
+ if (this.menuUpdateTimer) {
52
+ clearTimeout(this.menuUpdateTimer);
53
+ this.menuUpdateTimer = undefined;
54
+ }
55
+
56
+ return false;
57
+ };
58
+
59
+ editor.view.dom.addEventListener("mouseover", (event) => {
60
+ // Resets the hyperlink mark currently hovered by the mouse cursor.
61
+ this.mouseHoveredHyperlinkMark = undefined;
62
+ this.mouseHoveredHyperlinkMarkRange = undefined;
63
+
64
+ this.stopMenuUpdateTimer();
65
+
66
+ if (
67
+ event.target instanceof HTMLAnchorElement &&
68
+ event.target.nodeName === "A"
69
+ ) {
70
+ // Finds link mark at the hovered element's position to update mouseHoveredHyperlinkMark and
71
+ // mouseHoveredHyperlinkMarkRange.
72
+ const hoveredHyperlinkElement = event.target;
73
+ const posInHoveredHyperlinkMark =
74
+ editor.view.posAtDOM(hoveredHyperlinkElement, 0) + 1;
75
+ const resolvedPosInHoveredHyperlinkMark = editor.state.doc.resolve(
76
+ posInHoveredHyperlinkMark
77
+ );
78
+ const marksAtPos = resolvedPosInHoveredHyperlinkMark.marks();
79
+
80
+ for (const mark of marksAtPos) {
81
+ if (mark.type.name === editor.schema.mark("link").type.name) {
82
+ this.mouseHoveredHyperlinkMark = mark;
83
+ this.mouseHoveredHyperlinkMarkRange =
84
+ getMarkRange(
85
+ resolvedPosInHoveredHyperlinkMark,
86
+ mark.type,
87
+ mark.attrs
88
+ ) || undefined;
89
+
90
+ break;
91
+ }
92
+ }
93
+ }
94
+
95
+ this.startMenuUpdateTimer();
96
+
97
+ return false;
98
+ });
99
+ }
100
+
101
+ update() {
102
+ if (!this.editor.view.hasFocus()) {
103
+ return;
104
+ }
105
+
106
+ // Saves the currently hovered hyperlink mark before it's updated.
107
+ const prevHyperlinkMark = this.hyperlinkMark;
108
+
109
+ // Resets the currently hovered hyperlink mark.
110
+ this.hyperlinkMark = undefined;
111
+ this.hyperlinkMarkRange = undefined;
112
+
113
+ // Resets the hyperlink mark currently hovered by the keyboard cursor.
114
+ this.keyboardHoveredHyperlinkMark = undefined;
115
+ this.keyboardHoveredHyperlinkMarkRange = undefined;
116
+
117
+ // Finds link mark at the editor selection's position to update keyboardHoveredHyperlinkMark and
118
+ // keyboardHoveredHyperlinkMarkRange.
119
+ if (this.editor.state.selection.empty) {
120
+ const marksAtPos = this.editor.state.selection.$from.marks();
121
+
122
+ for (const mark of marksAtPos) {
123
+ if (mark.type.name === this.editor.schema.mark("link").type.name) {
124
+ this.keyboardHoveredHyperlinkMark = mark;
125
+ this.keyboardHoveredHyperlinkMarkRange =
126
+ getMarkRange(
127
+ this.editor.state.selection.$from,
128
+ mark.type,
129
+ mark.attrs
130
+ ) || undefined;
131
+
132
+ break;
133
+ }
134
+ }
135
+ }
136
+
137
+ if (this.mouseHoveredHyperlinkMark) {
138
+ this.hyperlinkMark = this.mouseHoveredHyperlinkMark;
139
+ this.hyperlinkMarkRange = this.mouseHoveredHyperlinkMarkRange;
140
+ }
141
+
142
+ // Keyboard cursor position takes precedence over mouse hovered hyperlink.
143
+ if (this.keyboardHoveredHyperlinkMark) {
144
+ this.hyperlinkMark = this.keyboardHoveredHyperlinkMark;
145
+ this.hyperlinkMarkRange = this.keyboardHoveredHyperlinkMarkRange;
146
+ }
147
+
148
+ if (this.hyperlinkMark) {
149
+ this.getDynamicParams();
150
+
151
+ // Shows menu.
152
+ if (!prevHyperlinkMark) {
153
+ this.hyperlinkToolbar.render(this.getDynamicParams(), true);
154
+
155
+ this.hyperlinkToolbar.element?.addEventListener(
156
+ "mouseleave",
157
+ this.startMenuUpdateTimer
158
+ );
159
+ this.hyperlinkToolbar.element?.addEventListener(
160
+ "mouseenter",
161
+ this.stopMenuUpdateTimer
162
+ );
163
+
164
+ return;
165
+ }
166
+
167
+ // Updates menu.
168
+ this.hyperlinkToolbar.render(this.getDynamicParams(), false);
169
+ }
170
+
171
+ // Hides menu.
172
+ if (!this.hyperlinkMark && prevHyperlinkMark) {
173
+ this.hyperlinkToolbar.element?.removeEventListener(
174
+ "mouseleave",
175
+ this.startMenuUpdateTimer
176
+ );
177
+ this.hyperlinkToolbar.element?.removeEventListener(
178
+ "mouseenter",
179
+ this.stopMenuUpdateTimer
180
+ );
181
+
182
+ this.hyperlinkToolbar.hide();
183
+
184
+ return;
185
+ }
186
+ }
187
+
188
+ getStaticParams(): HyperlinkToolbarStaticParams {
189
+ return {
190
+ editHyperlink: (url: string, text: string) => {
191
+ const tr = this.editor.view.state.tr.insertText(
192
+ text,
193
+ this.hyperlinkMarkRange!.from,
194
+ this.hyperlinkMarkRange!.to
195
+ );
196
+ tr.addMark(
197
+ this.hyperlinkMarkRange!.from,
198
+ this.hyperlinkMarkRange!.from + text.length,
199
+ this.editor.schema.mark("link", { href: url })
200
+ );
201
+ this.editor.view.dispatch(tr);
202
+ this.editor.view.focus();
203
+
204
+ this.hyperlinkToolbar.hide();
205
+ },
206
+ deleteHyperlink: () => {
207
+ this.editor.view.dispatch(
208
+ this.editor.view.state.tr
209
+ .removeMark(
210
+ this.hyperlinkMarkRange!.from,
211
+ this.hyperlinkMarkRange!.to,
212
+ this.hyperlinkMark!.type
213
+ )
214
+ .setMeta("preventAutolink", true)
215
+ );
216
+ this.editor.view.focus();
217
+
218
+ this.hyperlinkToolbar.hide();
219
+ },
220
+ };
221
+ }
222
+
223
+ getDynamicParams(): HyperlinkToolbarDynamicParams {
224
+ return {
225
+ url: this.hyperlinkMark!.attrs.href,
226
+ text: this.editor.view.state.doc.textBetween(
227
+ this.hyperlinkMarkRange!.from,
228
+ this.hyperlinkMarkRange!.to
229
+ ),
230
+ boundingBox: posToDOMRect(
231
+ this.editor.view,
232
+ this.hyperlinkMarkRange!.from,
233
+ this.hyperlinkMarkRange!.to
234
+ ),
235
+ };
236
+ }
237
+ }
238
+
239
+ export const createHyperlinkToolbarPlugin = (
240
+ editor: Editor,
241
+ options: HyperlinkToolbarPluginProps
242
+ ) => {
243
+ return new Plugin({
244
+ key: PLUGIN_KEY,
245
+ view: () =>
246
+ new HyperlinkToolbarView({
247
+ editor: editor,
248
+ hyperlinkToolbarFactory: options.hyperlinkToolbarFactory,
249
+ }),
250
+ });
251
+ };
@@ -1,7 +1,7 @@
1
1
  import { Editor, Extension } from "@tiptap/core";
2
2
  import { Node as ProsemirrorNode } from "prosemirror-model";
3
- import { Decoration, DecorationSet } from "prosemirror-view";
4
3
  import { Plugin } from "prosemirror-state";
4
+ import { Decoration, DecorationSet } from "prosemirror-view";
5
5
  import { SlashMenuPluginKey } from "../SlashMenu/SlashMenuExtension";
6
6
 
7
7
  /**
@@ -78,7 +78,7 @@ export const Placeholder = Extension.create<PlaceholderOptions>({
78
78
  }
79
79
 
80
80
  // If slash menu is of drag type and active, show the filter placeholder
81
- if (menuState.type === "drag" && menuState.active) {
81
+ if (menuState?.type === "drag" && menuState?.active) {
82
82
  classes.push(this.options.isFilterClass);
83
83
  }
84
84
  // using widget, didn't work (caret position bug)
@@ -1,11 +1,13 @@
1
1
  import { Extension } from "@tiptap/core";
2
+ import { PluginKey } from "prosemirror-state";
2
3
  import { createSuggestionPlugin } from "../../shared/plugins/suggestion/SuggestionPlugin";
4
+ import { SuggestionsMenuFactory } from "../../shared/plugins/suggestion/SuggestionsMenuFactoryTypes";
3
5
  import defaultCommands from "./defaultCommands";
4
6
  import { SlashMenuItem } from "./SlashMenuItem";
5
- import { PluginKey } from "prosemirror-state";
6
7
 
7
8
  export type SlashMenuOptions = {
8
9
  commands: { [key: string]: SlashMenuItem };
10
+ slashMenuFactory: SuggestionsMenuFactory<any> | undefined;
9
11
  };
10
12
 
11
13
  export const SlashMenuPluginKey = new PluginKey("suggestions-slash-commands");
@@ -16,15 +18,21 @@ export const SlashMenuExtension = Extension.create<SlashMenuOptions>({
16
18
  addOptions() {
17
19
  return {
18
20
  commands: defaultCommands,
21
+ slashMenuFactory: undefined, // TODO: fix undefined
19
22
  };
20
23
  },
21
24
 
22
25
  addProseMirrorPlugins() {
26
+ if (!this.options.slashMenuFactory) {
27
+ throw new Error("UI Element factory not defined for SlashMenuExtension");
28
+ }
29
+
23
30
  return [
24
31
  createSuggestionPlugin<SlashMenuItem>({
25
32
  pluginKey: SlashMenuPluginKey,
26
33
  editor: this.editor,
27
34
  char: "/",
35
+ suggestionsMenuFactory: this.options.slashMenuFactory!,
28
36
  items: (query) => {
29
37
  const commands = [];
30
38
 
@@ -1,6 +1,5 @@
1
1
  import { Editor, Range } from "@tiptap/core";
2
- import SuggestionItem from "../../shared/plugins/suggestion/SuggestionItem";
3
- import { IconType } from "react-icons";
2
+ import { SuggestionItem } from "../../shared/plugins/suggestion/SuggestionItem";
4
3
 
5
4
  export type SlashMenuCallback = (editor: Editor, range: Range) => boolean;
6
5
 
@@ -40,7 +39,6 @@ export class SlashMenuItem implements SuggestionItem {
40
39
  public readonly group: SlashMenuGroups,
41
40
  public readonly execute: SlashMenuCallback,
42
41
  public readonly aliases: string[] = [],
43
- public readonly icon?: IconType,
44
42
  public readonly hint?: string,
45
43
  public readonly shortcut?: string
46
44
  ) {