@blocknote/xl-multi-column 0.19.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 (78) hide show
  1. package/LICENSE +661 -0
  2. package/dist/blocknote-xl-multi-column.js +3038 -0
  3. package/dist/blocknote-xl-multi-column.js.map +1 -0
  4. package/dist/blocknote-xl-multi-column.umd.cjs +69 -0
  5. package/dist/blocknote-xl-multi-column.umd.cjs.map +1 -0
  6. package/dist/webpack-stats.json +1 -0
  7. package/package.json +80 -0
  8. package/src/blocks/Columns/index.ts +15 -0
  9. package/src/blocks/schema.ts +43 -0
  10. package/src/extensions/ColumnResize/ColumnResizeExtension.ts +357 -0
  11. package/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts +480 -0
  12. package/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx +105 -0
  13. package/src/i18n/dictionary.ts +27 -0
  14. package/src/i18n/locales/ar.ts +18 -0
  15. package/src/i18n/locales/de.ts +18 -0
  16. package/src/i18n/locales/en.ts +16 -0
  17. package/src/i18n/locales/es.ts +18 -0
  18. package/src/i18n/locales/fr.ts +18 -0
  19. package/src/i18n/locales/hr.ts +18 -0
  20. package/src/i18n/locales/index.ts +15 -0
  21. package/src/i18n/locales/is.ts +18 -0
  22. package/src/i18n/locales/ja.ts +18 -0
  23. package/src/i18n/locales/ko.ts +18 -0
  24. package/src/i18n/locales/nl.ts +18 -0
  25. package/src/i18n/locales/pl.ts +18 -0
  26. package/src/i18n/locales/pt.ts +18 -0
  27. package/src/i18n/locales/ru.ts +18 -0
  28. package/src/i18n/locales/vi.ts +18 -0
  29. package/src/i18n/locales/zh.ts +18 -0
  30. package/src/index.ts +7 -0
  31. package/src/pm-nodes/Column.ts +87 -0
  32. package/src/pm-nodes/ColumnList.ts +44 -0
  33. package/src/test/commands/__snapshots__/insertBlocks.test.ts.snap +757 -0
  34. package/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap +169 -0
  35. package/src/test/commands/__snapshots__/updateBlock.test.ts.snap +964 -0
  36. package/src/test/commands/insertBlocks.test.ts +206 -0
  37. package/src/test/commands/textCursorPosition.test.ts +19 -0
  38. package/src/test/commands/updateBlock.test.ts +212 -0
  39. package/src/test/conversions/__snapshots__/multi-column/undefined/external.html +1 -0
  40. package/src/test/conversions/__snapshots__/multi-column/undefined/internal.html +1 -0
  41. package/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap +118 -0
  42. package/src/test/conversions/htmlConversion.test.ts +100 -0
  43. package/src/test/conversions/nodeConversion.test.ts +84 -0
  44. package/src/test/conversions/testCases.ts +54 -0
  45. package/src/test/setupTestEnv.ts +99 -0
  46. package/src/vite-env.d.ts +1 -0
  47. package/types/src/blocks/Columns/index.d.ts +32 -0
  48. package/types/src/blocks/schema.d.ts +102 -0
  49. package/types/src/extensions/ColumnResize/ColumnResizeExtension.d.ts +3 -0
  50. package/types/src/extensions/DropCursor/MultiColumnDropCursorPlugin.d.ts +11 -0
  51. package/types/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.d.ts +5 -0
  52. package/types/src/i18n/dictionary.d.ts +19 -0
  53. package/types/src/i18n/locales/ar.d.ts +2 -0
  54. package/types/src/i18n/locales/de.d.ts +2 -0
  55. package/types/src/i18n/locales/en.d.ts +16 -0
  56. package/types/src/i18n/locales/es.d.ts +2 -0
  57. package/types/src/i18n/locales/fr.d.ts +2 -0
  58. package/types/src/i18n/locales/hr.d.ts +2 -0
  59. package/types/src/i18n/locales/index.d.ts +15 -0
  60. package/types/src/i18n/locales/is.d.ts +2 -0
  61. package/types/src/i18n/locales/ja.d.ts +2 -0
  62. package/types/src/i18n/locales/ko.d.ts +2 -0
  63. package/types/src/i18n/locales/nl.d.ts +2 -0
  64. package/types/src/i18n/locales/pl.d.ts +2 -0
  65. package/types/src/i18n/locales/pt.d.ts +2 -0
  66. package/types/src/i18n/locales/ru.d.ts +2 -0
  67. package/types/src/i18n/locales/vi.d.ts +2 -0
  68. package/types/src/i18n/locales/zh.d.ts +2 -0
  69. package/types/src/index.d.ts +7 -0
  70. package/types/src/pm-nodes/Column.d.ts +6 -0
  71. package/types/src/pm-nodes/ColumnList.d.ts +6 -0
  72. package/types/src/test/commands/insertBlocks.test.d.ts +1 -0
  73. package/types/src/test/commands/textCursorPosition.test.d.ts +1 -0
  74. package/types/src/test/commands/updateBlock.test.d.ts +1 -0
  75. package/types/src/test/conversions/htmlConversion.test.d.ts +1 -0
  76. package/types/src/test/conversions/nodeConversion.test.d.ts +1 -0
  77. package/types/src/test/conversions/testCases.d.ts +3 -0
  78. package/types/src/test/setupTestEnv.d.ts +1041 -0
