@blocknote/core 0.9.2 → 0.9.4
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/dist/blocknote.js +1562 -1240
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +5 -5
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +44 -12
- package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +21 -21
- package/src/api/blockManipulation/blockManipulation.test.ts +8 -11
- package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +3 -3
- package/src/api/formatConversions/formatConversions.test.ts +5 -5
- package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +3 -3
- package/src/api/nodeConversions/nodeConversions.test.ts +10 -4
- package/src/api/nodeConversions/nodeConversions.ts +9 -7
- package/src/api/nodeConversions/testUtil.ts +3 -3
- package/src/editor.module.css +1 -1
- package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +5 -3
- package/src/extensions/BackgroundColor/BackgroundColorMark.ts +2 -1
- package/src/extensions/Blocks/NonEditableBlockPlugin.ts +17 -0
- package/src/extensions/Blocks/api/block.ts +29 -16
- package/src/extensions/Blocks/api/blockTypes.ts +79 -27
- package/src/extensions/Blocks/api/defaultBlocks.ts +13 -41
- package/src/extensions/Blocks/api/defaultProps.ts +16 -0
- package/src/extensions/Blocks/nodes/Block.module.css +78 -24
- package/src/extensions/Blocks/nodes/BlockContainer.ts +45 -45
- package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +61 -13
- package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.ts +305 -0
- package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +13 -0
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +26 -2
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +146 -118
- package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +12 -2
- package/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +239 -0
- package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +47 -6
- package/src/extensions/TextColor/TextColorExtension.ts +4 -3
- package/src/extensions/TextColor/TextColorMark.ts +2 -1
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +13 -1
- package/src/index.ts +4 -0
- package/types/src/BlockNoteEditor.d.ts +9 -0
- package/types/src/BlockNoteExtensions.d.ts +1 -1
- package/types/src/extensions/Blocks/NonEditableBlockPlugin.d.ts +2 -0
- package/types/src/extensions/Blocks/api/block.d.ts +7 -8
- package/types/src/extensions/Blocks/api/blockTypes.d.ts +29 -20
- package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +55 -51
- package/types/src/extensions/Blocks/api/defaultProps.d.ts +14 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +43 -9
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/Image.d.ts +6 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.d.ts +37 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +35 -9
- package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +35 -9
- package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +36 -1
- package/types/src/extensions/ImageToolbar/ImageToolbarPlugin.d.ts +36 -0
- package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -1
- package/types/src/index.d.ts +4 -0
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +1 -1
- package/types/src/extensions/Blocks/nodes/TableCell.d.ts +0 -5
- package/types/src/extensions/Blocks/nodes/TableRow.d.ts +0 -5
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { InputRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { defaultProps } from "../../../api/defaultProps";
|
|
2
3
|
import { createTipTapBlock } from "../../../api/block";
|
|
3
|
-
import
|
|
4
|
+
import { BlockSpec, PropSchema } from "../../../api/blockTypes";
|
|
4
5
|
import { mergeCSSClasses } from "../../../../../shared/utils";
|
|
6
|
+
import styles from "../../Block.module.css";
|
|
7
|
+
|
|
8
|
+
export const headingPropSchema = {
|
|
9
|
+
...defaultProps,
|
|
10
|
+
level: { default: 1, values: [1, 2, 3] as const },
|
|
11
|
+
} satisfies PropSchema;
|
|
5
12
|
|
|
6
|
-
|
|
13
|
+
const HeadingBlockContent = createTipTapBlock<"heading", true>({
|
|
7
14
|
name: "heading",
|
|
8
15
|
content: "inline*",
|
|
9
16
|
|
|
10
17
|
addAttributes() {
|
|
11
18
|
return {
|
|
12
19
|
level: {
|
|
13
|
-
default:
|
|
20
|
+
default: 1,
|
|
14
21
|
// instead of "level" attributes, use "data-level"
|
|
15
|
-
parseHTML: (element) => element.getAttribute("data-level")
|
|
22
|
+
parseHTML: (element) => element.getAttribute("data-level")!,
|
|
16
23
|
renderHTML: (attributes) => {
|
|
17
24
|
return {
|
|
18
|
-
"data-level": attributes.level,
|
|
25
|
+
"data-level": (attributes.level as number).toString(),
|
|
19
26
|
};
|
|
20
27
|
},
|
|
21
28
|
},
|
|
@@ -24,16 +31,18 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
24
31
|
|
|
25
32
|
addInputRules() {
|
|
26
33
|
return [
|
|
27
|
-
...[
|
|
34
|
+
...[1, 2, 3].map((level) => {
|
|
28
35
|
// Creates a heading of appropriate level when starting with "#", "##", or "###".
|
|
29
36
|
return new InputRule({
|
|
30
|
-
find: new RegExp(`^(#{${
|
|
37
|
+
find: new RegExp(`^(#{${level}})\\s$`),
|
|
31
38
|
handler: ({ state, chain, range }) => {
|
|
32
39
|
chain()
|
|
33
|
-
.BNUpdateBlock
|
|
40
|
+
.BNUpdateBlock<{
|
|
41
|
+
heading: BlockSpec<"heading", typeof headingPropSchema, true>;
|
|
42
|
+
}>(state.selection.from, {
|
|
34
43
|
type: "heading",
|
|
35
44
|
props: {
|
|
36
|
-
level: level as
|
|
45
|
+
level: level as 1 | 2 | 3,
|
|
37
46
|
},
|
|
38
47
|
})
|
|
39
48
|
// Removes the "#" character(s) used to set the heading.
|
|
@@ -44,21 +53,53 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
44
53
|
];
|
|
45
54
|
},
|
|
46
55
|
|
|
56
|
+
addKeyboardShortcuts() {
|
|
57
|
+
return {
|
|
58
|
+
"Mod-Alt-1": () =>
|
|
59
|
+
this.editor.commands.BNUpdateBlock<{
|
|
60
|
+
heading: BlockSpec<"heading", typeof headingPropSchema, true>;
|
|
61
|
+
}>(this.editor.state.selection.anchor, {
|
|
62
|
+
type: "heading",
|
|
63
|
+
props: {
|
|
64
|
+
level: 1,
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
"Mod-Alt-2": () =>
|
|
68
|
+
this.editor.commands.BNUpdateBlock<{
|
|
69
|
+
heading: BlockSpec<"heading", typeof headingPropSchema, true>;
|
|
70
|
+
}>(this.editor.state.selection.anchor, {
|
|
71
|
+
type: "heading",
|
|
72
|
+
props: {
|
|
73
|
+
level: 2,
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
"Mod-Alt-3": () =>
|
|
77
|
+
this.editor.commands.BNUpdateBlock<{
|
|
78
|
+
heading: BlockSpec<"heading", typeof headingPropSchema, true>;
|
|
79
|
+
}>(this.editor.state.selection.anchor, {
|
|
80
|
+
type: "heading",
|
|
81
|
+
props: {
|
|
82
|
+
level: 3,
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
|
|
47
88
|
parseHTML() {
|
|
48
89
|
return [
|
|
49
90
|
{
|
|
50
91
|
tag: "h1",
|
|
51
|
-
attrs: { level:
|
|
92
|
+
attrs: { level: 1 },
|
|
52
93
|
node: "heading",
|
|
53
94
|
},
|
|
54
95
|
{
|
|
55
96
|
tag: "h2",
|
|
56
|
-
attrs: { level:
|
|
97
|
+
attrs: { level: 2 },
|
|
57
98
|
node: "heading",
|
|
58
99
|
},
|
|
59
100
|
{
|
|
60
101
|
tag: "h3",
|
|
61
|
-
attrs: { level:
|
|
102
|
+
attrs: { level: 3 },
|
|
62
103
|
node: "heading",
|
|
63
104
|
},
|
|
64
105
|
];
|
|
@@ -73,6 +114,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
73
114
|
return [
|
|
74
115
|
"div",
|
|
75
116
|
mergeAttributes(HTMLAttributes, {
|
|
117
|
+
...blockContentDOMAttributes,
|
|
76
118
|
class: mergeCSSClasses(
|
|
77
119
|
styles.blockContent,
|
|
78
120
|
blockContentDOMAttributes.class
|
|
@@ -80,8 +122,9 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
80
122
|
"data-content-type": this.name,
|
|
81
123
|
}),
|
|
82
124
|
[
|
|
83
|
-
|
|
125
|
+
`h${node.attrs.level}`,
|
|
84
126
|
{
|
|
127
|
+
...inlineContentDOMAttributes,
|
|
85
128
|
class: mergeCSSClasses(
|
|
86
129
|
styles.inlineContent,
|
|
87
130
|
inlineContentDOMAttributes.class
|
|
@@ -92,3 +135,8 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
92
135
|
];
|
|
93
136
|
},
|
|
94
137
|
});
|
|
138
|
+
|
|
139
|
+
export const Heading = {
|
|
140
|
+
node: HeadingBlockContent,
|
|
141
|
+
propSchema: headingPropSchema,
|
|
142
|
+
} satisfies BlockSpec<"heading", typeof headingPropSchema, true>;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { createBlockSpec } from "../../../api/block";
|
|
2
|
+
import { defaultProps } from "../../../api/defaultProps";
|
|
3
|
+
import { BlockSpec, PropSchema, SpecificBlock } from "../../../api/blockTypes";
|
|
4
|
+
import { BlockNoteEditor } from "../../../../../BlockNoteEditor";
|
|
5
|
+
import { imageToolbarPluginKey } from "../../../../ImageToolbar/ImageToolbarPlugin";
|
|
6
|
+
import styles from "../../Block.module.css";
|
|
7
|
+
|
|
8
|
+
export const imagePropSchema = {
|
|
9
|
+
textAlignment: defaultProps.textAlignment,
|
|
10
|
+
backgroundColor: defaultProps.backgroundColor,
|
|
11
|
+
// Image url.
|
|
12
|
+
url: {
|
|
13
|
+
default: "" as const,
|
|
14
|
+
},
|
|
15
|
+
// Image caption.
|
|
16
|
+
caption: {
|
|
17
|
+
default: "" as const,
|
|
18
|
+
},
|
|
19
|
+
// Image width in px.
|
|
20
|
+
width: {
|
|
21
|
+
default: 512 as const,
|
|
22
|
+
},
|
|
23
|
+
} satisfies PropSchema;
|
|
24
|
+
|
|
25
|
+
// Converts text alignment prop values to the flexbox `align-items` values.
|
|
26
|
+
const textAlignmentToAlignItems = (
|
|
27
|
+
textAlignment: "left" | "center" | "right" | "justify"
|
|
28
|
+
): "flex-start" | "center" | "flex-end" => {
|
|
29
|
+
switch (textAlignment) {
|
|
30
|
+
case "left":
|
|
31
|
+
return "flex-start";
|
|
32
|
+
case "center":
|
|
33
|
+
return "center";
|
|
34
|
+
case "right":
|
|
35
|
+
return "flex-end";
|
|
36
|
+
default:
|
|
37
|
+
return "flex-start";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Min image width in px.
|
|
42
|
+
const minWidth = 64;
|
|
43
|
+
|
|
44
|
+
const renderImage = (
|
|
45
|
+
block: SpecificBlock<
|
|
46
|
+
{ image: BlockSpec<"image", typeof imagePropSchema, false> },
|
|
47
|
+
"image"
|
|
48
|
+
>,
|
|
49
|
+
editor: BlockNoteEditor<{
|
|
50
|
+
image: BlockSpec<"image", typeof imagePropSchema, false>;
|
|
51
|
+
}>
|
|
52
|
+
) => {
|
|
53
|
+
// Wrapper element to set the image alignment, contains both image/image
|
|
54
|
+
// upload dashboard and caption.
|
|
55
|
+
const wrapper = document.createElement("div");
|
|
56
|
+
wrapper.className = styles.wrapper;
|
|
57
|
+
wrapper.style.alignItems = textAlignmentToAlignItems(
|
|
58
|
+
block.props.textAlignment
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Button element that acts as a placeholder for images with no src.
|
|
62
|
+
const addImageButton = document.createElement("div");
|
|
63
|
+
addImageButton.className = styles.addImageButton;
|
|
64
|
+
addImageButton.style.display = block.props.url === "" ? "" : "none";
|
|
65
|
+
|
|
66
|
+
// Icon for the add image button.
|
|
67
|
+
const addImageButtonIcon = document.createElement("div");
|
|
68
|
+
addImageButtonIcon.className = styles.addImageButtonIcon;
|
|
69
|
+
|
|
70
|
+
// Text for the add image button.
|
|
71
|
+
const addImageButtonText = document.createElement("p");
|
|
72
|
+
addImageButtonText.className = styles.addImageButtonText;
|
|
73
|
+
addImageButtonText.innerText = "Add Image";
|
|
74
|
+
|
|
75
|
+
// Wrapper element for the image, resize handles and caption.
|
|
76
|
+
const imageAndCaptionWrapper = document.createElement("div");
|
|
77
|
+
imageAndCaptionWrapper.className = styles.imageAndCaptionWrapper;
|
|
78
|
+
imageAndCaptionWrapper.style.display = block.props.url !== "" ? "" : "none";
|
|
79
|
+
|
|
80
|
+
// Wrapper element for the image and resize handles.
|
|
81
|
+
const imageWrapper = document.createElement("div");
|
|
82
|
+
imageWrapper.className = styles.imageWrapper;
|
|
83
|
+
imageWrapper.style.display = block.props.url !== "" ? "" : "none";
|
|
84
|
+
|
|
85
|
+
// Image element.
|
|
86
|
+
const image = document.createElement("img");
|
|
87
|
+
image.className = styles.image;
|
|
88
|
+
image.src = block.props.url;
|
|
89
|
+
image.alt = "placeholder";
|
|
90
|
+
image.contentEditable = "false";
|
|
91
|
+
image.draggable = false;
|
|
92
|
+
image.style.width = `${Math.min(
|
|
93
|
+
block.props.width,
|
|
94
|
+
editor.domElement.firstElementChild!.clientWidth
|
|
95
|
+
)}px`;
|
|
96
|
+
|
|
97
|
+
// Resize handle elements.
|
|
98
|
+
const leftResizeHandle = document.createElement("div");
|
|
99
|
+
leftResizeHandle.className = styles.resizeHandle;
|
|
100
|
+
leftResizeHandle.style.left = "4px";
|
|
101
|
+
const rightResizeHandle = document.createElement("div");
|
|
102
|
+
rightResizeHandle.className = styles.resizeHandle;
|
|
103
|
+
rightResizeHandle.style.right = "4px";
|
|
104
|
+
|
|
105
|
+
// Caption element.
|
|
106
|
+
const caption = document.createElement("p");
|
|
107
|
+
caption.className = styles.caption;
|
|
108
|
+
caption.innerText = block.props.caption;
|
|
109
|
+
caption.style.padding = block.props.caption ? "4px" : "";
|
|
110
|
+
|
|
111
|
+
// Adds a light blue outline to selected image blocks.
|
|
112
|
+
const handleEditorUpdate = () => {
|
|
113
|
+
const selection = editor.getSelection()?.blocks || [];
|
|
114
|
+
const currentBlock = editor.getTextCursorPosition().block;
|
|
115
|
+
|
|
116
|
+
const isSelected =
|
|
117
|
+
[currentBlock, ...selection].find(
|
|
118
|
+
(selectedBlock) => selectedBlock.id === block.id
|
|
119
|
+
) !== undefined;
|
|
120
|
+
|
|
121
|
+
if (isSelected) {
|
|
122
|
+
addImageButton.style.outline = "4px solid rgb(100, 160, 255)";
|
|
123
|
+
imageAndCaptionWrapper.style.outline = "4px solid rgb(100, 160, 255)";
|
|
124
|
+
} else {
|
|
125
|
+
addImageButton.style.outline = "";
|
|
126
|
+
imageAndCaptionWrapper.style.outline = "";
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
editor.onEditorContentChange(handleEditorUpdate);
|
|
130
|
+
editor.onEditorSelectionChange(handleEditorUpdate);
|
|
131
|
+
|
|
132
|
+
// Temporary parameters set when the user begins resizing the image, used to
|
|
133
|
+
// calculate the new width of the image.
|
|
134
|
+
let resizeParams:
|
|
135
|
+
| {
|
|
136
|
+
handleUsed: "left" | "right";
|
|
137
|
+
initialWidth: number;
|
|
138
|
+
initialClientX: number;
|
|
139
|
+
}
|
|
140
|
+
| undefined;
|
|
141
|
+
|
|
142
|
+
// Updates the image width with an updated width depending on the cursor X
|
|
143
|
+
// offset from when the resize began, and which resize handle is being used.
|
|
144
|
+
const windowMouseMoveHandler = (event: MouseEvent) => {
|
|
145
|
+
if (!resizeParams) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let newWidth: number;
|
|
150
|
+
|
|
151
|
+
if (textAlignmentToAlignItems(block.props.textAlignment) === "center") {
|
|
152
|
+
if (resizeParams.handleUsed === "left") {
|
|
153
|
+
newWidth =
|
|
154
|
+
resizeParams.initialWidth +
|
|
155
|
+
(resizeParams.initialClientX - event.clientX) * 2;
|
|
156
|
+
} else {
|
|
157
|
+
newWidth =
|
|
158
|
+
resizeParams.initialWidth +
|
|
159
|
+
(event.clientX - resizeParams.initialClientX) * 2;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
if (resizeParams.handleUsed === "left") {
|
|
163
|
+
newWidth =
|
|
164
|
+
resizeParams.initialWidth +
|
|
165
|
+
resizeParams.initialClientX -
|
|
166
|
+
event.clientX;
|
|
167
|
+
} else {
|
|
168
|
+
newWidth =
|
|
169
|
+
resizeParams.initialWidth +
|
|
170
|
+
event.clientX -
|
|
171
|
+
resizeParams.initialClientX;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Ensures the image is not wider than the editor and not smaller than a
|
|
176
|
+
// predetermined minimum width.
|
|
177
|
+
if (newWidth < minWidth) {
|
|
178
|
+
image.style.width = `${minWidth}px`;
|
|
179
|
+
} else if (newWidth > editor.domElement.firstElementChild!.clientWidth) {
|
|
180
|
+
image.style.width = `${
|
|
181
|
+
editor.domElement.firstElementChild!.clientWidth
|
|
182
|
+
}px`;
|
|
183
|
+
} else {
|
|
184
|
+
image.style.width = `${newWidth}px`;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
// Stops mouse movements from resizing the image and updates the block's
|
|
188
|
+
// `width` prop to the new value.
|
|
189
|
+
const windowMouseUpHandler = (event: MouseEvent) => {
|
|
190
|
+
if (!resizeParams) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Hides the drag handles if the cursor is no longer over the image.
|
|
195
|
+
if (
|
|
196
|
+
(!event.target || !imageWrapper.contains(event.target as Node)) &&
|
|
197
|
+
imageWrapper.contains(leftResizeHandle) &&
|
|
198
|
+
imageWrapper.contains(rightResizeHandle)
|
|
199
|
+
) {
|
|
200
|
+
leftResizeHandle.style.display = "none";
|
|
201
|
+
rightResizeHandle.style.display = "none";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
resizeParams = undefined;
|
|
205
|
+
|
|
206
|
+
editor.updateBlock(block, {
|
|
207
|
+
type: "image",
|
|
208
|
+
props: {
|
|
209
|
+
// Removes "px" from the end of the width string and converts to float.
|
|
210
|
+
width: parseFloat(image.style.width.slice(0, -2)),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Prevents focus from moving to the button.
|
|
216
|
+
const addImageButtonMouseDownHandler = (event: MouseEvent) => {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
};
|
|
219
|
+
// Opens the image toolbar.
|
|
220
|
+
const addImageButtonClickHandler = () => {
|
|
221
|
+
editor._tiptapEditor.view.dispatch(
|
|
222
|
+
editor._tiptapEditor.state.tr.setMeta(imageToolbarPluginKey, {
|
|
223
|
+
block: block,
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Sets the resize params, allowing the user to begin resizing the image by
|
|
229
|
+
// moving the cursor left or right.
|
|
230
|
+
const leftResizeHandleMouseDownHandler = (event: MouseEvent) => {
|
|
231
|
+
event.preventDefault();
|
|
232
|
+
|
|
233
|
+
leftResizeHandle.style.display = "block";
|
|
234
|
+
rightResizeHandle.style.display = "block";
|
|
235
|
+
|
|
236
|
+
resizeParams = {
|
|
237
|
+
handleUsed: "left",
|
|
238
|
+
initialWidth: block.props.width,
|
|
239
|
+
initialClientX: event.clientX,
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
const rightResizeHandleMouseDownHandler = (event: MouseEvent) => {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
|
|
245
|
+
leftResizeHandle.style.display = "block";
|
|
246
|
+
rightResizeHandle.style.display = "block";
|
|
247
|
+
|
|
248
|
+
resizeParams = {
|
|
249
|
+
handleUsed: "right",
|
|
250
|
+
initialWidth: block.props.width,
|
|
251
|
+
initialClientX: event.clientX,
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
wrapper.appendChild(addImageButton);
|
|
256
|
+
addImageButton.appendChild(addImageButtonIcon);
|
|
257
|
+
addImageButton.appendChild(addImageButtonText);
|
|
258
|
+
wrapper.appendChild(imageAndCaptionWrapper);
|
|
259
|
+
imageAndCaptionWrapper.appendChild(imageWrapper);
|
|
260
|
+
imageWrapper.appendChild(image);
|
|
261
|
+
imageWrapper.appendChild(leftResizeHandle);
|
|
262
|
+
imageWrapper.appendChild(rightResizeHandle);
|
|
263
|
+
imageAndCaptionWrapper.appendChild(caption);
|
|
264
|
+
|
|
265
|
+
window.addEventListener("mousemove", windowMouseMoveHandler);
|
|
266
|
+
window.addEventListener("mouseup", windowMouseUpHandler);
|
|
267
|
+
addImageButton.addEventListener("mousedown", addImageButtonMouseDownHandler);
|
|
268
|
+
addImageButton.addEventListener("click", addImageButtonClickHandler);
|
|
269
|
+
leftResizeHandle.addEventListener(
|
|
270
|
+
"mousedown",
|
|
271
|
+
leftResizeHandleMouseDownHandler
|
|
272
|
+
);
|
|
273
|
+
rightResizeHandle.addEventListener(
|
|
274
|
+
"mousedown",
|
|
275
|
+
rightResizeHandleMouseDownHandler
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
dom: wrapper,
|
|
280
|
+
destroy: () => {
|
|
281
|
+
window.removeEventListener("mousemove", windowMouseMoveHandler);
|
|
282
|
+
window.removeEventListener("mouseup", windowMouseUpHandler);
|
|
283
|
+
addImageButton.removeEventListener(
|
|
284
|
+
"mousedown",
|
|
285
|
+
addImageButtonMouseDownHandler
|
|
286
|
+
);
|
|
287
|
+
addImageButton.removeEventListener("click", addImageButtonClickHandler);
|
|
288
|
+
leftResizeHandle.removeEventListener(
|
|
289
|
+
"mousedown",
|
|
290
|
+
leftResizeHandleMouseDownHandler
|
|
291
|
+
);
|
|
292
|
+
rightResizeHandle.removeEventListener(
|
|
293
|
+
"mousedown",
|
|
294
|
+
rightResizeHandleMouseDownHandler
|
|
295
|
+
);
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export const Image = createBlockSpec({
|
|
301
|
+
type: "image",
|
|
302
|
+
propSchema: imagePropSchema,
|
|
303
|
+
containsInlineContent: false,
|
|
304
|
+
render: renderImage,
|
|
305
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const uploadToTmpFilesDotOrg_DEV_ONLY = async (file: File) => {
|
|
2
|
+
const body = new FormData();
|
|
3
|
+
body.append("file", file);
|
|
4
|
+
|
|
5
|
+
const ret = await fetch("https://tmpfiles.org/api/v1/upload", {
|
|
6
|
+
method: "POST",
|
|
7
|
+
body: body,
|
|
8
|
+
});
|
|
9
|
+
return (await ret.json()).data.url.replace(
|
|
10
|
+
"tmpfiles.org/",
|
|
11
|
+
"tmpfiles.org/dl/"
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { InputRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import { defaultProps } from "../../../../api/defaultProps";
|
|
2
3
|
import { createTipTapBlock } from "../../../../api/block";
|
|
4
|
+
import { BlockSpec, PropSchema } from "../../../../api/blockTypes";
|
|
5
|
+
import { mergeCSSClasses } from "../../../../../../shared/utils";
|
|
3
6
|
import { handleEnter } from "../ListItemKeyboardShortcuts";
|
|
4
7
|
import styles from "../../../Block.module.css";
|
|
5
|
-
import { mergeCSSClasses } from "../../../../../../shared/utils";
|
|
6
8
|
|
|
7
|
-
export const
|
|
9
|
+
export const bulletListItemPropSchema = {
|
|
10
|
+
...defaultProps,
|
|
11
|
+
} satisfies PropSchema;
|
|
12
|
+
|
|
13
|
+
const BulletListItemBlockContent = createTipTapBlock<"bulletListItem", true>({
|
|
8
14
|
name: "bulletListItem",
|
|
9
15
|
content: "inline*",
|
|
10
16
|
|
|
@@ -29,6 +35,17 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
|
|
|
29
35
|
addKeyboardShortcuts() {
|
|
30
36
|
return {
|
|
31
37
|
Enter: () => handleEnter(this.editor),
|
|
38
|
+
"Mod-Shift-7": () =>
|
|
39
|
+
this.editor.commands.BNUpdateBlock<{
|
|
40
|
+
bulletListItem: BlockSpec<
|
|
41
|
+
"bulletListItem",
|
|
42
|
+
typeof bulletListItemPropSchema,
|
|
43
|
+
true
|
|
44
|
+
>;
|
|
45
|
+
}>(this.editor.state.selection.anchor, {
|
|
46
|
+
type: "bulletListItem",
|
|
47
|
+
props: {},
|
|
48
|
+
}),
|
|
32
49
|
};
|
|
33
50
|
},
|
|
34
51
|
|
|
@@ -91,6 +108,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
|
|
|
91
108
|
return [
|
|
92
109
|
"div",
|
|
93
110
|
mergeAttributes(HTMLAttributes, {
|
|
111
|
+
...blockContentDOMAttributes,
|
|
94
112
|
class: mergeCSSClasses(
|
|
95
113
|
styles.blockContent,
|
|
96
114
|
blockContentDOMAttributes.class
|
|
@@ -100,6 +118,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
|
|
|
100
118
|
[
|
|
101
119
|
"p",
|
|
102
120
|
{
|
|
121
|
+
...inlineContentDOMAttributes,
|
|
103
122
|
class: mergeCSSClasses(
|
|
104
123
|
styles.inlineContent,
|
|
105
124
|
inlineContentDOMAttributes.class
|
|
@@ -110,3 +129,8 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
|
|
|
110
129
|
];
|
|
111
130
|
},
|
|
112
131
|
});
|
|
132
|
+
|
|
133
|
+
export const BulletListItem = {
|
|
134
|
+
node: BulletListItemBlockContent,
|
|
135
|
+
propSchema: bulletListItemPropSchema,
|
|
136
|
+
} satisfies BlockSpec<"bulletListItem", typeof bulletListItemPropSchema, true>;
|