@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.
Files changed (143) hide show
  1. package/README.md +99 -0
  2. package/dist/blocknote.js +4485 -0
  3. package/dist/blocknote.js.map +1 -0
  4. package/dist/blocknote.umd.cjs +90 -0
  5. package/dist/blocknote.umd.cjs.map +1 -0
  6. package/dist/style.css +1 -0
  7. package/package.json +109 -0
  8. package/src/BlockNoteExtensions.ts +90 -0
  9. package/src/EditorContent.tsx +1 -0
  10. package/src/assets/inter-v12-latin/inter-v12-latin-100.woff +0 -0
  11. package/src/assets/inter-v12-latin/inter-v12-latin-100.woff2 +0 -0
  12. package/src/assets/inter-v12-latin/inter-v12-latin-200.woff +0 -0
  13. package/src/assets/inter-v12-latin/inter-v12-latin-200.woff2 +0 -0
  14. package/src/assets/inter-v12-latin/inter-v12-latin-300.woff +0 -0
  15. package/src/assets/inter-v12-latin/inter-v12-latin-300.woff2 +0 -0
  16. package/src/assets/inter-v12-latin/inter-v12-latin-500.woff +0 -0
  17. package/src/assets/inter-v12-latin/inter-v12-latin-500.woff2 +0 -0
  18. package/src/assets/inter-v12-latin/inter-v12-latin-600.woff +0 -0
  19. package/src/assets/inter-v12-latin/inter-v12-latin-600.woff2 +0 -0
  20. package/src/assets/inter-v12-latin/inter-v12-latin-700.woff +0 -0
  21. package/src/assets/inter-v12-latin/inter-v12-latin-700.woff2 +0 -0
  22. package/src/assets/inter-v12-latin/inter-v12-latin-800.woff +0 -0
  23. package/src/assets/inter-v12-latin/inter-v12-latin-800.woff2 +0 -0
  24. package/src/assets/inter-v12-latin/inter-v12-latin-900.woff +0 -0
  25. package/src/assets/inter-v12-latin/inter-v12-latin-900.woff2 +0 -0
  26. package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff +0 -0
  27. package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff2 +0 -0
  28. package/src/editor.module.css +3 -0
  29. package/src/extensions/Blocks/OrderedListPlugin.ts +46 -0
  30. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +146 -0
  31. package/src/extensions/Blocks/commands/joinBackward.ts +274 -0
  32. package/src/extensions/Blocks/helpers/findBlock.ts +3 -0
  33. package/src/extensions/Blocks/helpers/setBlockHeading.ts +30 -0
  34. package/src/extensions/Blocks/index.ts +15 -0
  35. package/src/extensions/Blocks/nodes/Block.module.css +226 -0
  36. package/src/extensions/Blocks/nodes/Block.ts +390 -0
  37. package/src/extensions/Blocks/nodes/BlockGroup.ts +28 -0
  38. package/src/extensions/Blocks/nodes/Content.ts +50 -0
  39. package/src/extensions/Blocks/nodes/README.md +26 -0
  40. package/src/extensions/Blocks/rule.ts +48 -0
  41. package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +28 -0
  42. package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +245 -0
  43. package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +216 -0
  44. package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +13 -0
  45. package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +25 -0
  46. package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +67 -0
  47. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +15 -0
  48. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +266 -0
  49. package/src/extensions/DraggableBlocks/components/DragHandle.module.css +33 -0
  50. package/src/extensions/DraggableBlocks/components/DragHandle.tsx +108 -0
  51. package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +10 -0
  52. package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +18 -0
  53. package/src/extensions/Hyperlinks/HyperlinkMark.tsx +16 -0
  54. package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +200 -0
  55. package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +59 -0
  56. package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +72 -0
  57. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +173 -0
  58. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +36 -0
  59. package/src/extensions/Hyperlinks/menus/atlaskit/README.md +1 -0
  60. package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +61 -0
  61. package/src/extensions/Paragraph/FixedParagraph.ts +12 -0
  62. package/src/extensions/Placeholder/PlaceholderExtension.ts +127 -0
  63. package/src/extensions/SlashMenu/SlashMenuExtension.ts +43 -0
  64. package/src/extensions/SlashMenu/SlashMenuItem.ts +56 -0
  65. package/src/extensions/SlashMenu/defaultCommands.tsx +229 -0
  66. package/src/extensions/SlashMenu/index.ts +11 -0
  67. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +70 -0
  68. package/src/extensions/UniqueID/UniqueID.ts +281 -0
  69. package/src/extensions/helpers/formatKeyboardShortcut.ts +9 -0
  70. package/src/fonts-inter.css +94 -0
  71. package/src/globals.css +28 -0
  72. package/src/index.ts +5 -0
  73. package/src/lib/atlaskit/browser.ts +47 -0
  74. package/src/root.module.css +19 -0
  75. package/src/shared/components/toolbar/SimpleToolbarButton.module.css +13 -0
  76. package/src/shared/components/toolbar/SimpleToolbarButton.tsx +56 -0
  77. package/src/shared/components/toolbar/Toolbar.module.css +10 -0
  78. package/src/shared/components/toolbar/Toolbar.tsx +5 -0
  79. package/src/shared/components/toolbar/ToolbarSeparator.module.css +13 -0
  80. package/src/shared/components/toolbar/ToolbarSeparator.tsx +7 -0
  81. package/src/shared/components/tooltip/TooltipContent.module.css +15 -0
  82. package/src/shared/components/tooltip/TooltipContent.tsx +23 -0
  83. package/src/shared/hooks/useEditorForceUpdate.tsx +30 -0
  84. package/src/shared/plugins/suggestion/SuggestionItem.ts +31 -0
  85. package/src/shared/plugins/suggestion/SuggestionListReactRenderer.ts +227 -0
  86. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +365 -0
  87. package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +45 -0
  88. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +134 -0
  89. package/src/shared/plugins/suggestion/components/SuggestionList.module.css +10 -0
  90. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +91 -0
  91. package/src/style.css +7 -0
  92. package/src/useEditor.ts +47 -0
  93. package/src/vite-env.d.ts +1 -0
  94. package/types/src/BlockNoteExtensions.d.ts +4 -0
  95. package/types/src/EditorContent.d.ts +1 -0
  96. package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +2 -0
  97. package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +13 -0
  98. package/types/src/extensions/Blocks/commands/joinBackward.d.ts +14 -0
  99. package/types/src/extensions/Blocks/helpers/findBlock.d.ts +6 -0
  100. package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +5 -0
  101. package/types/src/extensions/Blocks/index.d.ts +1 -0
  102. package/types/src/extensions/Blocks/nodes/Block.d.ts +32 -0
  103. package/types/src/extensions/Blocks/nodes/BlockGroup.d.ts +2 -0
  104. package/types/src/extensions/Blocks/nodes/Content.d.ts +5 -0
  105. package/types/src/extensions/Blocks/rule.d.ts +16 -0
  106. package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +5 -0
  107. package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +46 -0
  108. package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +5 -0
  109. package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +10 -0
  110. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +11 -0
  111. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +7 -0
  112. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +18 -0
  113. package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +12 -0
  114. package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +6 -0
  115. package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +7 -0
  116. package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +2 -0
  117. package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +12 -0
  118. package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +10 -0
  119. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +39 -0
  120. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +1 -0
  121. package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +11 -0
  122. package/types/src/extensions/Paragraph/FixedParagraph.d.ts +1 -0
  123. package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +25 -0
  124. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +10 -0
  125. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +43 -0
  126. package/types/src/extensions/SlashMenu/defaultCommands.d.ts +8 -0
  127. package/types/src/extensions/SlashMenu/index.d.ts +5 -0
  128. package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +10 -0
  129. package/types/src/extensions/UniqueID/UniqueID.d.ts +3 -0
  130. package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +1 -0
  131. package/types/src/index.d.ts +4 -0
  132. package/types/src/lib/atlaskit/browser.d.ts +12 -0
  133. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +16 -0
  134. package/types/src/shared/components/toolbar/Toolbar.d.ts +4 -0
  135. package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +2 -0
  136. package/types/src/shared/components/tooltip/TooltipContent.d.ts +15 -0
  137. package/types/src/shared/hooks/useEditorForceUpdate.d.ts +2 -0
  138. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +29 -0
  139. package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +71 -0
  140. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +74 -0
  141. package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +23 -0
  142. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +26 -0
  143. package/types/src/useEditor.d.ts +8 -0
