@blocknote/core 0.13.2 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/blocknote.js +5282 -2785
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +7 -7
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +2 -2
  8. package/src/api/exporters/html/__snapshots__/file/basic/external.html +1 -0
  9. package/src/api/exporters/html/__snapshots__/file/basic/internal.html +1 -0
  10. package/src/api/exporters/html/__snapshots__/file/button/external.html +1 -0
  11. package/src/api/exporters/html/__snapshots__/file/button/internal.html +1 -0
  12. package/src/api/exporters/html/__snapshots__/file/nested/external.html +1 -0
  13. package/src/api/exporters/html/__snapshots__/file/nested/internal.html +1 -0
  14. package/src/api/exporters/html/__snapshots__/file/noCaption/external.html +1 -0
  15. package/src/api/exporters/html/__snapshots__/file/noCaption/internal.html +1 -0
  16. package/src/api/exporters/html/__snapshots__/file/noName/external.html +1 -0
  17. package/src/api/exporters/html/__snapshots__/file/noName/internal.html +1 -0
  18. package/src/api/exporters/html/__snapshots__/image/basic/external.html +1 -1
  19. package/src/api/exporters/html/__snapshots__/image/basic/internal.html +1 -1
  20. package/src/api/exporters/html/__snapshots__/image/button/external.html +1 -1
  21. package/src/api/exporters/html/__snapshots__/image/button/internal.html +1 -1
  22. package/src/api/exporters/html/__snapshots__/image/nested/external.html +1 -1
  23. package/src/api/exporters/html/__snapshots__/image/nested/internal.html +1 -1
  24. package/src/api/exporters/html/__snapshots__/image/noCaption/external.html +1 -0
  25. package/src/api/exporters/html/__snapshots__/image/noCaption/internal.html +1 -0
  26. package/src/api/exporters/html/__snapshots__/image/noName/external.html +1 -0
  27. package/src/api/exporters/html/__snapshots__/image/noName/internal.html +1 -0
  28. package/src/api/exporters/html/__snapshots__/image/noPreview/external.html +1 -0
  29. package/src/api/exporters/html/__snapshots__/image/noPreview/internal.html +1 -0
  30. package/src/api/exporters/html/__snapshots__/simpleFile/basic/external.html +1 -0
  31. package/src/api/exporters/html/__snapshots__/simpleFile/basic/internal.html +1 -0
  32. package/src/api/exporters/html/__snapshots__/simpleFile/button/external.html +1 -0
  33. package/src/api/exporters/html/__snapshots__/simpleFile/button/internal.html +1 -0
  34. package/src/api/exporters/html/__snapshots__/simpleFile/nested/external.html +1 -0
  35. package/src/api/exporters/html/__snapshots__/simpleFile/nested/internal.html +1 -0
  36. package/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +1 -1
  37. package/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html +1 -1
  38. package/src/api/exporters/html/__snapshots__/simpleImage/button/external.html +1 -1
  39. package/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html +1 -1
  40. package/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +1 -1
  41. package/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html +1 -1
  42. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html +1 -0
  43. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html +1 -0
  44. package/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html +1 -0
  45. package/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html +1 -0
  46. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html +1 -0
  47. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html +1 -0
  48. package/src/api/exporters/markdown/__snapshots__/file/basic/markdown.md +3 -0
  49. package/src/api/exporters/markdown/__snapshots__/file/button/markdown.md +1 -0
  50. package/src/api/exporters/markdown/__snapshots__/file/nested/markdown.md +7 -0
  51. package/src/api/exporters/markdown/__snapshots__/file/noCaption/markdown.md +1 -0
  52. package/src/api/exporters/markdown/__snapshots__/file/noName/markdown.md +3 -0
  53. package/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md +1 -1
  54. package/src/api/exporters/markdown/__snapshots__/image/button/markdown.md +1 -1
  55. package/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md +2 -2
  56. package/src/api/exporters/markdown/__snapshots__/image/noCaption/markdown.md +1 -0
  57. package/src/api/exporters/markdown/__snapshots__/image/noName/markdown.md +3 -0
  58. package/src/api/exporters/markdown/__snapshots__/image/noPreview/markdown.md +3 -0
  59. package/src/api/exporters/markdown/__snapshots__/simpleFile/basic/markdown.md +3 -0
  60. package/src/api/exporters/markdown/__snapshots__/simpleFile/button/markdown.md +1 -0
  61. package/src/api/exporters/markdown/__snapshots__/simpleFile/nested/markdown.md +7 -0
  62. package/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md +3 -1
  63. package/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md +1 -0
  64. package/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md +6 -2
  65. package/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md +1 -0
  66. package/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md +3 -0
  67. package/src/api/exporters/markdown/__snapshots__/simpleImage/noPreview/markdown.md +3 -0
  68. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +212 -4
  69. package/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json +3 -1
  70. package/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json +3 -1
  71. package/src/api/testUtil/cases/customBlocks.ts +79 -33
  72. package/src/api/testUtil/cases/customInlineContent.ts +1 -1
  73. package/src/api/testUtil/cases/customStyles.ts +1 -1
  74. package/src/api/testUtil/cases/defaultSchema.ts +114 -4
  75. package/src/blocks/AudioBlockContent/AudioBlockContent.ts +162 -0
  76. package/src/blocks/AudioBlockContent/audioBlockHelpers.ts +5 -0
  77. package/src/blocks/FileBlockContent/FileBlockContent.ts +121 -0
  78. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +377 -0
  79. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +135 -356
  80. package/src/blocks/ImageBlockContent/imageBlockHelpers.ts +6 -0
  81. package/src/blocks/VideoBlockContent/VideoBlockContent.ts +182 -0
  82. package/src/blocks/VideoBlockContent/videoBlockHelpers.ts +6 -0
  83. package/src/blocks/defaultBlockTypeGuards.ts +53 -1
  84. package/src/blocks/defaultBlocks.ts +8 -2
  85. package/src/editor/Block.css +67 -27
  86. package/src/editor/BlockNoteEditor.ts +14 -10
  87. package/src/editor/BlockNoteSchema.ts +12 -3
  88. package/src/extensions/{ImagePanel/ImageToolbarPlugin.ts → FilePanel/FilePanelPlugin.ts} +22 -25
  89. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +14 -2
  90. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +59 -2
  91. package/src/i18n/locales/en.ts +102 -11
  92. package/src/i18n/locales/fr.ts +104 -11
  93. package/src/i18n/locales/index.ts +8 -2
  94. package/src/i18n/locales/is.ts +288 -0
  95. package/src/i18n/locales/ja.ts +300 -0
  96. package/src/i18n/locales/ko.ts +292 -0
  97. package/src/i18n/locales/nl.ts +101 -8
  98. package/src/i18n/locales/pl.ts +280 -0
  99. package/src/i18n/locales/pt.ts +281 -0
  100. package/src/i18n/locales/vi.ts +281 -0
  101. package/src/i18n/locales/zh.ts +107 -8
  102. package/src/index.ts +9 -2
  103. package/src/pm-nodes/BlockContainer.ts +2 -2
  104. package/src/schema/blocks/createSpec.ts +1 -0
  105. package/src/schema/blocks/internal.ts +10 -0
  106. package/src/schema/blocks/types.ts +41 -5
  107. package/src/util/string.ts +12 -0
  108. package/types/src/api/testUtil/cases/customBlocks.d.ts +228 -42
  109. package/types/src/api/testUtil/cases/customInlineContent.d.ts +178 -4
  110. package/types/src/api/testUtil/cases/customStyles.d.ts +178 -4
  111. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +104 -0
  112. package/types/src/blocks/AudioBlockContent/audioBlockHelpers.d.ts +3 -0
  113. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +96 -0
  114. package/types/src/blocks/FileBlockContent/fileBlockHelpers.d.ts +30 -0
  115. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +53 -14
  116. package/types/src/blocks/ImageBlockContent/imageBlockHelpers.d.ts +4 -0
  117. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +132 -0
  118. package/types/src/blocks/VideoBlockContent/videoBlockHelpers.d.ts +4 -0
  119. package/types/src/blocks/defaultBlockTypeGuards.d.ts +6 -1
  120. package/types/src/blocks/defaultBlocks.d.ts +356 -8
  121. package/types/src/editor/BlockNoteEditor.d.ts +5 -5
  122. package/types/src/extensions/{ImagePanel/ImageToolbarPlugin.d.ts → FilePanel/FilePanelPlugin.d.ts} +9 -12
  123. package/types/src/i18n/locales/en.d.ts +49 -7
  124. package/types/src/i18n/locales/fr.d.ts +2 -184
  125. package/types/src/i18n/locales/index.d.ts +7 -1
  126. package/types/src/i18n/locales/is.d.ts +2 -0
  127. package/types/src/i18n/locales/ja.d.ts +2 -0
  128. package/types/src/i18n/locales/ko.d.ts +2 -0
  129. package/types/src/i18n/locales/pl.d.ts +2 -0
  130. package/types/src/i18n/locales/pt.d.ts +2 -0
  131. package/types/src/i18n/locales/vi.d.ts +2 -0
  132. package/types/src/index.d.ts +8 -2
  133. package/types/src/schema/blocks/internal.d.ts +1 -1
  134. package/types/src/schema/blocks/types.d.ts +26 -1
  135. package/types/src/util/string.d.ts +1 -0
  136. /package/src/blocks/{ImageBlockContent → FileBlockContent}/uploadToTmpFilesDotOrg_DEV_ONLY.ts +0 -0
  137. /package/types/src/blocks/{ImageBlockContent → FileBlockContent}/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +0 -0