@@ -0,0 +1,480 @@
1
+ import type { BlockNoteEditor } from "@blocknote/core";
2
+ import {
3
+ UniqueID,
4
+ getBlockInfo,
5
+ getNearestBlockContainerPos,
6
+ nodeToBlock,
7
+ } from "@blocknote/core";
8
+ import { EditorState, Plugin } from "prosemirror-state";
9
+ import { dropPoint } from "prosemirror-transform";
10
+ import { EditorView } from "prosemirror-view";
11
+
12
+ const PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP = 0.1;
13
+
14
+ function eventCoords(event: MouseEvent) {
15
+ return { left: event.clientX, top: event.clientY };
16
+ }
17
+
18
+ interface DropCursorOptions {
19
+ /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class.
20
+ color?: string | false;
21
+
22
+ /// The precise width of the cursor in pixels. Defaults to 1.
23
+ width?: number;
24
+
25
+ /// A CSS class name to add to the cursor element.
26
+ class?: string;
27
+ }
28
+
29
+ /// Create a plugin that, when added to a ProseMirror instance,
30
+ /// causes a decoration to show up at the drop position when something
31
+ /// is dragged over the editor.
32
+ ///
33
+ /// Nodes may add a `disableDropCursor` property to their spec to
34
+ /// control the showing of a drop cursor inside them. This may be a
35
+ /// boolean or a function, which will be called with a view and a
36
+ /// position, and should return a boolean.
37
+ export function multiColumnDropCursor(
38
+ options: DropCursorOptions & {
39
+ editor: BlockNoteEditor<any, any, any>;
40
+ }
41
+ ): Plugin {
42
+ const editor = options.editor;
43
+ return new Plugin({
44
+ view(editorView) {
45
+ return new DropCursorView(editorView, options);
46
+ },
47
+ props: {
48
+ handleDrop(view, event, slice, _moved) {
49
+ const eventPos = view.posAtCoords(eventCoords(event));
50
+
51
+ if (!eventPos) {
52
+ throw new Error("Could not get event position");
53
+ }
54
+
55
+ const posInfo = getTargetPosInfo(view.state, eventPos);
56
+ const blockInfo = getBlockInfo(posInfo);
57
+
58
+ const blockElement = view.nodeDOM(posInfo.posBeforeNode);
59
+ const blockRect = (blockElement as HTMLElement).getBoundingClientRect();
60
+ let position: "regular" | "left" | "right" = "regular";
61
+ if (
62
+ event.clientX <=
63
+ blockRect.left +
64
+ blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP
65
+ ) {
66
+ position = "left";
67
+ }
68
+ if (
69
+ event.clientX >=
70
+ blockRect.right -
71
+ blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP
72
+ ) {
73
+ position = "right";
74
+ }
75
+
76
+ if (position === "regular") {
77
+ // handled by default prosemirror drop behaviour
78
+ return false;
79
+ }
80
+
81
+ const draggedBlock = nodeToBlock(
82
+ slice.content.child(0),
83
+ editor.schema.blockSchema,
84
+ editor.schema.inlineContentSchema,
85
+ editor.schema.styleSchema
86
+ // TODO: cache?
87
+ );
88
+
89
+ // const block = blockInfo.block(editor);
90
+ if (blockInfo.blockNoteType === "column") {
91
+ // insert new column in existing columnList
92
+ const parentBlock = view.state.doc
93
+ .resolve(blockInfo.bnBlock.beforePos)
94
+ .node();
95
+
96
+ const columnList = nodeToBlock(
97
+ parentBlock,
98
+ editor.schema.blockSchema,
99
+ editor.schema.inlineContentSchema,
100
+ editor.schema.styleSchema
101
+ );
102
+
103
+ // In a `columnList`, we expect that the average width of each column
104
+ // is 1. However, there are cases in which this stops being true. For
105
+ // example, having one wider column and then removing it will cause
106
+ // the average width to go down. This isn't really an issue until the
107
+ // user tries to add a new column, which will, in this case, be wider
108
+ // than expected. Therefore, we normalize the column widths to an
109
+ // average of 1 here to avoid this issue.
110
+ let sumColumnWidthPercent = 0;
111
+ columnList.children.forEach((column) => {
112
+ sumColumnWidthPercent += column.props.width as number;
113
+ });
114
+ const avgColumnWidthPercent =
115
+ sumColumnWidthPercent / columnList.children.length;
116
+
117
+ // If the average column width is not 1, normalize it. We're dealing
118
+ // with floats so we need a small margin to account for precision
119
+ // errors.
120
+ if (avgColumnWidthPercent < 0.99 || avgColumnWidthPercent > 1.01) {
121
+ const scalingFactor = 1 / avgColumnWidthPercent;
122
+
123
+ columnList.children.forEach((column) => {
124
+ column.props.width =
125
+ (column.props.width as number) * scalingFactor;
126
+ });
127
+ }
128
+
129
+ const index = columnList.children.findIndex(
130
+ (b) => b.id === blockInfo.bnBlock.node.attrs.id
131
+ );
132
+
133
+ const newChildren = columnList.children.toSpliced(
134
+ position === "left" ? index : index + 1,
135
+ 0,
136
+ {
137
+ type: "column",
138
+ children: [draggedBlock],
139
+ props: {},
140
+ content: undefined,
141
+ id: UniqueID.options.generateID(),
142
+ }
143
+ );
144
+
145
+ editor.removeBlocks([draggedBlock]);
146
+
147
+ editor.updateBlock(columnList, {
148
+ children: newChildren,
149
+ });
150
+ } else {
151
+ // create new columnList with blocks as columns
152
+ const block = nodeToBlock(
153
+ blockInfo.bnBlock.node,
154
+ editor.schema.blockSchema,
155
+ editor.schema.inlineContentSchema,
156
+ editor.schema.styleSchema
157
+ );
158
+
159
+ const blocks =
160
+ position === "left" ? [draggedBlock, block] : [block, draggedBlock];
161
+ editor.removeBlocks([draggedBlock]);
162
+ editor.replaceBlocks(
163
+ [block],
164
+ [
165
+ {
166
+ type: "columnList",
167
+ children: blocks.map((b) => {
168
+ return {
169
+ type: "column",
170
+ children: [b],
171
+ };
172
+ }),
173
+ },
174
+ ]
175
+ );
176
+ }
177
+
178
+ return true;
179
+ },
180
+ },
181
+ });
182
+ }
183
+
184
+ class DropCursorView {
185
+ width: number;
186
+ color: string | undefined;
187
+ class: string | undefined;
188
+ cursorPos:
189
+ | { pos: number; position: "left" | "right" | "regular" }
190
+ | undefined = undefined;
191
+ element: HTMLElement | null = null;
192
+ timeout: ReturnType<typeof setTimeout> | undefined = undefined;
193
+ handlers: { name: string; handler: (event: Event) => void }[];
194
+
195
+ constructor(readonly editorView: EditorView, options: DropCursorOptions) {
196
+ this.width = options.width ?? 1;
197
+ this.color = options.color === false ? undefined : options.color || "black";
198
+ this.class = options.class;
199
+
200
+ this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => {
201
+ const handler = (e: Event) => {
202
+ (this as any)[name](e);
203
+ };
204
+ editorView.dom.addEventListener(name, handler);
205
+ return { name, handler };
206
+ });
207
+ }
208
+
209
+ destroy() {
210
+ this.handlers.forEach(({ name, handler }) =>
211
+ this.editorView.dom.removeEventListener(name, handler)
212
+ );
213
+ }
214
+
215
+ update(editorView: EditorView, prevState: EditorState) {
216
+ if (this.cursorPos != null && prevState.doc !== editorView.state.doc) {
217
+ if (this.cursorPos.pos > editorView.state.doc.content.size) {
218
+ this.setCursor(undefined);
219
+ } else {
220
+ // update overlay because document has changed
221
+ this.updateOverlay();
222
+ }
223
+ }
224
+ }
225
+
226
+ setCursor(
227
+ cursorPos:
228
+ | { pos: number; position: "left" | "right" | "regular" }
229
+ | undefined
230
+ ) {
231
+ if (
232
+ cursorPos === this.cursorPos ||
233
+ (cursorPos?.pos === this.cursorPos?.pos &&
234
+ cursorPos?.position === this.cursorPos?.position)
235
+ ) {
236
+ // no change
237
+ return;
238
+ }
239
+ this.cursorPos = cursorPos;
240
+ if (!cursorPos) {
241
+ this.element!.parentNode!.removeChild(this.element!);
242
+ this.element = null;
243
+ } else {
244
+ // update overlay because cursor has changed
245
+ this.updateOverlay();
246
+ }
247
+ }
248
+
249
+ updateOverlay() {
250
+ if (!this.cursorPos) {
251
+ throw new Error("updateOverlay called with no cursor position");
252
+ }
253
+ const $pos = this.editorView.state.doc.resolve(this.cursorPos.pos);
254
+ const isBlock = !$pos.parent.inlineContent;
255
+ let rect;
256
+ const editorDOM = this.editorView.dom;
257
+ const editorRect = editorDOM.getBoundingClientRect();
258
+ const scaleX = editorRect.width / editorDOM.offsetWidth;
259
+ const scaleY = editorRect.height / editorDOM.offsetHeight;
260
+ if (isBlock) {
261
+ const before = $pos.nodeBefore;
262
+ const after = $pos.nodeAfter;
263
+ if (before || after) {
264
+ if (
265
+ this.cursorPos.position === "left" ||
266
+ this.cursorPos.position === "right"
267
+ ) {
268
+ const block = this.editorView.nodeDOM(this.cursorPos.pos);
269
+
270
+ const blockRect = (block as HTMLElement).getBoundingClientRect();
271
+ const halfWidth = (this.width / 2) * scaleY;
272
+ const left =
273
+ this.cursorPos.position === "left"
274
+ ? blockRect.left
275
+ : blockRect.right;
276
+ rect = {
277
+ left: left - halfWidth,
278
+ right: left + halfWidth,
279
+ top: blockRect.top,
280
+ bottom: blockRect.bottom,
281
+ // left: blockRect.left,
282
+ // right: blockRect.right,
283
+ };
284
+ } else {
285
+ // regular logic
286
+ const node = this.editorView.nodeDOM(
287
+ this.cursorPos.pos - (before ? before.nodeSize : 0)
288
+ );
289
+ if (node) {
290
+ const nodeRect = (node as HTMLElement).getBoundingClientRect();
291
+
292
+ let top = before ? nodeRect.bottom : nodeRect.top;
293
+ if (before && after) {
294
+ // find the middle between the node above and below
295
+ top =
296
+ (top +
297
+ (
298
+ this.editorView.nodeDOM(this.cursorPos.pos) as HTMLElement
299
+ ).getBoundingClientRect().top) /
300
+ 2;
301
+ }
302
+ // console.log("node");
303
+ const halfWidth = (this.width / 2) * scaleY;
304
+
305
+ if (this.cursorPos.position === "regular") {
306
+ rect = {
307
+ left: nodeRect.left,
308
+ right: nodeRect.right,
309
+ top: top - halfWidth,
310
+ bottom: top + halfWidth,
311
+ };
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ if (!rect) {
319
+ // Cursor is an inline vertical dropcursor
320
+ const coords = this.editorView.coordsAtPos(this.cursorPos.pos);
321
+ const halfWidth = (this.width / 2) * scaleX;
322
+ rect = {
323
+ left: coords.left - halfWidth,
324
+ right: coords.left + halfWidth,
325
+ top: coords.top,
326
+ bottom: coords.bottom,
327
+ };
328
+ }
329
+
330
+ // Code below positions the cursor overlay based on the rect
331
+ const parent = this.editorView.dom.offsetParent as HTMLElement;
332
+ if (!this.element) {
333
+ this.element = parent.appendChild(document.createElement("div"));
334
+ if (this.class) {
335
+ this.element.className = this.class;
336
+ }
337
+ this.element.style.cssText =
338
+ "position: absolute; z-index: 50; pointer-events: none;";
339
+ if (this.color) {
340
+ this.element.style.backgroundColor = this.color;
341
+ }
342
+ }
343
+ this.element.classList.toggle("prosemirror-dropcursor-block", isBlock);
344
+ this.element.classList.toggle(
345
+ "prosemirror-dropcursor-vertical",
346
+ this.cursorPos.position !== "regular"
347
+ );
348
+ this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock);
349
+ let parentLeft, parentTop;
350
+ if (
351
+ !parent ||
352
+ (parent === document.body &&
353
+ getComputedStyle(parent).position === "static")
354
+ ) {
355
+ parentLeft = -window.scrollX;
356
+ parentTop = -window.scrollY;
357
+ } else {
358
+ const rect = parent.getBoundingClientRect();
359
+ const parentScaleX = rect.width / parent.offsetWidth;
360
+ const parentScaleY = rect.height / parent.offsetHeight;
361
+ parentLeft = rect.left - parent.scrollLeft * parentScaleX;
362
+ parentTop = rect.top - parent.scrollTop * parentScaleY;
363
+ }
364
+ this.element.style.left = (rect.left - parentLeft) / scaleX + "px";
365
+ this.element.style.top = (rect.top - parentTop) / scaleY + "px";
366
+ this.element.style.width = (rect.right - rect.left) / scaleX + "px";
367
+ this.element.style.height = (rect.bottom - rect.top) / scaleY + "px";
368
+ }
369
+
370
+ scheduleRemoval(timeout: number) {
371
+ clearTimeout(this.timeout);
372
+ this.timeout = setTimeout(() => this.setCursor(undefined), timeout);
373
+ }
374
+
375
+ // this gets executed on every mouse move when dragging (drag over)
376
+ dragover(event: DragEvent) {
377
+ if (!this.editorView.editable) {
378
+ return;
379
+ }
380
+ const pos = this.editorView.posAtCoords({
381
+ left: event.clientX,
382
+ top: event.clientY,
383
+ });
384
+
385
+ // console.log("posatcoords", pos);
386
+
387
+ const node =
388
+ pos && pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside);
389
+ const disableDropCursor = node && node.type.spec.disableDropCursor;
390
+ const disabled =
391
+ typeof disableDropCursor == "function"
392
+ ? disableDropCursor(this.editorView, pos, event)
393
+ : disableDropCursor;
394
+
395
+ if (pos && !disabled) {
396
+ let position: "regular" | "left" | "right" = "regular";
397
+ let target: number | null = pos.pos;
398
+
399
+ const posInfo = getTargetPosInfo(this.editorView.state, pos);
400
+
401
+ const block = this.editorView.nodeDOM(posInfo.posBeforeNode);
402
+ const blockRect = (block as HTMLElement).getBoundingClientRect();
403
+
404
+ if (
405
+ event.clientX <=
406
+ blockRect.left +
407
+ blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP
408
+ ) {
409
+ position = "left";
410
+ target = posInfo.posBeforeNode;
411
+ }
412
+ if (
413
+ event.clientX >=
414
+ blockRect.right -
415
+ blockRect.width * PERCENTAGE_OF_BLOCK_WIDTH_CONSIDERED_SIDE_DROP
416
+ ) {
417
+ position = "right";
418
+ target = posInfo.posBeforeNode;
419
+ }
420
+
421
+ // "regular logic"
422
+ if (
423
+ position === "regular" &&
424
+ this.editorView.dragging &&
425
+ this.editorView.dragging.slice
426
+ ) {
427
+ const point = dropPoint(
428
+ this.editorView.state.doc,
429
+ target,
430
+ this.editorView.dragging.slice
431
+ );
432
+
433
+ if (point != null) {
434
+ target = point;
435
+ }
436
+ }
437
+ // console.log("target", target);
438
+ this.setCursor({ pos: target, position });
439
+ this.scheduleRemoval(5000);
440
+ }
441
+ }
442
+
443
+ dragend() {
444
+ this.scheduleRemoval(20);
445
+ }
446
+
447
+ drop() {
448
+ this.scheduleRemoval(20);
449
+ }
450
+
451
+ dragleave(event: DragEvent) {
452
+ if (
453
+ event.target === this.editorView.dom ||
454
+ !this.editorView.dom.contains((event as any).relatedTarget)
455
+ ) {
456
+ this.setCursor(undefined);
457
+ }
458
+ }
459
+ }
460
+
461
+ /**
462
+ * From a position inside the document get the block that should be the "drop target" block.
463
+ */
464
+ function getTargetPosInfo(
465
+ state: EditorState,
466
+ eventPos: { pos: number; inside: number }
467
+ ) {
468
+ const blockPos = getNearestBlockContainerPos(state.doc, eventPos.pos);
469
+
470
+ // if we're at a block that's in a column, we want to compare the mouse position to the column, not the block inside it
471
+ // why? because we want to insert a new column in the columnList, instead of a new columnList inside of the column
472
+ let resolved = state.doc.resolve(blockPos.posBeforeNode);
473
+ if (resolved.parent.type.name === "column") {
474
+ resolved = state.doc.resolve(resolved.before());
475
+ }
476
+ return {
477
+ posBeforeNode: resolved.pos,
478
+ node: resolved.nodeAfter!,
479
+ };
480
+ }
@@ -0,0 +1,105 @@
1
+ import {
2
+ BlockNoteEditor,
3
+ BlockSchema,
4
+ InlineContentSchema,
5
+ insertOrUpdateBlock,
6
+ StyleSchema,
7
+ } from "@blocknote/core";
8
+ import { DefaultReactSuggestionItem } from "@blocknote/react";
9
+ import { TbColumns2, TbColumns3 } from "react-icons/tb";
10
+
11
+ import { multiColumnSchema } from "../../blocks/schema.js";
12
+ import { getMultiColumnDictionary } from "../../i18n/dictionary.js";
13
+
14
+ export function checkMultiColumnBlocksInSchema<
15
+ I extends InlineContentSchema,
16
+ S extends StyleSchema
17
+ >(
18
+ editor: BlockNoteEditor<any, I, S>
19
+ ): editor is BlockNoteEditor<typeof multiColumnSchema.blockSchema, I, S> {
20
+ return (
21
+ "column" in editor.schema.blockSchema &&
22
+ editor.schema.blockSchema["columnList"] ===
23
+ multiColumnSchema.blockSchema["columnList"] &&
24
+ "column" in editor.schema.blockSchema &&
25
+ editor.schema.blockSchema["column"] ===
26
+ multiColumnSchema.blockSchema["column"]
27
+ );
28
+ }
29
+
30
+ export function getMultiColumnSlashMenuItems<
31
+ BSchema extends BlockSchema,
32
+ I extends InlineContentSchema,
33
+ S extends StyleSchema
34
+ >(editor: BlockNoteEditor<BSchema, I, S>) {
35
+ const items: Omit<DefaultReactSuggestionItem, "key">[] = [];
36
+
37
+ if (checkMultiColumnBlocksInSchema(editor)) {
38
+ items.push(
39
+ {
40
+ ...getMultiColumnDictionary(editor).slash_menu.two_columns,
41
+ icon: <TbColumns2 size={18} />,
42
+ onItemClick: () => {
43
+ insertOrUpdateBlock(editor, {
44
+ type: "columnList",
45
+ children: [
46
+ {
47
+ type: "column",
48
+ children: [
49
+ {
50
+ type: "paragraph" as any,
51
+ },
52
+ ],
53
+ },
54
+ {
55
+ type: "column",
56
+ children: [
57
+ {
58
+ type: "paragraph" as any,
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ });
64
+ },
65
+ },
66
+ {
67
+ ...getMultiColumnDictionary(editor).slash_menu.three_columns,
68
+ icon: <TbColumns3 size={18} />,
69
+ onItemClick: () => {
70
+ insertOrUpdateBlock(editor, {
71
+ type: "columnList",
72
+ children: [
73
+ {
74
+ type: "column",
75
+ children: [
76
+ {
77
+ type: "paragraph" as any,
78
+ },
79
+ ],
80
+ },
81
+ {
82
+ type: "column",
83
+ children: [
84
+ {
85
+ type: "paragraph" as any,
86
+ },
87
+ ],
88
+ },
89
+ {
90
+ type: "column",
91
+ children: [
92
+ {
93
+ type: "paragraph" as any,
94
+ },
95
+ ],
96
+ },
97
+ ],
98
+ });
99
+ },
100
+ }
101
+ );
102
+ }
103
+
104
+ return items;
105
+ }
@@ -0,0 +1,27 @@
1
+ // function scramble(dict: any) {
2
+ // const newDict: any = {} as any;
3
+
4
+ import type { en } from "./locales/index.js";
5
+ import { BlockNoteEditor } from "@blocknote/core";
6
+
7
+ // for (const key in dict) {
8
+ // if (typeof dict[key] === "object") {
9
+ // newDict[key] = scramble(dict[key]);
10
+ // } else {
11
+ // newDict[key] = dict[key].split("").reverse().join("");
12
+ // }
13
+ // }
14
+
15
+ // return newDict;
16
+ // }
17
+
18
+ export function getMultiColumnDictionary(
19
+ editor: BlockNoteEditor<any, any, any>
20
+ ) {
21
+ if (!(editor.dictionary as any).multi_column) {
22
+ throw new Error("Multi-column dictionary not found");
23
+ }
24
+ return (editor.dictionary as any).multi_column as MultiColumnDictionary;
25
+ }
26
+
27
+ export type MultiColumnDictionary = typeof en;
@@ -0,0 +1,18 @@
1
+ import type { MultiColumnDictionary } from "../dictionary.js";
2
+
3
+ export const ar: MultiColumnDictionary = {
4
+ slash_menu: {
5
+ two_columns: {
6
+ title: "عمودان",
7
+ subtext: "عمودان جنبًا إلى جنب",
8
+ aliases: ["أعمدة", "صف", "تقسيم"],
9
+ group: "الكتل الأساسية",
10
+ },
11
+ three_columns: {
12
+ title: "ثلاثة أعمدة",
13
+ subtext: "ثلاثة أعمدة جنبًا إلى جنب",
14
+ aliases: ["أعمدة", "صف", "تقسيم"],
15
+ group: "الكتل الأساسية",
16
+ },
17
+ },
18
+ };
@@ -0,0 +1,18 @@
1
+ import type { MultiColumnDictionary } from "../dictionary.js";
2
+
3
+ export const de: MultiColumnDictionary = {
4
+ slash_menu: {
5
+ two_columns: {
6
+ title: "Zwei Spalten",
7
+ subtext: "Zwei Spalten nebeneinander",
8
+ aliases: ["Spalten", "Reihe", "teilen"],
9
+ group: "Grundlegende blöcke",
10
+ },
11
+ three_columns: {
12
+ title: "Drei Spalten",
13
+ subtext: "Drei Spalten nebeneinander",
14
+ aliases: ["Spalten", "Reihe", "teilen"],
15
+ group: "Grundlegende blöcke",
16
+ },
17
+ },
18
+ };
@@ -0,0 +1,16 @@
1
+ export const en = {
2
+ slash_menu: {
3
+ two_columns: {
4
+ title: "Two Columns",
5
+ subtext: "Two columns side by side",
6
+ aliases: ["columns", "row", "split"],
7
+ group: "Basic blocks",
8
+ },
9
+ three_columns: {
10
+ title: "Three Columns",
11
+ subtext: "Three columns side by side",
12
+ aliases: ["columns", "row", "split"],
13
+ group: "Basic blocks",
14
+ },
15
+ },
16
+ };
@@ -0,0 +1,18 @@
1
+ import type { MultiColumnDictionary } from "../dictionary.js";
2
+
3
+ export const es: MultiColumnDictionary = {
4
+ slash_menu: {
5
+ two_columns: {
6
+ title: "Dos Columnas",
7
+ subtext: "Dos columnas lado a lado",
8
+ aliases: ["columnas", "fila", "dividir"],
9
+ group: "Bloques básicos",
10
+ },
11
+ three_columns: {
12
+ title: "Tres Columnas",
13
+ subtext: "Tres columnas lado a lado",
14
+ aliases: ["columnas", "fila", "dividir"],
15
+ group: "Bloques básicos",
16
+ },
17
+ },
18
+ };
@@ -0,0 +1,18 @@
1
+ import type { MultiColumnDictionary } from "../dictionary.js";
2
+
3
+ export const fr: MultiColumnDictionary = {
4
+ slash_menu: {
5
+ two_columns: {
6
+ title: "Deux Colonnes",
7
+ subtext: "Deux colonnes côte à côte",
8
+ aliases: ["colonnes", "rangée", "partager"],
9
+ group: "Blocs de base",
10
+ },
11
+ three_columns: {
12
+ title: "Trois Colonnes",
13
+ subtext: "Trois colonnes côte à côte",
14
+ aliases: ["colonnes", "rangée", "partager"],
15
+ group: "Blocs de base",
16
+ },
17
+ },
18
+ };