@blocknote/core 0.44.2 → 0.45.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 (72) hide show
  1. package/dist/BlockNoteExtension-BWw0r8Gy.cjs.map +1 -1
  2. package/dist/BlockNoteExtension-C2X7LW-V.js.map +1 -1
  3. package/dist/{BlockNoteSchema-BsTi0fNS.js → BlockNoteSchema-BOW16JHv.js} +2 -2
  4. package/dist/{BlockNoteSchema-BsTi0fNS.js.map → BlockNoteSchema-BOW16JHv.js.map} +1 -1
  5. package/dist/{BlockNoteSchema-CBNkNhkw.cjs → BlockNoteSchema-CzZbr4Ed.cjs} +2 -2
  6. package/dist/{BlockNoteSchema-CBNkNhkw.cjs.map → BlockNoteSchema-CzZbr4Ed.cjs.map} +1 -1
  7. package/dist/{TrailingNode-CG2a-HDA.js → TrailingNode-8cXFaQUm.js} +484 -487
  8. package/dist/TrailingNode-8cXFaQUm.js.map +1 -0
  9. package/dist/TrailingNode-DPu6X9ym.cjs +2 -0
  10. package/dist/TrailingNode-DPu6X9ym.cjs.map +1 -0
  11. package/dist/{blockToNode-DBNbhwwC.js → blockToNode-BNoNIXU7.js} +2 -2
  12. package/dist/{blockToNode-DBNbhwwC.js.map → blockToNode-BNoNIXU7.js.map} +1 -1
  13. package/dist/{blockToNode-w7H99R6p.cjs → blockToNode-CumVjgem.cjs} +2 -2
  14. package/dist/{blockToNode-w7H99R6p.cjs.map → blockToNode-CumVjgem.cjs.map} +1 -1
  15. package/dist/blocknote.cjs +4 -4
  16. package/dist/blocknote.cjs.map +1 -1
  17. package/dist/blocknote.js +1118 -1077
  18. package/dist/blocknote.js.map +1 -1
  19. package/dist/blocks.cjs +1 -1
  20. package/dist/blocks.js +2 -2
  21. package/dist/defaultBlocks-D1cc0lV9.cjs +6 -0
  22. package/dist/defaultBlocks-D1cc0lV9.cjs.map +1 -0
  23. package/dist/{defaultBlocks-B63ufZ5N.js → defaultBlocks-DvCGYzqu.js} +168 -206
  24. package/dist/defaultBlocks-DvCGYzqu.js.map +1 -0
  25. package/dist/extensions.cjs +1 -1
  26. package/dist/extensions.js +3 -3
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/dist/webpack-stats.json +1 -1
  29. package/dist/yjs.cjs +1 -1
  30. package/dist/yjs.js +1 -1
  31. package/package.json +15 -15
  32. package/src/api/blockManipulation/selections/selection.ts +9 -4
  33. package/src/api/blockManipulation/tables/tables.test.ts +140 -0
  34. package/src/api/blockManipulation/tables/tables.ts +1 -1
  35. package/src/api/parsers/markdown/parseMarkdown.ts +11 -0
  36. package/src/blocks/ListItem/CheckListItem/block.ts +2 -2
  37. package/src/blocks/ListItem/NumberedListItem/block.ts +5 -1
  38. package/src/editor/BlockNoteEditor.test.ts +0 -1
  39. package/src/editor/BlockNoteEditor.ts +9 -39
  40. package/src/editor/BlockNoteExtension.ts +5 -0
  41. package/src/editor/managers/EventManager.ts +1 -1
  42. package/src/editor/managers/ExtensionManager/extensions.ts +2 -12
  43. package/src/editor/managers/ExtensionManager/index.ts +7 -2
  44. package/src/editor/managers/SelectionManager.ts +10 -10
  45. package/src/extensions/BlockChange/BlockChange.ts +2 -2
  46. package/src/extensions/Collaboration/Collaboration.ts +55 -0
  47. package/src/extensions/Collaboration/ForkYDoc.ts +4 -9
  48. package/src/extensions/Collaboration/YCursorPlugin.ts +56 -60
  49. package/src/extensions/Collaboration/YSync.ts +2 -2
  50. package/src/extensions/Collaboration/YUndo.ts +2 -2
  51. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +40 -68
  52. package/src/extensions/TableHandles/TableHandles.ts +9 -5
  53. package/src/index.ts +2 -1
  54. package/src/schema/blocks/createSpec.ts +3 -0
  55. package/src/util/expandToWords.ts +38 -0
  56. package/types/src/api/blockManipulation/selections/selection.d.ts +1 -1
  57. package/types/src/editor/BlockNoteEditor.d.ts +5 -34
  58. package/types/src/editor/BlockNoteExtension.d.ts +4 -0
  59. package/types/src/editor/managers/SelectionManager.d.ts +4 -4
  60. package/types/src/extensions/Collaboration/Collaboration.d.ts +76 -0
  61. package/types/src/extensions/Collaboration/ForkYDoc.d.ts +2 -11
  62. package/types/src/extensions/Collaboration/YCursorPlugin.d.ts +3 -11
  63. package/types/src/extensions/Collaboration/YSync.d.ts +2 -4
  64. package/types/src/extensions/Collaboration/YUndo.d.ts +1 -1
  65. package/types/src/index.d.ts +2 -1
  66. package/types/src/util/expandToWords.d.ts +13 -0
  67. package/dist/TrailingNode-CG2a-HDA.js.map +0 -1
  68. package/dist/TrailingNode-Du4SNHun.cjs +0 -2
  69. package/dist/TrailingNode-Du4SNHun.cjs.map +0 -1
  70. package/dist/defaultBlocks-B63ufZ5N.js.map +0 -1
  71. package/dist/defaultBlocks-BX6UxQa8.cjs +0 -6
  72. package/dist/defaultBlocks-BX6UxQa8.cjs.map +0 -1