@@ -1,404 +1,183 @@
1
1
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
2
-
3
2
  import {
4
3
  BlockFromConfig,
5
- BlockSchemaWithBlock,
6
- CustomBlockConfig,
7
- InlineContentSchema,
8
- PropSchema,
9
- StyleSchema,
10
4
  createBlockSpec,
5
+ FileBlockConfig,
6
+ Props,
7
+ PropSchema,
11
8
  } from "../../schema";
12
9
  import { defaultProps } from "../defaultProps";
13
10
 
11
+ import {
12
+ createAddFileButton,
13
+ createDefaultFilePreview,
14
+ createFigureWithCaption,
15
+ createFileAndCaptionWrapper,
16
+ createLinkWithCaption,
17
+ createResizeHandlesWrapper,
18
+ parseFigureElement,
19
+ } from "../FileBlockContent/fileBlockHelpers";
20
+ import { parseImageElement } from "./imageBlockHelpers";
21
+
14
22
  export const imagePropSchema = {
15
23
  textAlignment: defaultProps.textAlignment,
16
24
  backgroundColor: defaultProps.backgroundColor,
17
- // Image url.
25
+ // File name.
26
+ name: {
27
+ default: "" as const,
28
+ },
29
+ // File url.
18
30
  url: {
19
31
  default: "" as const,
20
32
  },
21
- // Image caption.
33
+ // File caption.
22
34
  caption: {
23
35
  default: "" as const,
24
36
  },
25
- // Image width in px.
26
- width: {
27
- default: 512 as const,
37
+
38
+ showPreview: {
39
+ default: true,
40
+ },
41
+ // File preview width in px.
42
+ previewWidth: {
43
+ default: 512,
28
44
  },
29
45
  } satisfies PropSchema;
30
46
 
31
- // Converts text alignment prop values to the flexbox `align-items` values.
32
- const textAlignmentToAlignItems = (
33
- textAlignment: "left" | "center" | "right" | "justify"
34
- ): "flex-start" | "center" | "flex-end" => {
35
- switch (textAlignment) {
36
- case "left":
37
- return "flex-start";
38
- case "center":
39
- return "center";
40
- case "right":
41
- return "flex-end";
42
- default:
43
- return "flex-start";
44
- }
45
- };
46
-
47
- // Min image width in px.
48
- const minWidth = 64;
49
-
50
- const blockConfig = {
47
+ export const imageBlockConfig = {
51
48
  type: "image" as const,
52
49
  propSchema: imagePropSchema,
53
50
  content: "none",
54
- } satisfies CustomBlockConfig;
55
-
56
- export const renderImage = (
57
- block: BlockFromConfig<typeof blockConfig, InlineContentSchema, StyleSchema>,
58
- editor: BlockNoteEditor<BlockSchemaWithBlock<"image", typeof blockConfig>>
51
+ isFileBlock: true,
52
+ isFileBlockPlaceholder: (block: any) => !block.props.url,
53
+ fileBlockAcceptMimeTypes: ["image/*"],
54
+ } satisfies FileBlockConfig;
55
+
56
+ export const imageRender = (
57
+ block: BlockFromConfig<typeof imageBlockConfig, any, any>,
58
+ editor: BlockNoteEditor<any, any, any>
59
59
  ) => {
60
- // Wrapper element to set the image alignment, contains both image/image
61
- // upload dashboard and caption.
62
60
  const wrapper = document.createElement("div");
63
- wrapper.className = "bn-image-block-content-wrapper";
64
- wrapper.style.alignItems = textAlignmentToAlignItems(
65
- block.props.textAlignment
66
- );
67
-
68
- // Button element that acts as a placeholder for images with no src.
69
- const addImageButton = document.createElement("div");
70
- addImageButton.className = "bn-add-image-button";
71
-
72
- // Icon for the add image button.
73
- const addImageButtonIcon = document.createElement("div");
74
- addImageButtonIcon.className = "bn-add-image-button-icon";
75
-
76
- // Text for the add image button.
77
- const addImageButtonText = document.createElement("p");
78
- addImageButtonText.className = "bn-add-image-button-text";
79
- addImageButtonText.innerText = editor.dictionary.image.add_button;
80
-
81
- // Wrapper element for the image, resize handles and caption.
82
- const imageAndCaptionWrapper = document.createElement("div");
83
- imageAndCaptionWrapper.className = "bn-image-and-caption-wrapper";
84
-
85
- // Wrapper element for the image and resize handles.
86
- const imageWrapper = document.createElement("div");
87
- imageWrapper.className = "bn-image-wrapper";
88
-
89
- // Image element.
90
- const image = document.createElement("img");
91
- image.className = "bn-image";
92
- image.src = block.props.url;
93
- image.alt = "placeholder";
94
- image.contentEditable = "false";
95
- image.draggable = false;
96
- image.style.width = `${Math.min(
97
- block.props.width,
98
- editor.domElement.firstElementChild!.clientWidth
99
- )}px`;
100
-
101
- // Resize handle elements.
102
- const leftResizeHandle = document.createElement("div");
103
- leftResizeHandle.className = "bn-image-resize-handle";
104
- leftResizeHandle.style.left = "4px";
105
- const rightResizeHandle = document.createElement("div");
106
- rightResizeHandle.className = "bn-image-resize-handle";
107
- rightResizeHandle.style.right = "4px";
108
-
109
- // Caption element.
110
- const caption = document.createElement("p");
111
- caption.className = "bn-image-caption";
112
- caption.innerText = block.props.caption;
113
- caption.style.padding = block.props.caption ? "4px" : "";
61
+ wrapper.className = "bn-file-block-content-wrapper";
114
62
 
115
- // Adds a light blue outline to selected image blocks.
116
- const handleEditorUpdate = () => {
117
- const selection = editor.getSelection()?.blocks || [];
118
- const currentBlock = editor.getTextCursorPosition().block;
119
-
120
- const isSelected =
121
- [currentBlock, ...selection].find(
122
- (selectedBlock) => selectedBlock.id === block.id
123
- ) !== undefined;
124
-
125
- if (isSelected) {
126
- addImageButton.style.outline = "4px solid rgb(100, 160, 255)";
127
- imageAndCaptionWrapper.style.outline = "4px solid rgb(100, 160, 255)";
128
- } else {
129
- addImageButton.style.outline = "";
130
- imageAndCaptionWrapper.style.outline = "";
131
- }
132
- };
133
- editor.onEditorContentChange(handleEditorUpdate);
134
- editor.onEditorSelectionChange(handleEditorUpdate);
135
-
136
- // Temporary parameters set when the user begins resizing the image, used to
137
- // calculate the new width of the image.
138
- let resizeParams:
139
- | {
140
- handleUsed: "left" | "right";
141
- initialWidth: number;
142
- initialClientX: number;
143
- }
144
- | undefined;
145
-
146
- // Updates the image width with an updated width depending on the cursor X
147
- // offset from when the resize began, and which resize handle is being used.
148
- const windowMouseMoveHandler = (event: MouseEvent) => {
149
- if (!resizeParams) {
150
- if (
151
- !editor.isEditable &&
152
- imageWrapper.contains(leftResizeHandle) &&
153
- imageWrapper.contains(rightResizeHandle)
154
- ) {
155
- imageWrapper.removeChild(leftResizeHandle);
156
- imageWrapper.removeChild(rightResizeHandle);
157
- }
158
-
159
- return;
160
- }
161
-
162
- let newWidth: number;
163
-
164
- if (textAlignmentToAlignItems(block.props.textAlignment) === "center") {
165
- if (resizeParams.handleUsed === "left") {
166
- newWidth =
167
- resizeParams.initialWidth +
168
- (resizeParams.initialClientX - event.clientX) * 2;
169
- } else {
170
- newWidth =
171
- resizeParams.initialWidth +
172
- (event.clientX - resizeParams.initialClientX) * 2;
173
- }
174
- } else {
175
- if (resizeParams.handleUsed === "left") {
176
- newWidth =
177
- resizeParams.initialWidth +
178
- resizeParams.initialClientX -
179
- event.clientX;
180
- } else {
181
- newWidth =
182
- resizeParams.initialWidth +
183
- event.clientX -
184
- resizeParams.initialClientX;
185
- }
186
- }
187
-
188
- // Ensures the image is not wider than the editor and not smaller than a
189
- // predetermined minimum width.
190
- if (newWidth < minWidth) {
191
- image.style.width = `${minWidth}px`;
192
- } else if (newWidth > editor.domElement.firstElementChild!.clientWidth) {
193
- image.style.width = `${
194
- editor.domElement.firstElementChild!.clientWidth
195
- }px`;
196
- } else {
197
- image.style.width = `${newWidth}px`;
198
- }
199
- };
200
- // Stops mouse movements from resizing the image and updates the block's
201
- // `width` prop to the new value.
202
- const windowMouseUpHandler = (event: MouseEvent) => {
203
- // Hides the drag handles if the cursor is no longer over the image.
204
- if (
205
- (!event.target ||
206
- !imageWrapper.contains(event.target as Node) ||
207
- !editor.isEditable) &&
208
- imageWrapper.contains(leftResizeHandle) &&
209
- imageWrapper.contains(rightResizeHandle)
210
- ) {
211
- imageWrapper.removeChild(leftResizeHandle);
212
- imageWrapper.removeChild(rightResizeHandle);
213
- }
214
-
215
- if (!resizeParams) {
216
- return;
217
- }
218
-
219
- resizeParams = undefined;
63
+ if (block.props.url === "") {
64
+ const fileBlockImageIcon = document.createElement("div");
65
+ fileBlockImageIcon.innerHTML =
66
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5 11.1005L7 9.1005L12.5 14.6005L16 11.1005L19 14.1005V5H5V11.1005ZM4 3H20C20.5523 3 21 3.44772 21 4V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V4C3 3.44772 3.44772 3 4 3ZM15.5 10C14.6716 10 14 9.32843 14 8.5C14 7.67157 14.6716 7 15.5 7C16.3284 7 17 7.67157 17 8.5C17 9.32843 16.3284 10 15.5 10Z"></path></svg>';
67
+ const addImageButton = createAddFileButton(
68
+ block,
69
+ editor,
70
+ editor.dictionary.file_blocks.image.add_button_text,
71
+ fileBlockImageIcon.firstElementChild as HTMLElement
72
+ );
73
+ wrapper.appendChild(addImageButton.dom);
220
74
 
221
- editor.updateBlock(block, {
222
- type: "image",
223
- props: {
224
- // Removes "px" from the end of the width string and converts to float.
225
- width: parseFloat(image.style.width.slice(0, -2)) as any,
75
+ return {
76
+ dom: wrapper,
77
+ destroy: () => {
78
+ addImageButton?.destroy?.();
226
79
  },
227
- });
228
- };
80
+ };
81
+ } else if (!block.props.showPreview) {
82
+ const file = createDefaultFilePreview(block).dom;
83
+ const element = createFileAndCaptionWrapper(block, file);
229
84
 
230
- // Prevents focus from moving to the button.
231
- const addImageButtonMouseDownHandler = (event: MouseEvent) => {
232
- event.preventDefault();
233
- };
234
- // Opens the image toolbar.
235
- const addImageButtonClickHandler = () => {
236
- editor._tiptapEditor.view.dispatch(
237
- editor._tiptapEditor.state.tr.setMeta(editor.imagePanel!.plugin, {
238
- block: block,
239
- })
85
+ return {
86
+ dom: element.dom,
87
+ };
88
+ } else {
89
+ const image = document.createElement("img");
90
+ image.className = "bn-visual-media";
91
+ image.src = block.props.url;
92
+ image.alt = block.props.name || block.props.caption || "BlockNote image";
93
+ image.contentEditable = "false";
94
+ image.draggable = false;
95
+ image.width = Math.min(
96
+ block.props.previewWidth,
97
+ editor.domElement.firstElementChild!.clientWidth
240
98
  );
241
- };
242
-
243
- // Shows the resize handles when hovering over the image with the cursor.
244
- const imageMouseEnterHandler = () => {
245
- if (editor.isEditable) {
246
- imageWrapper.appendChild(leftResizeHandle);
247
- imageWrapper.appendChild(rightResizeHandle);
248
- }
249
- };
250
- // Hides the resize handles when the cursor leaves the image, unless the
251
- // cursor moves to one of the resize handles.
252
- const imageMouseLeaveHandler = (event: MouseEvent) => {
253
- if (
254
- event.relatedTarget === leftResizeHandle ||
255
- event.relatedTarget === rightResizeHandle
256
- ) {
257
- return;
258
- }
259
99
 
260
- if (resizeParams) {
261
- return;
262
- }
100
+ const file = createResizeHandlesWrapper(
101
+ block,
102
+ editor,
103
+ image,
104
+ () => image.width,
105
+ (width) => (image.width = width)
106
+ );
263
107
 
264
- if (
265
- editor.isEditable &&
266
- imageWrapper.contains(leftResizeHandle) &&
267
- imageWrapper.contains(rightResizeHandle)
268
- ) {
269
- imageWrapper.removeChild(leftResizeHandle);
270
- imageWrapper.removeChild(rightResizeHandle);
271
- }
272
- };
108
+ const element = createFileAndCaptionWrapper(block, file.dom);
109
+ wrapper.appendChild(element.dom);
273
110
 
274
- // Sets the resize params, allowing the user to begin resizing the image by
275
- // moving the cursor left or right.
276
- const leftResizeHandleMouseDownHandler = (event: MouseEvent) => {
277
- event.preventDefault();
111
+ return {
112
+ dom: wrapper,
113
+ destroy: file.destroy,
114
+ };
115
+ }
116
+ };
278
117
 
279
- imageWrapper.appendChild(leftResizeHandle);
280
- imageWrapper.appendChild(rightResizeHandle);
118
+ export const imageParse = (
119
+ element: HTMLElement
120
+ ): Partial<Props<typeof imageBlockConfig.propSchema>> | undefined => {
121
+ if (element.tagName === "IMG") {
122
+ return parseImageElement(element as HTMLImageElement);
123
+ }
281
124
 
282
- resizeParams = {
283
- handleUsed: "left",
284
- initialWidth: block.props.width,
285
- initialClientX: event.clientX,
286
- };
287
- };
288
- const rightResizeHandleMouseDownHandler = (event: MouseEvent) => {
289
- event.preventDefault();
125
+ if (element.tagName === "FIGURE") {
126
+ const parsedFigure = parseFigureElement(element, "img");
127
+ if (!parsedFigure) {
128
+ return undefined;
129
+ }
290
130
 
291
- imageWrapper.appendChild(leftResizeHandle);
292
- imageWrapper.appendChild(rightResizeHandle);
131
+ const { targetElement, caption } = parsedFigure;
293
132
 
294
- resizeParams = {
295
- handleUsed: "right",
296
- initialWidth: block.props.width,
297
- initialClientX: event.clientX,
133
+ return {
134
+ ...parseImageElement(targetElement as HTMLImageElement),
135
+ caption,
298
136
  };
299
- };
137
+ }
300
138
 
301
- addImageButton.appendChild(addImageButtonIcon);
302
- addImageButton.appendChild(addImageButtonText);
139
+ return undefined;
140
+ };
303
141
 
304
- imageAndCaptionWrapper.appendChild(imageWrapper);
305
- imageWrapper.appendChild(image);
306
- imageAndCaptionWrapper.appendChild(caption);
142
+ export const imageToExternalHTML = (
143
+ block: BlockFromConfig<typeof imageBlockConfig, any, any>
144
+ ) => {
145
+ if (!block.props.url) {
146
+ const div = document.createElement("p");
147
+ div.textContent = "Add image";
307
148
 
308
- if (block.props.url === "") {
309
- wrapper.appendChild(addImageButton);
149
+ return {
150
+ dom: div,
151
+ };
152
+ }
153
+
154
+ let image;
155
+ if (block.props.showPreview) {
156
+ image = document.createElement("img");
157
+ image.src = block.props.url;
158
+ image.alt = block.props.name || block.props.caption || "BlockNote image";
159
+ image.width = block.props.previewWidth;
310
160
  } else {
311
- wrapper.appendChild(imageAndCaptionWrapper);
161
+ image = document.createElement("a");
162
+ image.href = block.props.url;
163
+ image.textContent = block.props.name || block.props.url;
312
164
  }
313
165
 
314
- window.addEventListener("mousemove", windowMouseMoveHandler);
315
- window.addEventListener("mouseup", windowMouseUpHandler);
316
- addImageButton.addEventListener("mousedown", addImageButtonMouseDownHandler);
317
- addImageButton.addEventListener("click", addImageButtonClickHandler);
318
- image.addEventListener("mouseenter", imageMouseEnterHandler);
319
- image.addEventListener("mouseleave", imageMouseLeaveHandler);
320
- leftResizeHandle.addEventListener(
321
- "mousedown",
322
- leftResizeHandleMouseDownHandler
323
- );
324
- rightResizeHandle.addEventListener(
325
- "mousedown",
326
- rightResizeHandleMouseDownHandler
327
- );
166
+ if (block.props.caption) {
167
+ if (block.props.showPreview) {
168
+ return createFigureWithCaption(image, block.props.caption);
169
+ } else {
170
+ return createLinkWithCaption(image, block.props.caption);
171
+ }
172
+ }
328
173
 
329
174
  return {
330
- dom: wrapper,
331
- destroy: () => {
332
- window.removeEventListener("mousemove", windowMouseMoveHandler);
333
- window.removeEventListener("mouseup", windowMouseUpHandler);
334
- addImageButton.removeEventListener(
335
- "mousedown",
336
- addImageButtonMouseDownHandler
337
- );
338
- addImageButton.removeEventListener("click", addImageButtonClickHandler);
339
- leftResizeHandle.removeEventListener(
340
- "mousedown",
341
- leftResizeHandleMouseDownHandler
342
- );
343
- rightResizeHandle.removeEventListener(
344
- "mousedown",
345
- rightResizeHandleMouseDownHandler
346
- );
347
- },
175
+ dom: image,
348
176
  };
349
177
  };
350
178
 
351
- export const Image = createBlockSpec(
352
- {
353
- type: "image" as const,
354
- propSchema: imagePropSchema,
355
- content: "none",
356
- },
357
- {
358
- render: renderImage,
359
- toExternalHTML: (block) => {
360
- if (block.props.url === "") {
361
- const div = document.createElement("p");
362
- div.innerHTML = "Add Image";
363
-
364
- return {
365
- dom: div,
366
- };
367
- }
368
-
369
- const figure = document.createElement("figure");
370
-
371
- const img = document.createElement("img");
372
- img.src = block.props.url;
373
- figure.appendChild(img);
374
-
375
- if (block.props.caption !== "") {
376
- const figcaption = document.createElement("figcaption");
377
- figcaption.innerHTML = block.props.caption;
378
- figure.appendChild(figcaption);
379
- }
380
-
381
- return {
382
- dom: figure,
383
- };
384
- },
385
- parse: (element: HTMLElement) => {
386
- if (element.tagName === "FIGURE") {
387
- const img = element.querySelector("img");
388
- const caption = element.querySelector("figcaption");
389
- return {
390
- url: img?.getAttribute("src") || "",
391
- caption:
392
- caption?.textContent || img?.getAttribute("alt") || undefined,
393
- };
394
- } else if (element.tagName === "IMG") {
395
- return {
396
- url: element.getAttribute("src") || "",
397
- caption: element.getAttribute("alt") || undefined,
398
- };
399
- }
400
-
401
- return undefined;
402
- },
403
- }
404
- );
179
+ export const ImageBlock = createBlockSpec(imageBlockConfig, {
180
+ render: imageRender,
181
+ parse: imageParse,
182
+ toExternalHTML: imageToExternalHTML,
183
+ });
@@ -0,0 +1,6 @@
1
+ export const parseImageElement = (imageElement: HTMLImageElement) => {
2
+ const url = imageElement.src || undefined;
3
+ const previewWidth = imageElement.width || undefined;
4
+
5
+ return { url, previewWidth };
6
+ };