@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.
- package/LICENSE +661 -0
- package/dist/blocknote-xl-multi-column.js +3038 -0
- package/dist/blocknote-xl-multi-column.js.map +1 -0
- package/dist/blocknote-xl-multi-column.umd.cjs +69 -0
- package/dist/blocknote-xl-multi-column.umd.cjs.map +1 -0
- package/dist/webpack-stats.json +1 -0
- package/package.json +80 -0
- package/src/blocks/Columns/index.ts +15 -0
- package/src/blocks/schema.ts +43 -0
- package/src/extensions/ColumnResize/ColumnResizeExtension.ts +357 -0
- package/src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts +480 -0
- package/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx +105 -0
- package/src/i18n/dictionary.ts +27 -0
- package/src/i18n/locales/ar.ts +18 -0
- package/src/i18n/locales/de.ts +18 -0
- package/src/i18n/locales/en.ts +16 -0
- package/src/i18n/locales/es.ts +18 -0
- package/src/i18n/locales/fr.ts +18 -0
- package/src/i18n/locales/hr.ts +18 -0
- package/src/i18n/locales/index.ts +15 -0
- package/src/i18n/locales/is.ts +18 -0
- package/src/i18n/locales/ja.ts +18 -0
- package/src/i18n/locales/ko.ts +18 -0
- package/src/i18n/locales/nl.ts +18 -0
- package/src/i18n/locales/pl.ts +18 -0
- package/src/i18n/locales/pt.ts +18 -0
- package/src/i18n/locales/ru.ts +18 -0
- package/src/i18n/locales/vi.ts +18 -0
- package/src/i18n/locales/zh.ts +18 -0
- package/src/index.ts +7 -0
- package/src/pm-nodes/Column.ts +87 -0
- package/src/pm-nodes/ColumnList.ts +44 -0
- package/src/test/commands/__snapshots__/insertBlocks.test.ts.snap +757 -0
- package/src/test/commands/__snapshots__/textCursorPosition.test.ts.snap +169 -0
- package/src/test/commands/__snapshots__/updateBlock.test.ts.snap +964 -0
- package/src/test/commands/insertBlocks.test.ts +206 -0
- package/src/test/commands/textCursorPosition.test.ts +19 -0
- package/src/test/commands/updateBlock.test.ts +212 -0
- package/src/test/conversions/__snapshots__/multi-column/undefined/external.html +1 -0
- package/src/test/conversions/__snapshots__/multi-column/undefined/internal.html +1 -0
- package/src/test/conversions/__snapshots__/nodeConversion.test.ts.snap +118 -0
- package/src/test/conversions/htmlConversion.test.ts +100 -0
- package/src/test/conversions/nodeConversion.test.ts +84 -0
- package/src/test/conversions/testCases.ts +54 -0
- package/src/test/setupTestEnv.ts +99 -0
- package/src/vite-env.d.ts +1 -0
- package/types/src/blocks/Columns/index.d.ts +32 -0
- package/types/src/blocks/schema.d.ts +102 -0
- package/types/src/extensions/ColumnResize/ColumnResizeExtension.d.ts +3 -0
- package/types/src/extensions/DropCursor/MultiColumnDropCursorPlugin.d.ts +11 -0
- package/types/src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.d.ts +5 -0
- package/types/src/i18n/dictionary.d.ts +19 -0
- package/types/src/i18n/locales/ar.d.ts +2 -0
- package/types/src/i18n/locales/de.d.ts +2 -0
- package/types/src/i18n/locales/en.d.ts +16 -0
- package/types/src/i18n/locales/es.d.ts +2 -0
- package/types/src/i18n/locales/fr.d.ts +2 -0
- package/types/src/i18n/locales/hr.d.ts +2 -0
- package/types/src/i18n/locales/index.d.ts +15 -0
- package/types/src/i18n/locales/is.d.ts +2 -0
- package/types/src/i18n/locales/ja.d.ts +2 -0
- package/types/src/i18n/locales/ko.d.ts +2 -0
- package/types/src/i18n/locales/nl.d.ts +2 -0
- package/types/src/i18n/locales/pl.d.ts +2 -0
- package/types/src/i18n/locales/pt.d.ts +2 -0
- package/types/src/i18n/locales/ru.d.ts +2 -0
- package/types/src/i18n/locales/vi.d.ts +2 -0
- package/types/src/i18n/locales/zh.d.ts +2 -0
- package/types/src/index.d.ts +7 -0
- package/types/src/pm-nodes/Column.d.ts +6 -0
- package/types/src/pm-nodes/ColumnList.d.ts +6 -0
- package/types/src/test/commands/insertBlocks.test.d.ts +1 -0
- package/types/src/test/commands/textCursorPosition.test.d.ts +1 -0
- package/types/src/test/commands/updateBlock.test.d.ts +1 -0
- package/types/src/test/conversions/htmlConversion.test.d.ts +1 -0
- package/types/src/test/conversions/nodeConversion.test.d.ts +1 -0
- package/types/src/test/conversions/testCases.d.ts +3 -0
- package/types/src/test/setupTestEnv.d.ts +1041 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"builtAt":1730893824879,"assets":[{"name":"blocknote-xl-multi-column.umd.cjs","size":68564},{"name":"blocknote-xl-multi-column.umd.cjs.map","size":3630227}],"chunks":[{"id":"a1ee98a","entry":true,"initial":true,"files":["blocknote-xl-multi-column.umd.cjs"],"names":["index"]}],"modules":[{"name":"./src/i18n/locales/ar.ts","size":364,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/de.ts","size":400,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/en.ts","size":378,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/es.ts","size":392,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/fr.ts","size":398,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/hr.ts","size":396,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/is.ts","size":376,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/ja.ts","size":284,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/ko.ts","size":288,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/nl.ts","size":390,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/pl.ts","size":394,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/pt.ts","size":388,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/ru.ts","size":370,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/vi.ts","size":340,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/zh.ts","size":274,"chunks":["a1ee98a"]},{"name":"./src/i18n/locales/index.ts","size":0,"chunks":["a1ee98a"]},{"name":"./src/i18n/dictionary.ts","size":189,"chunks":["a1ee98a"]},{"name":"./src/extensions/ColumnResize/ColumnResizeExtension.ts","size":8106,"chunks":["a1ee98a"]},{"name":"./src/pm-nodes/Column.ts","size":2567,"chunks":["a1ee98a"]},{"name":"./src/pm-nodes/ColumnList.ts","size":1120,"chunks":["a1ee98a"]},{"name":"./src/blocks/Columns/index.ts","size":210,"chunks":["a1ee98a"]},{"name":"./src/blocks/schema.ts","size":425,"chunks":["a1ee98a"]},{"name":"./src/extensions/DropCursor/MultiColumnDropCursorPlugin.ts","size":11324,"chunks":["a1ee98a"]},{"name":"./commonjsHelpers.js","size":140,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/jsx-runtime.js?commonjs-module","size":31,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react-jsx-runtime.production.min.js?commonjs-exports","size":40,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/index.js?commonjs-module","size":26,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react.production.min.js?commonjs-exports","size":30,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react.production.min.js","size":7647,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react.development.js?commonjs-module","size":38,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react.development.js","size":92443,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/index.js","size":299,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react-jsx-runtime.production.min.js","size":1192,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react-jsx-runtime.development.js?commonjs-exports","size":37,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/cjs/react-jsx-runtime.development.js","size":43835,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/jsx-runtime.js","size":225,"chunks":["a1ee98a"]},{"name":"../../node_modules/react/index.js?commonjs-es-import","size":100,"chunks":["a1ee98a"]},{"name":"../../node_modules/react-icons/lib/iconContext.mjs","size":217,"chunks":["a1ee98a"]},{"name":"../../node_modules/react-icons/lib/iconBase.mjs","size":3929,"chunks":["a1ee98a"]},{"name":"../../node_modules/react-icons/tb/index.mjs","size":732,"chunks":["a1ee98a"]},{"name":"./src/extensions/SuggestionMenu/getMultiColumnSlashMenuItems.tsx","size":2087,"chunks":["a1ee98a"]},{"name":"./src/index.ts","size":0,"chunks":["a1ee98a"]}]}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blocknote/xl-multi-column",
|
|
3
|
+
"homepage": "https://github.com/TypeCellOS/BlockNote",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "AGPL-3.0 OR PROPRIETARY",
|
|
6
|
+
"version": "0.19.0",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"types",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"react",
|
|
14
|
+
"javascript",
|
|
15
|
+
"editor",
|
|
16
|
+
"typescript",
|
|
17
|
+
"prosemirror",
|
|
18
|
+
"wysiwyg",
|
|
19
|
+
"rich-text-editor",
|
|
20
|
+
"notion",
|
|
21
|
+
"yjs",
|
|
22
|
+
"block-based",
|
|
23
|
+
"tiptap"
|
|
24
|
+
],
|
|
25
|
+
"description": "A \"Notion-style\" block-based extensible text editor built on top of Prosemirror and Tiptap.",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"source": "src/index.ts",
|
|
28
|
+
"types": "./types/src/index.d.ts",
|
|
29
|
+
"main": "./dist/blocknote-xl-multi-column.umd.cjs",
|
|
30
|
+
"module": "./dist/blocknote-xl-multi-column.js",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./types/src/index.d.ts",
|
|
34
|
+
"import": "./dist/blocknote-xl-multi-column.js",
|
|
35
|
+
"require": "./dist/blocknote-xl-multi-column.umd.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"dev": "vite",
|
|
40
|
+
"build": "tsc && vite build",
|
|
41
|
+
"preview": "vite preview",
|
|
42
|
+
"lint": "eslint src --max-warnings 0",
|
|
43
|
+
"test": "vitest",
|
|
44
|
+
"test-watch": "vitest watch",
|
|
45
|
+
"clean": "rimraf dist && rimraf types"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@blocknote/core": "^0.19.0",
|
|
49
|
+
"@blocknote/react": "^0.19.0",
|
|
50
|
+
"@tiptap/core": "^2.7.1",
|
|
51
|
+
"prosemirror-model": "^1.23.0",
|
|
52
|
+
"prosemirror-state": "^1.4.3",
|
|
53
|
+
"prosemirror-tables": "^1.3.7",
|
|
54
|
+
"prosemirror-transform": "^1.9.0",
|
|
55
|
+
"prosemirror-view": "^1.33.7",
|
|
56
|
+
"react-icons": "^5.2.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@vitest/ui": "^2.1.4",
|
|
60
|
+
"eslint": "^8.10.0",
|
|
61
|
+
"jsdom": "^21.1.0",
|
|
62
|
+
"prettier": "^2.7.1",
|
|
63
|
+
"rimraf": "^5.0.5",
|
|
64
|
+
"rollup-plugin-webpack-stats": "^0.2.2",
|
|
65
|
+
"typescript": "^5.3.3",
|
|
66
|
+
"vite": "^5.3.4",
|
|
67
|
+
"vite-plugin-eslint": "^1.8.1",
|
|
68
|
+
"vitest": "^2.0.3"
|
|
69
|
+
},
|
|
70
|
+
"eslintConfig": {
|
|
71
|
+
"extends": [
|
|
72
|
+
"../../.eslintrc.js"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"access": "public",
|
|
77
|
+
"registry": "https://registry.npmjs.org/"
|
|
78
|
+
},
|
|
79
|
+
"gitHead": "c48248efc5ceab7962ed6ba39cf8db0cbf3f0d5c"
|
|
80
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Column } from "../../pm-nodes/Column.js";
|
|
2
|
+
import { ColumnList } from "../../pm-nodes/ColumnList.js";
|
|
3
|
+
|
|
4
|
+
import { createBlockSpecFromStronglyTypedTiptapNode } from "@blocknote/core";
|
|
5
|
+
|
|
6
|
+
export const ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode(Column, {
|
|
7
|
+
width: {
|
|
8
|
+
default: 1,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const ColumnListBlock = createBlockSpecFromStronglyTypedTiptapNode(
|
|
13
|
+
ColumnList,
|
|
14
|
+
{}
|
|
15
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlockNoteSchema,
|
|
3
|
+
BlockSchema,
|
|
4
|
+
InlineContentSchema,
|
|
5
|
+
StyleSchema,
|
|
6
|
+
} from "@blocknote/core";
|
|
7
|
+
import { ColumnBlock, ColumnListBlock } from "./Columns/index.js";
|
|
8
|
+
|
|
9
|
+
export const multiColumnSchema = BlockNoteSchema.create({
|
|
10
|
+
blockSpecs: {
|
|
11
|
+
column: ColumnBlock,
|
|
12
|
+
columnList: ColumnListBlock,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds multi-column support to the given schema.
|
|
18
|
+
*/
|
|
19
|
+
export const withMultiColumn = <
|
|
20
|
+
B extends BlockSchema,
|
|
21
|
+
I extends InlineContentSchema,
|
|
22
|
+
S extends StyleSchema
|
|
23
|
+
>(
|
|
24
|
+
schema: BlockNoteSchema<B, I, S>
|
|
25
|
+
) => {
|
|
26
|
+
return BlockNoteSchema.create({
|
|
27
|
+
blockSpecs: {
|
|
28
|
+
...schema.blockSpecs,
|
|
29
|
+
column: ColumnBlock,
|
|
30
|
+
columnList: ColumnListBlock,
|
|
31
|
+
},
|
|
32
|
+
inlineContentSpecs: schema.inlineContentSpecs,
|
|
33
|
+
styleSpecs: schema.styleSpecs,
|
|
34
|
+
}) as any as BlockNoteSchema<
|
|
35
|
+
// typescript needs some help here
|
|
36
|
+
B & {
|
|
37
|
+
column: typeof ColumnBlock.config;
|
|
38
|
+
columnList: typeof ColumnListBlock.config;
|
|
39
|
+
},
|
|
40
|
+
I,
|
|
41
|
+
S
|
|
42
|
+
>;
|
|
43
|
+
};
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { BlockNoteEditor, getNodeById } from "@blocknote/core";
|
|
2
|
+
import { Extension } from "@tiptap/core";
|
|
3
|
+
import { Node } from "prosemirror-model";
|
|
4
|
+
import { Plugin, PluginKey, PluginView } from "prosemirror-state";
|
|
5
|
+
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
|
6
|
+
|
|
7
|
+
type ColumnData = {
|
|
8
|
+
element: HTMLElement;
|
|
9
|
+
id: string;
|
|
10
|
+
node: Node;
|
|
11
|
+
posBeforeNode: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ColumnDataWithWidths = ColumnData & {
|
|
15
|
+
widthPx: number;
|
|
16
|
+
widthPercent: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ColumnDefaultState = {
|
|
20
|
+
type: "default";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type ColumnHoverState = {
|
|
24
|
+
type: "hover";
|
|
25
|
+
leftColumn: ColumnData;
|
|
26
|
+
rightColumn: ColumnData;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ColumnResizeState = {
|
|
30
|
+
type: "resize";
|
|
31
|
+
startPos: number;
|
|
32
|
+
leftColumn: ColumnDataWithWidths;
|
|
33
|
+
rightColumn: ColumnDataWithWidths;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ColumnState = ColumnDefaultState | ColumnHoverState | ColumnResizeState;
|
|
37
|
+
|
|
38
|
+
const columnResizePluginKey = new PluginKey<ColumnState>("ColumnResizePlugin");
|
|
39
|
+
|
|
40
|
+
class ColumnResizePluginView implements PluginView {
|
|
41
|
+
editor: BlockNoteEditor<any, any, any>;
|
|
42
|
+
view: EditorView;
|
|
43
|
+
|
|
44
|
+
readonly RESIZE_MARGIN_WIDTH_PX = 20;
|
|
45
|
+
readonly COLUMN_MIN_WIDTH_PERCENT = 0.5;
|
|
46
|
+
|
|
47
|
+
constructor(editor: BlockNoteEditor<any, any, any>, view: EditorView) {
|
|
48
|
+
this.editor = editor;
|
|
49
|
+
this.view = view;
|
|
50
|
+
|
|
51
|
+
this.view.dom.addEventListener("mousedown", this.mouseDownHandler);
|
|
52
|
+
document.body.addEventListener("mousemove", this.mouseMoveHandler);
|
|
53
|
+
document.body.addEventListener("mouseup", this.mouseUpHandler);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getColumnHoverOrDefaultState = (
|
|
57
|
+
event: MouseEvent
|
|
58
|
+
): ColumnDefaultState | ColumnHoverState => {
|
|
59
|
+
const target = event.target as HTMLElement;
|
|
60
|
+
|
|
61
|
+
// Do nothing if the event target is outside the editor.
|
|
62
|
+
if (!this.view.dom.contains(target)) {
|
|
63
|
+
return { type: "default" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const columnElement = target.closest(
|
|
67
|
+
".bn-block-column"
|
|
68
|
+
) as HTMLElement | null;
|
|
69
|
+
|
|
70
|
+
// Do nothing if a column element does not exist in the event target's
|
|
71
|
+
// ancestors.
|
|
72
|
+
if (!columnElement) {
|
|
73
|
+
return { type: "default" };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const startPos = event.clientX;
|
|
77
|
+
const columnElementDOMRect = columnElement.getBoundingClientRect();
|
|
78
|
+
|
|
79
|
+
// Whether the cursor is within the width margin to trigger a resize.
|
|
80
|
+
const cursorElementSide =
|
|
81
|
+
startPos < columnElementDOMRect.left + this.RESIZE_MARGIN_WIDTH_PX
|
|
82
|
+
? "left"
|
|
83
|
+
: startPos > columnElementDOMRect.right - this.RESIZE_MARGIN_WIDTH_PX
|
|
84
|
+
? "right"
|
|
85
|
+
: "none";
|
|
86
|
+
|
|
87
|
+
// The column element before or after the one hovered by the cursor,
|
|
88
|
+
// depending on which side the cursor is on.
|
|
89
|
+
const adjacentColumnElement =
|
|
90
|
+
cursorElementSide === "left"
|
|
91
|
+
? columnElement.previousElementSibling
|
|
92
|
+
: cursorElementSide === "right"
|
|
93
|
+
? columnElement.nextElementSibling
|
|
94
|
+
: undefined;
|
|
95
|
+
|
|
96
|
+
// Do nothing if the cursor is not within the resize margin or if there
|
|
97
|
+
// is no column before or after the one hovered by the cursor, depending
|
|
98
|
+
// on which side the cursor is on.
|
|
99
|
+
if (!adjacentColumnElement) {
|
|
100
|
+
return { type: "default" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const leftColumnElement =
|
|
104
|
+
cursorElementSide === "left"
|
|
105
|
+
? (adjacentColumnElement as HTMLElement)
|
|
106
|
+
: columnElement;
|
|
107
|
+
|
|
108
|
+
const rightColumnElement =
|
|
109
|
+
cursorElementSide === "left"
|
|
110
|
+
? columnElement
|
|
111
|
+
: (adjacentColumnElement as HTMLElement);
|
|
112
|
+
|
|
113
|
+
const leftColumnId = leftColumnElement.getAttribute("data-id")!;
|
|
114
|
+
const rightColumnId = rightColumnElement.getAttribute("data-id")!;
|
|
115
|
+
|
|
116
|
+
const leftColumnNodeAndPos = getNodeById(leftColumnId, this.view.state.doc);
|
|
117
|
+
|
|
118
|
+
const rightColumnNodeAndPos = getNodeById(
|
|
119
|
+
rightColumnId,
|
|
120
|
+
this.view.state.doc
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
!leftColumnNodeAndPos ||
|
|
125
|
+
!rightColumnNodeAndPos ||
|
|
126
|
+
!leftColumnNodeAndPos.posBeforeNode
|
|
127
|
+
) {
|
|
128
|
+
throw new Error("Column not found");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
type: "hover",
|
|
133
|
+
leftColumn: {
|
|
134
|
+
element: leftColumnElement,
|
|
135
|
+
id: leftColumnId,
|
|
136
|
+
...leftColumnNodeAndPos,
|
|
137
|
+
},
|
|
138
|
+
rightColumn: {
|
|
139
|
+
element: rightColumnElement,
|
|
140
|
+
id: rightColumnId,
|
|
141
|
+
...rightColumnNodeAndPos,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// When the user mouses down near the boundary between two columns, we
|
|
147
|
+
// want to set the plugin state to resize, so the columns can be resized
|
|
148
|
+
// by moving the mouse.
|
|
149
|
+
mouseDownHandler = (event: MouseEvent) => {
|
|
150
|
+
let newState: ColumnState = this.getColumnHoverOrDefaultState(event);
|
|
151
|
+
if (newState.type === "default") {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
|
|
157
|
+
const startPos = event.clientX;
|
|
158
|
+
|
|
159
|
+
const leftColumnWidthPx =
|
|
160
|
+
newState.leftColumn.element.getBoundingClientRect().width;
|
|
161
|
+
const rightColumnWidthPx =
|
|
162
|
+
newState.rightColumn.element.getBoundingClientRect().width;
|
|
163
|
+
|
|
164
|
+
const leftColumnWidthPercent = newState.leftColumn.node.attrs
|
|
165
|
+
.width as number;
|
|
166
|
+
const rightColumnWidthPercent = newState.rightColumn.node.attrs
|
|
167
|
+
.width as number;
|
|
168
|
+
|
|
169
|
+
newState = {
|
|
170
|
+
type: "resize",
|
|
171
|
+
startPos,
|
|
172
|
+
leftColumn: {
|
|
173
|
+
...newState.leftColumn,
|
|
174
|
+
widthPx: leftColumnWidthPx,
|
|
175
|
+
widthPercent: leftColumnWidthPercent,
|
|
176
|
+
},
|
|
177
|
+
rightColumn: {
|
|
178
|
+
...newState.rightColumn,
|
|
179
|
+
widthPx: rightColumnWidthPx,
|
|
180
|
+
widthPercent: rightColumnWidthPercent,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
this.view.dispatch(
|
|
185
|
+
this.view.state.tr.setMeta(columnResizePluginKey, newState)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
this.editor.sideMenu.freezeMenu();
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// If the plugin isn't in a resize state, we want to update it to either a
|
|
192
|
+
// hover state if the mouse is near the boundary between two columns, or
|
|
193
|
+
// default otherwise. If the plugin is in a resize state, we want to
|
|
194
|
+
// update the column widths based on the horizontal mouse movement.
|
|
195
|
+
mouseMoveHandler = (event: MouseEvent) => {
|
|
196
|
+
const pluginState = columnResizePluginKey.getState(this.view.state);
|
|
197
|
+
if (!pluginState) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If the user isn't currently resizing columns, we want to update the
|
|
202
|
+
// plugin state to maybe show or hide the resize border between columns.
|
|
203
|
+
if (pluginState.type !== "resize") {
|
|
204
|
+
const newState = this.getColumnHoverOrDefaultState(event);
|
|
205
|
+
|
|
206
|
+
// Prevent unnecessary state updates (when the state before and after
|
|
207
|
+
// is the same).
|
|
208
|
+
const bothDefaultStates =
|
|
209
|
+
pluginState.type === "default" && newState.type === "default";
|
|
210
|
+
const sameColumnIds =
|
|
211
|
+
pluginState.type !== "default" &&
|
|
212
|
+
newState.type !== "default" &&
|
|
213
|
+
pluginState.leftColumn.id === newState.leftColumn.id &&
|
|
214
|
+
pluginState.rightColumn.id === newState.rightColumn.id;
|
|
215
|
+
if (bothDefaultStates || sameColumnIds) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Since the resize bar overlaps the side menu, we don't want to show it
|
|
220
|
+
// if the side menu is already open.
|
|
221
|
+
if (newState.type === "hover" && this.editor.sideMenu.view?.state?.show) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Update the plugin state.
|
|
226
|
+
this.view.dispatch(
|
|
227
|
+
this.view.state.tr.setMeta(columnResizePluginKey, newState)
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const widthChangePx = event.clientX - pluginState.startPos;
|
|
234
|
+
// We need to scale the width change by the left column's width in
|
|
235
|
+
// percent, otherwise the rate at which the resizing happens will change
|
|
236
|
+
// based on the width of the left column.
|
|
237
|
+
const scaledWidthChangePx =
|
|
238
|
+
widthChangePx * pluginState.leftColumn.widthPercent;
|
|
239
|
+
const widthChangePercent =
|
|
240
|
+
(pluginState.leftColumn.widthPx + scaledWidthChangePx) /
|
|
241
|
+
pluginState.leftColumn.widthPx -
|
|
242
|
+
1;
|
|
243
|
+
|
|
244
|
+
let newLeftColumnWidth =
|
|
245
|
+
pluginState.leftColumn.widthPercent + widthChangePercent;
|
|
246
|
+
let newRightColumnWidth =
|
|
247
|
+
pluginState.rightColumn.widthPercent - widthChangePercent;
|
|
248
|
+
|
|
249
|
+
// Ensures that the column widths do not go below the minimum width.
|
|
250
|
+
// There is no maximum width, the user can resize the columns as much as
|
|
251
|
+
// they want provided the others don't go below the minimum width.
|
|
252
|
+
if (newLeftColumnWidth < this.COLUMN_MIN_WIDTH_PERCENT) {
|
|
253
|
+
newRightColumnWidth -= this.COLUMN_MIN_WIDTH_PERCENT - newLeftColumnWidth;
|
|
254
|
+
newLeftColumnWidth = this.COLUMN_MIN_WIDTH_PERCENT;
|
|
255
|
+
} else if (newRightColumnWidth < this.COLUMN_MIN_WIDTH_PERCENT) {
|
|
256
|
+
newLeftColumnWidth -= this.COLUMN_MIN_WIDTH_PERCENT - newRightColumnWidth;
|
|
257
|
+
newRightColumnWidth = this.COLUMN_MIN_WIDTH_PERCENT;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// possible improvement: only dispatch on mouse up, and use a different way
|
|
261
|
+
// to update the column widths while dragging.
|
|
262
|
+
// this prevents a lot of document updates
|
|
263
|
+
this.view.dispatch(
|
|
264
|
+
this.view.state.tr
|
|
265
|
+
.setNodeAttribute(
|
|
266
|
+
pluginState.leftColumn.posBeforeNode,
|
|
267
|
+
"width",
|
|
268
|
+
newLeftColumnWidth
|
|
269
|
+
)
|
|
270
|
+
.setNodeAttribute(
|
|
271
|
+
pluginState.rightColumn.posBeforeNode,
|
|
272
|
+
"width",
|
|
273
|
+
newRightColumnWidth
|
|
274
|
+
)
|
|
275
|
+
.setMeta("addToHistory", false)
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// If the plugin is in a resize state, we want to revert it to a default
|
|
280
|
+
// or hover, depending on where the mouse cursor is, when the user
|
|
281
|
+
// releases the mouse button.
|
|
282
|
+
mouseUpHandler = (event: MouseEvent) => {
|
|
283
|
+
const pluginState = columnResizePluginKey.getState(this.view.state);
|
|
284
|
+
if (!pluginState || pluginState.type !== "resize") {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const newState = this.getColumnHoverOrDefaultState(event);
|
|
289
|
+
|
|
290
|
+
// Revert plugin state to default or hover, depending on where the mouse
|
|
291
|
+
// cursor is.
|
|
292
|
+
this.view.dispatch(
|
|
293
|
+
this.view.state.tr.setMeta(columnResizePluginKey, newState)
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
this.editor.sideMenu.unfreezeMenu();
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// This is a required method for PluginView, so we get a type error if we
|
|
300
|
+
// don't implement it.
|
|
301
|
+
update: undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const createColumnResizePlugin = (editor: BlockNoteEditor<any, any, any>) =>
|
|
305
|
+
new Plugin({
|
|
306
|
+
key: columnResizePluginKey,
|
|
307
|
+
props: {
|
|
308
|
+
// This adds a border between the columns when the user is
|
|
309
|
+
// resizing them or when the cursor is near their boundary.
|
|
310
|
+
decorations: (state) => {
|
|
311
|
+
const pluginState = columnResizePluginKey.getState(state);
|
|
312
|
+
if (!pluginState || pluginState.type === "default") {
|
|
313
|
+
return DecorationSet.empty;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return DecorationSet.create(state.doc, [
|
|
317
|
+
Decoration.node(
|
|
318
|
+
pluginState.leftColumn.posBeforeNode,
|
|
319
|
+
pluginState.leftColumn.posBeforeNode +
|
|
320
|
+
pluginState.leftColumn.node.nodeSize,
|
|
321
|
+
{
|
|
322
|
+
style: "box-shadow: 4px 0 0 #ccc; cursor: col-resize",
|
|
323
|
+
}
|
|
324
|
+
),
|
|
325
|
+
Decoration.node(
|
|
326
|
+
pluginState.rightColumn.posBeforeNode,
|
|
327
|
+
pluginState.rightColumn.posBeforeNode +
|
|
328
|
+
pluginState.rightColumn.node.nodeSize,
|
|
329
|
+
{
|
|
330
|
+
style: "cursor: col-resize",
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
]);
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
state: {
|
|
337
|
+
init: () => ({ type: "default" } as ColumnState),
|
|
338
|
+
apply: (tr, oldPluginState) => {
|
|
339
|
+
const newPluginState = tr.getMeta(columnResizePluginKey) as
|
|
340
|
+
| ColumnState
|
|
341
|
+
| undefined;
|
|
342
|
+
|
|
343
|
+
return newPluginState === undefined ? oldPluginState : newPluginState;
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
view: (view) => new ColumnResizePluginView(editor, view),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
export const createColumnResizeExtension = (
|
|
350
|
+
editor: BlockNoteEditor<any, any, any>
|
|
351
|
+
) =>
|
|
352
|
+
Extension.create({
|
|
353
|
+
name: "columnResize",
|
|
354
|
+
addProseMirrorPlugins() {
|
|
355
|
+
return [createColumnResizePlugin(editor)];
|
|
356
|
+
},
|
|
357
|
+
});
|