@@ -0,0 +1,390 @@
1
+ import { mergeAttributes, Node } from "@tiptap/core";
2
+ import { Selection, TextSelection } from "prosemirror-state";
3
+ import { joinBackward } from "../commands/joinBackward";
4
+ import { findBlock } from "../helpers/findBlock";
5
+ import { setBlockHeading } from "../helpers/setBlockHeading";
6
+ import { OrderedListPlugin } from "../OrderedListPlugin";
7
+ import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
8
+ import { textblockTypeInputRuleSameNodeType } from "../rule";
9
+ import styles from "./Block.module.css";
10
+
11
+ export interface IBlock {
12
+ HTMLAttributes: Record<string, any>;
13
+ }
14
+
15
+ export type Level = 1 | 2 | 3;
16
+ export type ListType = "li" | "oli";
17
+
18
+ declare module "@tiptap/core" {
19
+ interface Commands<ReturnType> {
20
+ blockHeading: {
21
+ /**
22
+ * Set a heading node
23
+ */
24
+ setBlockHeading: (attributes: { level: Level }) => ReturnType;
25
+ /**
26
+ * Unset a heading node
27
+ */
28
+ unsetBlockHeading: () => ReturnType;
29
+
30
+ unsetList: () => ReturnType;
31
+
32
+ addNewBlockAsSibling: (attributes?: {
33
+ headingType?: Level;
34
+ listType?: ListType;
35
+ }) => ReturnType;
36
+
37
+ setBlockList: (type: ListType) => ReturnType;
38
+ };
39
+ }
40
+ }
41
+
42
+ /**
43
+ * The main "Block node" documents consist of
44
+ */
45
+ export const Block = Node.create<IBlock>({
46
+ name: "tcblock",
47
+ group: "block",
48
+ addOptions() {
49
+ return {
50
+ HTMLAttributes: {},
51
+ };
52
+ },
53
+
54
+ // A block always contains content, and optionally a blockGroup which contains nested blocks
55
+ content: "tccontent blockgroup?",
56
+
57
+ defining: true,
58
+
59
+ addAttributes() {
60
+ return {
61
+ listType: {
62
+ default: undefined,
63
+ renderHTML: (attributes) => {
64
+ return {
65
+ "data-listType": attributes.listType,
66
+ };
67
+ },
68
+ parseHTML: (element) => element.getAttribute("data-listType"),
69
+ },
70
+ blockColor: {
71
+ default: undefined,
72
+ renderHTML: (attributes) => {
73
+ return {
74
+ "data-blockColor": attributes.blockColor,
75
+ };
76
+ },
77
+ parseHTML: (element) => element.getAttribute("data-blockColor"),
78
+ },
79
+ blockStyle: {
80
+ default: undefined,
81
+ renderHTML: (attributes) => {
82
+ return {
83
+ "data-blockStyle": attributes.blockStyle,
84
+ };
85
+ },
86
+ parseHTML: (element) => element.getAttribute("data-blockStyle"),
87
+ },
88
+ headingType: {
89
+ default: undefined,
90
+ keepOnSplit: false,
91
+ renderHTML: (attributes) => {
92
+ return {
93
+ "data-headingType": attributes.headingType,
94
+ };
95
+ },
96
+ parseHTML: (element) => element.getAttribute("data-headingType"),
97
+ },
98
+ };
99
+ },
100
+
101
+ // TODO: should we parse <li>, <ol>, <h1>, etc?
102
+ parseHTML() {
103
+ return [
104
+ {
105
+ tag: "div",
106
+ },
107
+ ];
108
+ },
109
+
110
+ renderHTML({ HTMLAttributes }) {
111
+ return [
112
+ "div",
113
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
114
+ class: styles.blockOuter,
115
+ }),
116
+ [
117
+ "div",
118
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
119
+ class: styles.block,
120
+ }),
121
+ 0,
122
+ ],
123
+ ];
124
+ },
125
+
126
+ addInputRules() {
127
+ return [
128
+ ...[1, 2, 3].map((level) => {
129
+ // Create a heading when starting with "#", "##", or "###""
130
+ return textblockTypeInputRuleSameNodeType({
131
+ find: new RegExp(`^(#{1,${level}})\\s$`),
132
+ type: this.type,
133
+ getAttributes: {
134
+ headingType: level,
135
+ },
136
+ });
137
+ }),
138
+ // Create a list when starting with "-"
139
+ textblockTypeInputRuleSameNodeType({
140
+ find: /^\s*([-+*])\s$/,
141
+ type: this.type,
142
+ getAttributes: {
143
+ listType: "li",
144
+ },
145
+ }),
146
+ textblockTypeInputRuleSameNodeType({
147
+ find: new RegExp(/^1.\s/),
148
+ type: this.type,
149
+ getAttributes: {
150
+ listType: "oli",
151
+ },
152
+ }),
153
+ ];
154
+ },
155
+
156
+ addCommands() {
157
+ return {
158
+ setBlockHeading:
159
+ (attributes) =>
160
+ ({ tr, dispatch }) => {
161
+ return setBlockHeading(tr, dispatch, attributes.level);
162
+ },
163
+ unsetBlockHeading:
164
+ () =>
165
+ ({ tr, dispatch }) => {
166
+ return setBlockHeading(tr, dispatch, undefined);
167
+ },
168
+ unsetList:
169
+ () =>
170
+ ({ tr, dispatch }) => {
171
+ const node = tr.selection.$anchor.node(-1);
172
+ const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
173
+
174
+ // const node2 = tr.doc.nodeAt(nodePos);
175
+ if (node.type.name === "tcblock" && node.attrs["listType"]) {
176
+ if (dispatch) {
177
+ tr.setNodeMarkup(nodePos, undefined, {
178
+ ...node.attrs,
179
+ listType: undefined,
180
+ });
181
+ return true;
182
+ }
183
+ }
184
+ return false;
185
+ },
186
+
187
+ addNewBlockAsSibling:
188
+ (attributes) =>
189
+ ({ tr, dispatch, state }) => {
190
+ // Get current block
191
+ const currentBlock = findBlock(tr.selection);
192
+ if (!currentBlock) {
193
+ return false;
194
+ }
195
+
196
+ // If current blocks content is empty dont create a new block
197
+ if (currentBlock.node.firstChild?.textContent.length === 0) {
198
+ if (dispatch) {
199
+ tr.setNodeMarkup(currentBlock.pos, undefined, attributes);
200
+ }
201
+ return true;
202
+ }
203
+
204
+ // Create new block after current block
205
+ const endOfBlock = currentBlock.pos + currentBlock.node.nodeSize;
206
+ let newBlock =
207
+ state.schema.nodes["tcblock"].createAndFill(attributes)!;
208
+ if (dispatch) {
209
+ tr.insert(endOfBlock, newBlock);
210
+ tr.setSelection(new TextSelection(tr.doc.resolve(endOfBlock + 1)));
211
+ }
212
+ return true;
213
+ },
214
+ setBlockList:
215
+ (type) =>
216
+ ({ tr, dispatch }) => {
217
+ const node = tr.selection.$anchor.node(-1);
218
+ const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
219
+
220
+ // const node2 = tr.doc.nodeAt(nodePos);
221
+ if (node.type.name === "tcblock") {
222
+ if (dispatch) {
223
+ tr.setNodeMarkup(nodePos, undefined, {
224
+ ...node.attrs,
225
+ listType: type,
226
+ });
227
+ }
228
+ return true;
229
+ }
230
+ return false;
231
+ },
232
+ joinBackward:
233
+ () =>
234
+ ({ view, dispatch, state }) =>
235
+ joinBackward(state, dispatch, view), // Override default joinBackward with edited command
236
+ };
237
+ },
238
+ addProseMirrorPlugins() {
239
+ return [PreviousBlockTypePlugin(), OrderedListPlugin()];
240
+ },
241
+ addKeyboardShortcuts() {
242
+ // handleBackspace is partially adapted from https://github.com/ueberdosis/tiptap/blob/ed56337470efb4fd277128ab7ef792b37cfae992/packages/core/src/extensions/keymap.ts
243
+ const handleBackspace = () =>
244
+ this.editor.commands.first(({ commands }) => [
245
+ // Maybe the user wants to undo an auto formatting input rule (e.g.: - or #, and then hit backspace) (source: tiptap)
246
+ () => commands.undoInputRule(),
247
+ // maybe convert first text block node to default node (source: tiptap)
248
+ () =>
249
+ commands.command(({ tr }) => {
250
+ const { selection, doc } = tr;
251
+ const { empty, $anchor } = selection;
252
+ const { pos, parent } = $anchor;
253
+ const isAtStart = Selection.atStart(doc).from === pos;
254
+
255
+ if (
256
+ !empty ||
257
+ !isAtStart ||
258
+ !parent.type.isTextblock ||
259
+ parent.textContent.length
260
+ ) {
261
+ return false;
262
+ }
263
+
264
+ return commands.clearNodes();
265
+ }),
266
+ () => commands.deleteSelection(), // (source: tiptap)
267
+ () =>
268
+ commands.command(({ tr }) => {
269
+ const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
270
+ const node = tr.selection.$anchor.node(-1);
271
+ if (isAtStartOfNode && node.type.name === "tcblock") {
272
+ // we're at the start of the block, so we're trying to "backspace" the bullet or indentation
273
+ return commands.first([
274
+ () => commands.unsetList(), // first try to remove the "list" property
275
+ () => commands.liftListItem("tcblock"), // then try to remove a level of indentation
276
+ ]);
277
+ }
278
+ return false;
279
+ }),
280
+ ({ chain }) =>
281
+ // we are at the start of a block at the root level. The user hits backspace to "merge it" to the end of the block above
282
+ //
283
+ // BlockA
284
+ // BlockB
285
+
286
+ // Becomes:
287
+
288
+ // BlockABlockB
289
+
290
+ chain()
291
+ .command(({ tr, state, dispatch }) => {
292
+ const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
293
+ const anchor = tr.selection.$anchor;
294
+ const node = anchor.node(-1);
295
+ if (isAtStartOfNode && node.type.name === "tcblock") {
296
+ if (node.childCount === 2) {
297
+ // BlockB has children. We want to go from this:
298
+ //
299
+ // BlockA
300
+ // BlockB
301
+ // BlockC
302
+ // BlockD
303
+ //
304
+ // to:
305
+ //
306
+ // BlockABlockB
307
+ // BlockC
308
+ // BlockD
309
+
310
+ // This parts moves the children of BlockB to the top level
311
+ const startSecondChild = anchor.posAtIndex(1, -1) + 1; // start of blockgroup
312
+ const endSecondChild = anchor.posAtIndex(2, -1) - 1;
313
+ const range = state.doc
314
+ .resolve(startSecondChild)
315
+ .blockRange(state.doc.resolve(endSecondChild));
316
+
317
+ if (dispatch) {
318
+ tr.lift(range!, anchor.depth - 2);
319
+ }
320
+ }
321
+ return true;
322
+ }
323
+ return false;
324
+ })
325
+ // use joinBackward to merge BlockB to BlockA (i.e.: turn it into BlockABlockB)
326
+ // The standard JoinBackward would break here, and would turn it into:
327
+ // BlockA
328
+ // BlockB
329
+ //
330
+ // joinBackward has been patched with our custom version to fix this (see commands/joinBackward)
331
+ .joinBackward()
332
+ .run(),
333
+
334
+ () => commands.selectNodeBackward(), // (source: tiptap)
335
+ ]);
336
+
337
+ const handleEnter = () =>
338
+ this.editor.commands.first(({ commands }) => [
339
+ // Try to split the current block into 2 items:
340
+ () => commands.splitListItem("tcblock"),
341
+ // Otherwise, maybe we are in an empty list item. "Enter" should remove the list bullet
342
+ ({ tr, dispatch }) => {
343
+ const $from = tr.selection.$from;
344
+ if ($from.depth !== 3) {
345
+ // only needed at root level, at deeper levels it should be handled already by splitListItem
346
+ return false;
347
+ }
348
+ const node = tr.selection.$anchor.node(-1);
349
+ const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
350
+
351
+ if (node.type.name === "tcblock" && node.attrs["listType"]) {
352
+ if (dispatch) {
353
+ tr.setNodeMarkup(nodePos, undefined, {
354
+ ...node.attrs,
355
+ listType: undefined,
356
+ });
357
+ }
358
+ return true;
359
+ }
360
+ return false;
361
+ },
362
+ // Otherwise, we might be on an empty line and hit "Enter" to create a new line:
363
+ ({ tr, dispatch }) => {
364
+ const $from = tr.selection.$from;
365
+
366
+ if (dispatch) {
367
+ tr.split($from.pos, 2).scrollIntoView();
368
+ }
369
+ return true;
370
+ },
371
+ ]);
372
+
373
+ return {
374
+ Backspace: handleBackspace,
375
+ Enter: handleEnter,
376
+ Tab: () => this.editor.commands.sinkListItem("tcblock"),
377
+ "Shift-Tab": () => {
378
+ return this.editor.commands.liftListItem("tcblock");
379
+ },
380
+ "Mod-Alt-0": () =>
381
+ this.editor.chain().unsetList().unsetBlockHeading().run(),
382
+ "Mod-Alt-1": () => this.editor.commands.setBlockHeading({ level: 1 }),
383
+ "Mod-Alt-2": () => this.editor.commands.setBlockHeading({ level: 2 }),
384
+ "Mod-Alt-3": () => this.editor.commands.setBlockHeading({ level: 3 }),
385
+ "Mod-Shift-7": () => this.editor.commands.setBlockList("li"),
386
+ "Mod-Shift-8": () => this.editor.commands.setBlockList("oli"),
387
+ // TODO: Add shortcuts for numbered and bullet list
388
+ };
389
+ },
390
+ });
@@ -0,0 +1,28 @@
1
+ import { mergeAttributes, Node } from "@tiptap/core";
2
+ import styles from "./Block.module.css";
3
+
4
+ export const BlockGroup = Node.create({
5
+ name: "blockgroup",
6
+
7
+ addOptions() {
8
+ return {
9
+ HTMLAttributes: {},
10
+ };
11
+ },
12
+
13
+ content: "tcblock+",
14
+
15
+ parseHTML() {
16
+ return [{ tag: "div" }];
17
+ },
18
+
19
+ renderHTML({ HTMLAttributes }) {
20
+ return [
21
+ "div",
22
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
23
+ class: styles.blockGroup,
24
+ }),
25
+ 0,
26
+ ];
27
+ },
28
+ });
@@ -0,0 +1,50 @@
1
+ import { mergeAttributes, Node } from "@tiptap/core";
2
+ import styles from "./Block.module.css";
3
+ export interface IBlock {
4
+ HTMLAttributes: Record<string, any>;
5
+ }
6
+
7
+ export const ContentBlock = Node.create<IBlock>({
8
+ name: "tccontent",
9
+
10
+ addOptions() {
11
+ return {
12
+ HTMLAttributes: {},
13
+ };
14
+ },
15
+ addAttributes() {
16
+ return {
17
+ position: {
18
+ default: undefined,
19
+ renderHTML: (attributes) => {
20
+ return {
21
+ "data-position": attributes.position,
22
+ };
23
+ },
24
+ parseHTML: (element) => element.getAttribute("data-position"),
25
+ },
26
+ };
27
+ },
28
+
29
+ content: "inline*",
30
+
31
+ parseHTML() {
32
+ return [
33
+ {
34
+ tag: "div",
35
+ },
36
+ ];
37
+ },
38
+
39
+ renderHTML({ HTMLAttributes }) {
40
+ return [
41
+ "div",
42
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
43
+ class: styles.blockContent,
44
+ }),
45
+ // TODO: The extra nested div is only needed for placeholders, different solution (without extra div) would be preferable
46
+ // We can't use the other div because the ::before attribute on that one is already reserved for list-bullets
47
+ ["div", 0],
48
+ ];
49
+ },
50
+ });
@@ -0,0 +1,26 @@
1
+ # Node structure
2
+
3
+ We use the following document structure:
4
+
5
+ ```xml
6
+ <blockgroup>
7
+ <block>
8
+ <content>Parent element 1</content>
9
+ <blockgroup>
10
+ <block>
11
+ <content>Nested / child / indented item</content>
12
+ </block>
13
+ </blockgroup>
14
+ </block>
15
+ <block>
16
+ <content>Parent element 2</content>
17
+ <blockgroup>
18
+ <block>...</block>
19
+ <block>...</block>
20
+ </blockgroup>
21
+ </block>
22
+ <block>
23
+ <content>Element 3 without children</content>
24
+ </block>
25
+ </blockgroup>
26
+ ```
@@ -0,0 +1,48 @@
1
+ import {
2
+ callOrReturn,
3
+ ExtendedRegExpMatchArray,
4
+ InputRule,
5
+ InputRuleFinder,
6
+ } from "@tiptap/core";
7
+ import { NodeType } from "prosemirror-model";
8
+
9
+ /**
10
+ * Modified version of https://github.com/ueberdosis/tiptap/blob/6a813686f5e87cebac49a624936dbeadb5a29f95/packages/core/src/inputRules/textblockTypeInputRule.ts
11
+ * But instead of changing the type of a node, we use setNodeMarkup to change some of it's current attributes
12
+ *
13
+ * Build an input rule that changes the type of a textblock when the
14
+ * matched text is typed into it. When using a regular expresion you’ll
15
+ * probably want the regexp to start with `^`, so that the pattern can
16
+ * only occur at the start of a textblock.
17
+ */
18
+ export function textblockTypeInputRuleSameNodeType(config: {
19
+ find: InputRuleFinder;
20
+ type: NodeType;
21
+ getAttributes?:
22
+ | Record<string, any>
23
+ | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
24
+ | false
25
+ | null;
26
+ }) {
27
+ return new InputRule({
28
+ find: config.find,
29
+ handler: ({ state, range, match }) => {
30
+ const $start = state.doc.resolve(range.from);
31
+ const attributes =
32
+ callOrReturn(config.getAttributes, undefined, match) || {};
33
+
34
+ const blockNode = $start.node(-1);
35
+ if (blockNode.type !== config.type) {
36
+ return null;
37
+ }
38
+
39
+ state.tr
40
+ .setNodeMarkup(range.from - 2, undefined, {
41
+ ...blockNode.attrs,
42
+ ...attributes,
43
+ })
44
+ .delete(range.from, range.to);
45
+ return;
46
+ },
47
+ });
48
+ }
@@ -0,0 +1,28 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { PluginKey } from "prosemirror-state";
3
+ import ReactDOM from "react-dom";
4
+ import { createBubbleMenuPlugin } from "./BubbleMenuPlugin";
5
+ import { BubbleMenu } from "./component/BubbleMenu";
6
+ import rootStyles from "../../root.module.css";
7
+ /**
8
+ * The menu that is displayed when selecting a piece of text.
9
+ */
10
+ export const BubbleMenuExtension = Extension.create<{}>({
11
+ name: "BubbleMenuExtension",
12
+
13
+ addProseMirrorPlugins() {
14
+ const element = document.createElement("div");
15
+ element.className = rootStyles.bnRoot;
16
+ ReactDOM.render(<BubbleMenu editor={this.editor} />, element);
17
+ return [
18
+ createBubbleMenuPlugin({
19
+ editor: this.editor,
20
+ element,
21
+ pluginKey: new PluginKey("BubbleMenuPlugin"),
22
+ tippyOptions: {
23
+ appendTo: document.body,
24
+ },
25
+ }),
26
+ ];
27
+ },
28
+ });