@@ -203,6 +203,12 @@ export class ExtensionManager {
203
203
 
204
204
  this.extensions.push(instance);
205
205
 
206
+ if (instance.blockNoteExtensions) {
207
+ for (const extension of instance.blockNoteExtensions) {
208
+ this.addExtension(extension);
209
+ }
210
+ }
211
+
206
212
  return instance as any;
207
213
  }
208
214
 
@@ -317,8 +323,7 @@ export class ExtensionManager {
317
323
  const tiptapExtensions = getDefaultTiptapExtensions(
318
324
  this.editor,
319
325
  this.options,
320
- );
321
- // TODO filter out the default extensions via the disabledExtensions set?
326
+ ).filter((extension) => !this.disabledExtensions.has(extension.name));
322
327
 
323
328
  const getPriority = sortByDependencies(this.extensions);
324
329
 
@@ -1,3 +1,4 @@
1
+ import { isNodeSelection, posToDOMRect } from "@tiptap/core";
1
2
  import {
2
3
  getSelection,
3
4
  getSelectionCutBlocks,
@@ -7,21 +8,20 @@ import {
7
8
  getTextCursorPosition,
8
9
  setTextCursorPosition,
9
10
  } from "../../api/blockManipulation/selections/textCursorPosition.js";
10
- import { isNodeSelection, posToDOMRect } from "@tiptap/core";
11
+ import {
12
+ DefaultBlockSchema,
13
+ DefaultInlineContentSchema,
14
+ DefaultStyleSchema,
15
+ } from "../../blocks/defaultBlocks.js";
11
16
  import {
12
17
  BlockIdentifier,
13
18
  BlockSchema,
14
19
  InlineContentSchema,
15
20
  StyleSchema,
16
21
  } from "../../schema/index.js";
17
- import {
18
- DefaultBlockSchema,
19
- DefaultInlineContentSchema,
20
- DefaultStyleSchema,
21
- } from "../../blocks/defaultBlocks.js";
22
- import { Selection } from "../selectionTypes.js";
23
- import { TextCursorPosition } from "../cursorPositionTypes.js";
24
22
  import { BlockNoteEditor } from "../BlockNoteEditor.js";
23
+ import { TextCursorPosition } from "../cursorPositionTypes.js";
24
+ import { Selection } from "../selectionTypes.js";
25
25
 
26
26
  export class SelectionManager<
27
27
  BSchema extends BlockSchema = DefaultBlockSchema,
@@ -47,8 +47,8 @@ export class SelectionManager<
47
47
  * If the selection starts / ends halfway through a block, the returned block will be
48
48
  * only the part of the block that is included in the selection.
49
49
  */
50
- public getSelectionCutBlocks() {
51
- return this.editor.transact((tr) => getSelectionCutBlocks(tr));
50
+ public getSelectionCutBlocks(expandToWords = false) {
51
+ return this.editor.transact((tr) => getSelectionCutBlocks(tr, expandToWords));
52
52
  }
53
53
 
54
54
  /**
@@ -20,7 +20,7 @@ export const BlockChangeExtension = createExtension(() => {
20
20
  key: new PluginKey("blockChange"),
21
21
  filterTransaction: (tr) => {
22
22
  let changes:
23
- | ReturnType<typeof getBlocksChangedByTransaction>
23
+ | ReturnType<typeof getBlocksChangedByTransaction<any, any, any>>
24
24
  | undefined = undefined;
25
25
 
26
26
  return beforeChangeCallbacks.reduce((acc, cb) => {
@@ -34,7 +34,7 @@ export const BlockChangeExtension = createExtension(() => {
34
34
  if (changes) {
35
35
  return changes;
36
36
  }
37
- changes = getBlocksChangedByTransaction(tr);
37
+ changes = getBlocksChangedByTransaction<any, any, any>(tr);
38
38
  return changes;
39
39
  },
40
40
  tr,
@@ -0,0 +1,55 @@
1
+ import type * as Y from "yjs";
2
+ import type { Awareness } from "y-protocols/awareness";
3
+ import {
4
+ createExtension,
5
+ ExtensionOptions,
6
+ } from "../../editor/BlockNoteExtension.js";
7
+ import { ForkYDocExtension } from "./ForkYDoc.js";
8
+ import { SchemaMigration } from "./schemaMigration/SchemaMigration.js";
9
+ import { YCursorExtension } from "./YCursorPlugin.js";
10
+ import { YSyncExtension } from "./YSync.js";
11
+ import { YUndoExtension } from "./YUndo.js";
12
+
13
+ export type CollaborationOptions = {
14
+ /**
15
+ * The Yjs XML fragment that's used for collaboration.
16
+ */
17
+ fragment: Y.XmlFragment;
18
+ /**
19
+ * The user info for the current user that's shown to other collaborators.
20
+ */
21
+ user: {
22
+ name: string;
23
+ color: string;
24
+ };
25
+ /**
26
+ * A Yjs provider (used for awareness / cursor information)
27
+ */
28
+ provider?: { awareness?: Awareness };
29
+ /**
30
+ * Optional function to customize how cursors of users are rendered
31
+ */
32
+ renderCursor?: (user: any) => HTMLElement;
33
+ /**
34
+ * Optional flag to set when the user label should be shown with the default
35
+ * collaboration cursor. Setting to "always" will always show the label,
36
+ * while "activity" will only show the label when the user moves the cursor
37
+ * or types. Defaults to "activity".
38
+ */
39
+ showCursorLabels?: "always" | "activity";
40
+ };
41
+
42
+ export const CollaborationExtension = createExtension(
43
+ ({ options }: ExtensionOptions<CollaborationOptions>) => {
44
+ return {
45
+ key: "collaboration",
46
+ blockNoteExtensions: [
47
+ ForkYDocExtension(options),
48
+ YCursorExtension(options),
49
+ YSyncExtension(options),
50
+ YUndoExtension(),
51
+ SchemaMigration(options),
52
+ ],
53
+ } as const;
54
+ },
55
+ );
@@ -5,10 +5,10 @@ import {
5
5
  createStore,
6
6
  ExtensionOptions,
7
7
  } from "../../editor/BlockNoteExtension.js";
8
+ import { CollaborationOptions } from "./Collaboration.js";
8
9
  import { YCursorExtension } from "./YCursorPlugin.js";
9
10
  import { YSyncExtension } from "./YSync.js";
10
11
  import { YUndoExtension } from "./YUndo.js";
11
- import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
12
12
 
13
13
  /**
14
14
  * To find a fragment in another ydoc, we need to search for it.
@@ -44,12 +44,7 @@ function findTypeInOtherYdoc<T extends Y.AbstractType<any>>(
44
44
  }
45
45
 
46
46
  export const ForkYDocExtension = createExtension(
47
- ({
48
- editor,
49
- options,
50
- }: ExtensionOptions<
51
- NonNullable<BlockNoteEditorOptions<any, any, any>["collaboration"]>
52
- >) => {
47
+ ({ editor, options }: ExtensionOptions<CollaborationOptions>) => {
53
48
  let forkedState:
54
49
  | {
55
50
  originalFragment: Y.XmlFragment;
@@ -107,7 +102,7 @@ export const ForkYDocExtension = createExtension(
107
102
  editor.registerExtension([
108
103
  YSyncExtension(newOptions),
109
104
  // No need to register the cursor plugin again, it's a local fork
110
- YUndoExtension({}),
105
+ YUndoExtension(),
111
106
  ]);
112
107
 
113
108
  // Tell the store that the editor is now forked
@@ -131,7 +126,7 @@ export const ForkYDocExtension = createExtension(
131
126
  editor.registerExtension([
132
127
  YSyncExtension(options),
133
128
  YCursorExtension(options),
134
- YUndoExtension({}),
129
+ YUndoExtension(),
135
130
  ]);
136
131
 
137
132
  // Reset the undo stack to the original undo stack
@@ -3,7 +3,7 @@ import {
3
3
  createExtension,
4
4
  ExtensionOptions,
5
5
  } from "../../editor/BlockNoteExtension.js";
6
- import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
6
+ import { CollaborationOptions } from "./Collaboration.js";
7
7
 
8
8
  export type CollaborationUser = {
9
9
  name: string;
@@ -67,30 +67,24 @@ function defaultCursorRender(user: CollaborationUser) {
67
67
  }
68
68
 
69
69
  export const YCursorExtension = createExtension(
70
- ({
71
- options,
72
- }: ExtensionOptions<
73
- NonNullable<BlockNoteEditorOptions<any, any, any>["collaboration"]>
74
- >) => {
70
+ ({ options }: ExtensionOptions<CollaborationOptions>) => {
75
71
  const recentlyUpdatedCursors = new Map();
76
-
77
- if (
72
+ const awareness =
78
73
  options.provider &&
79
74
  "awareness" in options.provider &&
80
75
  typeof options.provider.awareness === "object"
81
- ) {
76
+ ? options.provider.awareness
77
+ : undefined;
78
+ if (awareness) {
82
79
  if (
83
- "setLocalStateField" in options.provider.awareness &&
84
- typeof options.provider.awareness.setLocalStateField === "function"
80
+ "setLocalStateField" in awareness &&
81
+ typeof awareness.setLocalStateField === "function"
85
82
  ) {
86
- options.provider.awareness.setLocalStateField("user", options.user);
83
+ awareness.setLocalStateField("user", options.user);
87
84
  }
88
- if (
89
- "on" in options.provider.awareness &&
90
- typeof options.provider.awareness.on === "function"
91
- ) {
85
+ if ("on" in awareness && typeof awareness.on === "function") {
92
86
  if (options.showCursorLabels !== "always") {
93
- options.provider.awareness.on(
87
+ awareness.on(
94
88
  "change",
95
89
  ({
96
90
  updated,
@@ -126,57 +120,59 @@ export const YCursorExtension = createExtension(
126
120
  return {
127
121
  key: "yCursor",
128
122
  prosemirrorPlugins: [
129
- yCursorPlugin(options.provider.awareness, {
130
- selectionBuilder: defaultSelectionBuilder,
131
- cursorBuilder(user: CollaborationUser, clientID: number) {
132
- let cursorData = recentlyUpdatedCursors.get(clientID);
133
-
134
- if (!cursorData) {
135
- const cursorElement = (
136
- options.renderCursor ?? defaultCursorRender
137
- )(user);
138
-
139
- if (options.showCursorLabels !== "always") {
140
- cursorElement.addEventListener("mouseenter", () => {
141
- const cursor = recentlyUpdatedCursors.get(clientID)!;
142
- cursor.element.setAttribute("data-active", "");
143
-
144
- if (cursor.hideTimeout) {
145
- clearTimeout(cursor.hideTimeout);
146
- recentlyUpdatedCursors.set(clientID, {
147
- element: cursor.element,
148
- hideTimeout: undefined,
123
+ awareness
124
+ ? yCursorPlugin(awareness, {
125
+ selectionBuilder: defaultSelectionBuilder,
126
+ cursorBuilder(user: CollaborationUser, clientID: number) {
127
+ let cursorData = recentlyUpdatedCursors.get(clientID);
128
+
129
+ if (!cursorData) {
130
+ const cursorElement = (
131
+ options.renderCursor ?? defaultCursorRender
132
+ )(user);
133
+
134
+ if (options.showCursorLabels !== "always") {
135
+ cursorElement.addEventListener("mouseenter", () => {
136
+ const cursor = recentlyUpdatedCursors.get(clientID)!;
137
+ cursor.element.setAttribute("data-active", "");
138
+
139
+ if (cursor.hideTimeout) {
140
+ clearTimeout(cursor.hideTimeout);
141
+ recentlyUpdatedCursors.set(clientID, {
142
+ element: cursor.element,
143
+ hideTimeout: undefined,
144
+ });
145
+ }
149
146
  });
150
- }
151
- });
152
147
 
153
- cursorElement.addEventListener("mouseleave", () => {
154
- const cursor = recentlyUpdatedCursors.get(clientID)!;
148
+ cursorElement.addEventListener("mouseleave", () => {
149
+ const cursor = recentlyUpdatedCursors.get(clientID)!;
155
150
 
156
- recentlyUpdatedCursors.set(clientID, {
157
- element: cursor.element,
158
- hideTimeout: setTimeout(() => {
159
- cursor.element.removeAttribute("data-active");
160
- }, 2000),
161
- });
162
- });
163
- }
151
+ recentlyUpdatedCursors.set(clientID, {
152
+ element: cursor.element,
153
+ hideTimeout: setTimeout(() => {
154
+ cursor.element.removeAttribute("data-active");
155
+ }, 2000),
156
+ });
157
+ });
158
+ }
164
159
 
165
- cursorData = {
166
- element: cursorElement,
167
- hideTimeout: undefined,
168
- };
160
+ cursorData = {
161
+ element: cursorElement,
162
+ hideTimeout: undefined,
163
+ };
169
164
 
170
- recentlyUpdatedCursors.set(clientID, cursorData);
171
- }
165
+ recentlyUpdatedCursors.set(clientID, cursorData);
166
+ }
172
167
 
173
- return cursorData.element;
174
- },
175
- }),
176
- ],
168
+ return cursorData.element;
169
+ },
170
+ })
171
+ : undefined,
172
+ ].filter(Boolean),
177
173
  dependsOn: ["ySync"],
178
174
  updateUser(user: { name: string; color: string; [key: string]: string }) {
179
- options.provider.awareness.setLocalStateField("user", user);
175
+ awareness?.setLocalStateField("user", user);
180
176
  },
181
177
  } as const;
182
178
  },
@@ -1,12 +1,12 @@
1
1
  import { ySyncPlugin } from "y-prosemirror";
2
- import { XmlFragment } from "yjs";
3
2
  import {
4
3
  ExtensionOptions,
5
4
  createExtension,
6
5
  } from "../../editor/BlockNoteExtension.js";
6
+ import { CollaborationOptions } from "./Collaboration.js";
7
7
 
8
8
  export const YSyncExtension = createExtension(
9
- ({ options }: ExtensionOptions<{ fragment: XmlFragment }>) => {
9
+ ({ options }: ExtensionOptions<Pick<CollaborationOptions, "fragment">>) => {
10
10
  return {
11
11
  key: "ySync",
12
12
  prosemirrorPlugins: [ySyncPlugin(options.fragment)],
@@ -1,10 +1,10 @@
1
1
  import { redoCommand, undoCommand, yUndoPlugin } from "y-prosemirror";
2
2
  import { createExtension } from "../../editor/BlockNoteExtension.js";
3
3
 
4
- export const YUndoExtension = createExtension(({ editor }) => {
4
+ export const YUndoExtension = createExtension(() => {
5
5
  return {
6
6
  key: "yUndo",
7
- prosemirrorPlugins: [yUndoPlugin({ trackedOrigins: [editor] })],
7
+ prosemirrorPlugins: [yUndoPlugin()],
8
8
  dependsOn: ["yCursor", "ySync"],
9
9
  undoCommand: undoCommand,
10
10
  redoCommand: redoCommand,
@@ -89,41 +89,24 @@ export function getDefaultSlashMenuItems<
89
89
  const items: DefaultSuggestionItem[] = [];
90
90
 
91
91
  if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
92
- items.push(
93
- {
94
- onItemClick: () => {
95
- insertOrUpdateBlockForSlashMenu(editor, {
96
- type: "heading",
97
- props: { level: 1 },
98
- });
99
- },
100
- badge: formatKeyboardShortcut("Mod-Alt-1"),
101
- key: "heading",
102
- ...editor.dictionary.slash_menu.heading,
103
- },
104
- {
105
- onItemClick: () => {
106
- insertOrUpdateBlockForSlashMenu(editor, {
107
- type: "heading",
108
- props: { level: 2 },
109
- });
110
- },
111
- badge: formatKeyboardShortcut("Mod-Alt-2"),
112
- key: "heading_2",
113
- ...editor.dictionary.slash_menu.heading_2,
114
- },
115
- {
116
- onItemClick: () => {
117
- insertOrUpdateBlockForSlashMenu(editor, {
118
- type: "heading",
119
- props: { level: 3 },
120
- });
121
- },
122
- badge: formatKeyboardShortcut("Mod-Alt-3"),
123
- key: "heading_3",
124
- ...editor.dictionary.slash_menu.heading_3,
125
- },
126
- );
92
+ (editor.schema.blockSchema.heading.propSchema.level.values || [])
93
+ .filter((level): level is 1 | 2 | 3 => level <= 3)
94
+ .forEach((level) => {
95
+ items.push({
96
+ onItemClick: () => {
97
+ insertOrUpdateBlockForSlashMenu(editor, {
98
+ type: "heading",
99
+ props: { level: level },
100
+ });
101
+ },
102
+ badge: formatKeyboardShortcut(`Mod-Alt-${level}`),
103
+ key:
104
+ level === 1 ? ("heading" as const) : (`heading_${level}` as const),
105
+ ...editor.dictionary.slash_menu[
106
+ level === 1 ? ("heading" as const) : (`heading_${level}` as const)
107
+ ],
108
+ });
109
+ });
127
110
  }
128
111
 
129
112
  if (editorHasBlockWithType(editor, "quote")) {
@@ -316,39 +299,27 @@ export function getDefaultSlashMenuItems<
316
299
  isToggleable: "boolean",
317
300
  })
318
301
  ) {
319
- items.push(
320
- {
321
- onItemClick: () => {
322
- insertOrUpdateBlockForSlashMenu(editor, {
323
- type: "heading",
324
- props: { level: 1, isToggleable: true },
325
- });
326
- },
327
- key: "toggle_heading",
328
- ...editor.dictionary.slash_menu.toggle_heading,
329
- },
330
- {
331
- onItemClick: () => {
332
- insertOrUpdateBlockForSlashMenu(editor, {
333
- type: "heading",
334
- props: { level: 2, isToggleable: true },
335
- });
336
- },
337
-
338
- key: "toggle_heading_2",
339
- ...editor.dictionary.slash_menu.toggle_heading_2,
340
- },
341
- {
342
- onItemClick: () => {
343
- insertOrUpdateBlockForSlashMenu(editor, {
344
- type: "heading",
345
- props: { level: 3, isToggleable: true },
346
- });
347
- },
348
- key: "toggle_heading_3",
349
- ...editor.dictionary.slash_menu.toggle_heading_3,
350
- },
351
- );
302
+ (editor.schema.blockSchema.heading.propSchema.level.values || [])
303
+ .filter((level): level is 1 | 2 | 3 => level <= 3)
304
+ .forEach((level) => {
305
+ items.push({
306
+ onItemClick: () => {
307
+ insertOrUpdateBlockForSlashMenu(editor, {
308
+ type: "heading",
309
+ props: { level: level, isToggleable: true },
310
+ });
311
+ },
312
+ key:
313
+ level === 1
314
+ ? ("toggle_heading" as const)
315
+ : (`toggle_heading_${level}` as const),
316
+ ...editor.dictionary.slash_menu[
317
+ level === 1
318
+ ? ("toggle_heading" as const)
319
+ : (`toggle_heading_${level}` as const)
320
+ ],
321
+ });
322
+ });
352
323
  }
353
324
 
354
325
  if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
@@ -362,6 +333,7 @@ export function getDefaultSlashMenuItems<
362
333
  props: { level: level },
363
334
  });
364
335
  },
336
+ badge: formatKeyboardShortcut(`Mod-Alt-${level}`),
365
337
  key: `heading_${level}`,
366
338
  ...editor.dictionary.slash_menu[`heading_${level}`],
367
339
  });
@@ -621,12 +621,16 @@ export const TableHandlesExtension = createExtension(({ editor }) => {
621
621
  key: tableHandlesPluginKey,
622
622
  view: (editorView) => {
623
623
  view = new TableHandlesView(editor as any, editorView, (state) => {
624
- store.setState({
625
- ...state,
626
- draggingState: state.draggingState
627
- ? { ...state.draggingState }
624
+ store.setState(
625
+ state.block
626
+ ? {
627
+ ...state,
628
+ draggingState: state.draggingState
629
+ ? { ...state.draggingState }
630
+ : undefined,
631
+ }
628
632
  : undefined,
629
- });
633
+ );
630
634
  });
631
635
  return view;
632
636
  },
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from "./api/blockManipulation/commands/insertBlocks/insertBlocks.js";
2
2
  export * from "./api/blockManipulation/commands/replaceBlocks/replaceBlocks.js";
3
- export * from "./api/blockManipulation/commands/updateBlock/updateBlock.js";
4
3
  export * from "./api/blockManipulation/commands/replaceBlocks/util/fixColumnList.js";
4
+ export * from "./api/blockManipulation/commands/updateBlock/updateBlock.js";
5
5
  export * from "./api/exporters/html/externalHTMLExporter.js";
6
6
  export * from "./api/exporters/html/internalHTMLSerializer.js";
7
7
  export * from "./api/getBlockInfoFromPos.js";
@@ -19,6 +19,7 @@ export * from "./i18n/dictionary.js";
19
19
  export * from "./schema/index.js";
20
20
  export * from "./util/browser.js";
21
21
  export * from "./util/combineByGroup.js";
22
+ export * from "./util/expandToWords.js";
22
23
  export * from "./util/string.js";
23
24
  export * from "./util/table.js";
24
25
  export * from "./util/typescript.js";
@@ -70,6 +70,8 @@ export function getParseRules<
70
70
 
71
71
  return props;
72
72
  },
73
+ // Because we do the parsing ourselves, we want to preserve whitespace for content we've parsed
74
+ preserveWhitespace: true,
73
75
  getContent:
74
76
  config.content === "inline" || config.content === "none"
75
77
  ? (node, schema) => {
@@ -97,6 +99,7 @@ export function getParseRules<
97
99
  const parser = DOMParser.fromSchema(schema);
98
100
  const parsed = parser.parse(clone, {
99
101
  topNode: schema.nodes.paragraph.create(),
102
+ preserveWhitespace: true,
100
103
  });
101
104
 
102
105
  return parsed.content;
@@ -0,0 +1,38 @@
1
+ import type { Node, ResolvedPos } from "prosemirror-model";
2
+
3
+ /**
4
+ * Expands a range (start to end) to include the rest of the word if it starts or ends within a word
5
+ */
6
+ export function expandPMRangeToWords(
7
+ doc: Node,
8
+ range: { $from: ResolvedPos; $to: ResolvedPos },
9
+ ) {
10
+ let { $from, $to } = range;
11
+
12
+ // Expand Start
13
+ // If the selection starts with a word character or punctuation, check if we need to expand left to include the rest of the word
14
+ if ($from.pos > $from.start() && $from.pos < doc.content.size) {
15
+ const charAfterStart = doc.textBetween($from.pos, $from.pos + 1);
16
+ if (/^[\w\p{P}]$/u.test(charAfterStart)) {
17
+ const textBefore = doc.textBetween($from.start(), $from.pos);
18
+ const wordMatch = textBefore.match(/[\w\p{P}]+$/u);
19
+ if (wordMatch) {
20
+ $from = doc.resolve($from.pos - wordMatch[0].length);
21
+ }
22
+ }
23
+ }
24
+
25
+ // Expand End
26
+ // If the selection ends with a word characte or punctuation, check if we need to expand right to include the rest of the word
27
+ if ($to.pos < $to.end() && $to.pos > 0) {
28
+ const charBeforeEnd = doc.textBetween($to.pos - 1, $to.pos);
29
+ if (/^[\w\p{P}]$/u.test(charBeforeEnd)) {
30
+ const textAfter = doc.textBetween($to.pos, $to.end());
31
+ const wordMatch = textAfter.match(/^[\w\p{P}]+/u);
32
+ if (wordMatch) {
33
+ $to = doc.resolve($to.pos + wordMatch[0].length);
34
+ }
35
+ }
36
+ }
37
+ return { $from, $to, from: $from.pos, to: $to.pos };
38
+ }
@@ -4,7 +4,7 @@ import { Selection } from "../../../editor/selectionTypes.js";
4
4
  import { BlockIdentifier, BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema/index.js";
5
5
  export declare function getSelection<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema>(tr: Transaction): Selection<BSchema, I, S> | undefined;
6
6
  export declare function setSelection(tr: Transaction, startBlock: BlockIdentifier, endBlock: BlockIdentifier): void;
7
- export declare function getSelectionCutBlocks(tr: Transaction): {
7
+ export declare function getSelectionCutBlocks(tr: Transaction, expandToWords?: boolean): {
8
8
  blocks: Block<Record<string, import("../../../index.js").BlockConfig<string, import("../../../index.js").PropSchema, "inline" | "none" | "table">>, InlineContentSchema, StyleSchema>[];
9
9
  blockCutAtStart: string | undefined;
10
10
  blockCutAtEnd: string | undefined;