@blocknote/core 0.9.3 → 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.
Files changed (52) hide show
  1. package/dist/blocknote.js +1623 -1318
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +5 -5
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +2 -2
  7. package/src/BlockNoteEditor.ts +44 -12
  8. package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +21 -21
  9. package/src/api/blockManipulation/blockManipulation.test.ts +8 -11
  10. package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +3 -3
  11. package/src/api/formatConversions/formatConversions.test.ts +5 -5
  12. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +3 -3
  13. package/src/api/nodeConversions/nodeConversions.test.ts +10 -4
  14. package/src/api/nodeConversions/nodeConversions.ts +9 -7
  15. package/src/api/nodeConversions/testUtil.ts +3 -3
  16. package/src/editor.module.css +1 -1
  17. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +5 -3
  18. package/src/extensions/BackgroundColor/BackgroundColorMark.ts +2 -1
  19. package/src/extensions/Blocks/NonEditableBlockPlugin.ts +17 -0
  20. package/src/extensions/Blocks/api/block.ts +29 -16
  21. package/src/extensions/Blocks/api/blockTypes.ts +79 -27
  22. package/src/extensions/Blocks/api/defaultBlocks.ts +13 -41
  23. package/src/extensions/Blocks/api/defaultProps.ts +16 -0
  24. package/src/extensions/Blocks/nodes/Block.module.css +78 -24
  25. package/src/extensions/Blocks/nodes/BlockContainer.ts +17 -41
  26. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +59 -13
  27. package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.ts +305 -0
  28. package/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +13 -0
  29. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +24 -2
  30. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +146 -120
  31. package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +12 -2
  32. package/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +239 -0
  33. package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +47 -6
  34. package/src/extensions/TextColor/TextColorExtension.ts +4 -3
  35. package/src/extensions/TextColor/TextColorMark.ts +2 -1
  36. package/src/index.ts +4 -0
  37. package/types/src/BlockNoteEditor.d.ts +9 -0
  38. package/types/src/BlockNoteExtensions.d.ts +1 -1
  39. package/types/src/extensions/Blocks/api/block.d.ts +7 -8
  40. package/types/src/extensions/Blocks/api/blockTypes.d.ts +29 -20
  41. package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +55 -51
  42. package/types/src/extensions/Blocks/api/defaultProps.d.ts +2 -2
  43. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +43 -9
  44. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.d.ts +2 -2
  45. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +1 -0
  46. package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesOrg_DEV_ONLY.d.ts +1 -0
  47. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +35 -9
  48. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +35 -9
  49. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +36 -1
  50. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -1
  51. package/types/src/index.d.ts +4 -0
  52. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { Attribute, Node } from "@tiptap/core";
1
+ import { Attribute, Attributes, Node } from "@tiptap/core";
2
2
  import { BlockNoteDOMAttributes, BlockNoteEditor } from "../../..";
3
3
  import styles from "../nodes/Block.module.css";
4
4
  import {
@@ -10,6 +10,7 @@ import {
10
10
  TipTapNodeConfig,
11
11
  } from "./blockTypes";
12
12
  import { mergeCSSClasses } from "../../../shared/utils";
13
+ import { ParseRule } from "prosemirror-model";
13
14
 
14
15
  export function camelToDataKebab(str: string): string {
15
16
  return "data-" + str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
@@ -27,7 +28,7 @@ export function propsToAttributes<
27
28
  BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>,
28
29
  "render"
29
30
  >
30
- ) {
31
+ ): Attributes {
31
32
  const tiptapAttributes: Record<string, Attribute> = {};
32
33
 
33
34
  Object.entries(blockConfig.propSchema).forEach(([name, spec]) => {
@@ -63,7 +64,7 @@ export function parse<
63
64
  BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>,
64
65
  "render"
65
66
  >
66
- ) {
67
+ ): ParseRule[] {
67
68
  return [
68
69
  {
69
70
  tag: "div[data-content-type=" + blockConfig.type + "]",
@@ -120,21 +121,24 @@ export function render<
120
121
  export function createBlockSpec<
121
122
  BType extends string,
122
123
  PSchema extends PropSchema,
123
- ContainsInlineContent extends boolean,
124
+ ContainsInlineContent extends false,
124
125
  BSchema extends BlockSchema
125
126
  >(
126
127
  blockConfig: BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>
127
- ): BlockSpec<BType, PSchema> {
128
+ ): BlockSpec<BType, PSchema, ContainsInlineContent> {
128
129
  const node = createTipTapBlock<
129
130
  BType,
131
+ ContainsInlineContent,
130
132
  {
131
133
  editor: BlockNoteEditor<BSchema>;
132
134
  domAttributes?: BlockNoteDOMAttributes;
133
135
  }
134
136
  >({
135
137
  name: blockConfig.type,
136
- content: blockConfig.containsInlineContent ? "inline*" : "",
137
- selectable: blockConfig.containsInlineContent,
138
+ content: (blockConfig.containsInlineContent
139
+ ? "inline*"
140
+ : "") as ContainsInlineContent extends true ? "inline*" : "",
141
+ selectable: true,
138
142
 
139
143
  addAttributes() {
140
144
  return propsToAttributes(blockConfig);
@@ -176,7 +180,9 @@ export function createBlockSpec<
176
180
 
177
181
  // Gets BlockNote editor instance
178
182
  const editor = this.options.editor! as BlockNoteEditor<
179
- BSchema & { [k in BType]: BlockSpec<BType, PSchema> }
183
+ BSchema & {
184
+ [k in BType]: BlockSpec<BType, PSchema, ContainsInlineContent>;
185
+ }
180
186
  >;
181
187
  // Gets position of the node
182
188
  if (typeof getPos === "boolean") {
@@ -201,7 +207,10 @@ export function createBlockSpec<
201
207
  // Render elements
202
208
  const rendered = blockConfig.render(block as any, editor);
203
209
  // Add HTML attributes to contentDOM
204
- if ("contentDOM" in rendered) {
210
+ if (blockConfig.containsInlineContent) {
211
+ const contentDOM = (rendered as { contentDOM: HTMLElement })
212
+ .contentDOM;
213
+
205
214
  const inlineContentDOMAttributes =
206
215
  this.options.domAttributes?.inlineContent || {};
207
216
  // Add custom HTML attributes
@@ -209,12 +218,12 @@ export function createBlockSpec<
209
218
  inlineContentDOMAttributes
210
219
  )) {
211
220
  if (attribute !== "class") {
212
- rendered.contentDOM.setAttribute(attribute, value);
221
+ contentDOM.setAttribute(attribute, value);
213
222
  }
214
223
  }
215
224
  // Merge existing classes with inlineContent & custom classes
216
- rendered.contentDOM.className = mergeCSSClasses(
217
- rendered.contentDOM.className,
225
+ contentDOM.className = mergeCSSClasses(
226
+ contentDOM.className,
218
227
  styles.inlineContent,
219
228
  inlineContentDOMAttributes.class
220
229
  );
@@ -226,22 +235,25 @@ export function createBlockSpec<
226
235
  ? {
227
236
  dom: blockContent,
228
237
  contentDOM: rendered.contentDOM,
238
+ destroy: rendered.destroy,
229
239
  }
230
240
  : {
231
241
  dom: blockContent,
242
+ destroy: rendered.destroy,
232
243
  };
233
244
  };
234
245
  },
235
246
  });
236
247
 
237
248
  return {
238
- node: node as TipTapNode<BType>,
249
+ node: node as TipTapNode<BType, ContainsInlineContent>,
239
250
  propSchema: blockConfig.propSchema,
240
251
  };
241
252
  }
242
253
 
243
254
  export function createTipTapBlock<
244
255
  Type extends string,
256
+ ContainsInlineContent extends boolean,
245
257
  Options extends {
246
258
  domAttributes?: BlockNoteDOMAttributes;
247
259
  } = {
@@ -249,8 +261,8 @@ export function createTipTapBlock<
249
261
  },
250
262
  Storage = any
251
263
  >(
252
- config: TipTapNodeConfig<Type, Options, Storage>
253
- ): TipTapNode<Type, Options, Storage> {
264
+ config: TipTapNodeConfig<Type, ContainsInlineContent, Options, Storage>
265
+ ): TipTapNode<Type, ContainsInlineContent, Options, Storage> {
254
266
  // Type cast is needed as Node.name is mutable, though there is basically no
255
267
  // reason to change it after creation. Alternative is to wrap Node in a new
256
268
  // class, which I don't think is worth it since we'd only be changing 1
@@ -258,5 +270,6 @@ export function createTipTapBlock<
258
270
  return Node.create<Options, Storage>({
259
271
  ...config,
260
272
  group: "blockContent",
261
- }) as TipTapNode<Type, Options, Storage>;
273
+ content: config.content,
274
+ }) as TipTapNode<Type, ContainsInlineContent, Options, Storage>;
262
275
  }
@@ -16,11 +16,13 @@ export type BlockNoteDOMAttributes = Partial<{
16
16
  }>;
17
17
 
18
18
  // A configuration for a TipTap node, but with stricter type constraints on the
19
- // "name" and "group" properties. The "name" property is now always a string
20
- // literal type, and the "blockGroup" property cannot be configured as it should
21
- // always be "blockContent". Used as the parameter in `createTipTapNode`.
19
+ // "name" and "content" properties. The "name" property is now always a string
20
+ // literal type, and the "content" property can only be "inline*" or "". Used as
21
+ // the parameter in `createTipTapNode`. The "group" is also removed as
22
+ // `createTipTapNode` always sets it to "blockContent"
22
23
  export type TipTapNodeConfig<
23
24
  Name extends string,
25
+ ContainsInlineContent extends boolean,
24
26
  Options extends {
25
27
  domAttributes?: BlockNoteDOMAttributes;
26
28
  } = {
@@ -30,32 +32,58 @@ export type TipTapNodeConfig<
30
32
  > = {
31
33
  [K in keyof NodeConfig<Options, Storage>]: K extends "name"
32
34
  ? Name
35
+ : K extends "content"
36
+ ? ContainsInlineContent extends true
37
+ ? "inline*"
38
+ : ""
33
39
  : K extends "group"
34
40
  ? never
35
41
  : NodeConfig<Options, Storage>[K];
42
+ } & {
43
+ name: Name;
44
+ content: ContainsInlineContent extends true ? "inline*" : "";
36
45
  };
37
46
 
38
- // A TipTap node with stricter type constraints on the "name" and "group"
39
- // properties. The "name" property is now a string literal type, and the
40
- // "blockGroup" property is now "blockContent". Returned by `createTipTapNode`.
47
+ // A TipTap node with stricter type constraints on the "name", "group", and
48
+ // "content properties. The "name" property is now a string literal type, and
49
+ // the "blockGroup" property is now "blockContent", and the "content" property
50
+ // can only be "inline*" or "". Returned by `createTipTapNode`.
41
51
  export type TipTapNode<
42
52
  Name extends string,
53
+ ContainsInlineContent extends boolean,
43
54
  Options extends {
44
55
  domAttributes?: BlockNoteDOMAttributes;
45
56
  } = {
46
57
  domAttributes?: BlockNoteDOMAttributes;
47
58
  },
48
59
  Storage = any
49
- > = Node<Options, Storage> & {
50
- name: Name;
51
- group: "blockContent";
60
+ > = {
61
+ [Key in keyof Node<Options, Storage>]: Key extends "name"
62
+ ? Name
63
+ : Key extends "config"
64
+ ? {
65
+ [ConfigKey in keyof Node<
66
+ Options,
67
+ Storage
68
+ >["config"]]: ConfigKey extends "group"
69
+ ? "blockContent"
70
+ : ConfigKey extends "content"
71
+ ? ContainsInlineContent extends true
72
+ ? "inline*"
73
+ : ""
74
+ : NodeConfig<Options, Storage>["config"][ConfigKey];
75
+ } & {
76
+ group: "blockContent";
77
+ content: ContainsInlineContent extends true ? "inline*" : "";
78
+ }
79
+ : Node<Options, Storage>["config"][Key];
52
80
  };
53
81
 
54
82
  // Defines a single prop spec, which includes the default value the prop should
55
83
  // take and possible values it can take.
56
- export type PropSpec = {
57
- values?: readonly string[];
58
- default: string;
84
+ export type PropSpec<PType extends boolean | number | string> = {
85
+ values?: readonly PType[];
86
+ default: PType;
59
87
  };
60
88
 
61
89
  // Defines multiple block prop specs. The key of each prop is the name of the
@@ -63,15 +91,25 @@ export type PropSpec = {
63
91
  // in a block config or schema. From a prop schema, we can derive both the props'
64
92
  // internal implementation (as TipTap node attributes) and the type information
65
93
  // for the external API.
66
- export type PropSchema = Record<string, PropSpec>;
94
+ export type PropSchema = Record<string, PropSpec<boolean | number | string>>;
67
95
 
68
96
  // Defines Props objects for use in Block objects in the external API. Converts
69
97
  // each prop spec into a union type of its possible values, or a string if no
70
98
  // values are specified.
71
99
  export type Props<PSchema extends PropSchema> = {
72
- [PType in keyof PSchema]: PSchema[PType]["values"] extends readonly string[]
73
- ? PSchema[PType]["values"][number]
74
- : string;
100
+ [PName in keyof PSchema]: PSchema[PName]["default"] extends boolean
101
+ ? PSchema[PName]["values"] extends readonly boolean[]
102
+ ? PSchema[PName]["values"][number]
103
+ : boolean
104
+ : PSchema[PName]["default"] extends number
105
+ ? PSchema[PName]["values"] extends readonly number[]
106
+ ? PSchema[PName]["values"][number]
107
+ : number
108
+ : PSchema[PName]["default"] extends string
109
+ ? PSchema[PName]["values"] extends readonly string[]
110
+ ? PSchema[PName]["values"][number]
111
+ : string
112
+ : never;
75
113
  };
76
114
 
77
115
  // Defines the config for a single block. Meant to be used as an argument to
@@ -96,7 +134,9 @@ export type BlockConfig<
96
134
  * The custom block to render
97
135
  */
98
136
  block: SpecificBlock<
99
- BSchema & { [k in Type]: BlockSpec<Type, PSchema> },
137
+ BSchema & {
138
+ [k in Type]: BlockSpec<Type, PSchema, ContainsInlineContent>;
139
+ },
100
140
  Type
101
141
  >,
102
142
  /**
@@ -104,16 +144,20 @@ export type BlockConfig<
104
144
  * This is typed generically. If you want an editor with your custom schema, you need to
105
145
  * cast it manually, e.g.: `const e = editor as BlockNoteEditor<typeof mySchema>;`
106
146
  */
107
- editor: BlockNoteEditor<BSchema & { [k in Type]: BlockSpec<Type, PSchema> }>
147
+ editor: BlockNoteEditor<
148
+ BSchema & { [k in Type]: BlockSpec<Type, PSchema, ContainsInlineContent> }
149
+ >
108
150
  // (note) if we want to fix the manual cast, we need to prevent circular references and separate block definition and render implementations
109
151
  // or allow manually passing <BSchema>, but that's not possible without passing the other generics because Typescript doesn't support partial inferred generics
110
152
  ) => ContainsInlineContent extends true
111
153
  ? {
112
154
  dom: HTMLElement;
113
155
  contentDOM: HTMLElement;
156
+ destroy?: () => void;
114
157
  }
115
158
  : {
116
159
  dom: HTMLElement;
160
+ destroy?: () => void;
117
161
  };
118
162
  };
119
163
 
@@ -121,18 +165,22 @@ export type BlockConfig<
121
165
  // the TipTap node used to implement it. Usually created using `createBlockSpec`
122
166
  // though it can also be defined from scratch by providing your own TipTap node,
123
167
  // allowing for more advanced custom blocks.
124
- export type BlockSpec<Type extends string, PSchema extends PropSchema> = {
168
+ export type BlockSpec<
169
+ Type extends string,
170
+ PSchema extends PropSchema,
171
+ ContainsInlineContent extends boolean
172
+ > = {
173
+ node: TipTapNode<Type, ContainsInlineContent, any>;
125
174
  readonly propSchema: PSchema;
126
- node: TipTapNode<Type, any>;
127
175
  };
128
176
 
129
177
  // Utility type. For a given object block schema, ensures that the key of each
130
178
  // block spec matches the name of the TipTap node in it.
131
- export type TypesMatch<
132
- Blocks extends Record<string, BlockSpec<string, PropSchema>>
179
+ type NamesMatch<
180
+ Blocks extends Record<string, BlockSpec<string, PropSchema, boolean>>
133
181
  > = Blocks extends {
134
182
  [Type in keyof Blocks]: Type extends string
135
- ? Blocks[Type] extends BlockSpec<Type, PropSchema>
183
+ ? Blocks[Type] extends BlockSpec<Type, PropSchema, boolean>
136
184
  ? Blocks[Type]
137
185
  : never
138
186
  : never;
@@ -145,8 +193,8 @@ export type TypesMatch<
145
193
  // `blocks` option of the BlockNoteEditor. From a block schema, we can derive
146
194
  // both the blocks' internal implementation (as TipTap nodes) and the type
147
195
  // information for the external API.
148
- export type BlockSchema = TypesMatch<
149
- Record<string, BlockSpec<string, PropSchema>>
196
+ export type BlockSchema = NamesMatch<
197
+ Record<string, BlockSpec<string, PropSchema, boolean>>
150
198
  >;
151
199
 
152
200
  // Converts each block spec into a Block object without children. We later merge
@@ -157,7 +205,9 @@ type BlocksWithoutChildren<BSchema extends BlockSchema> = {
157
205
  id: string;
158
206
  type: BType;
159
207
  props: Props<BSchema[BType]["propSchema"]>;
160
- content: InlineContent[];
208
+ content: BSchema[BType]["node"]["config"]["content"] extends "inline*"
209
+ ? InlineContent[]
210
+ : undefined;
161
211
  };
162
212
  };
163
213
 
@@ -182,7 +232,9 @@ type PartialBlocksWithoutChildren<BSchema extends BlockSchema> = {
182
232
  id: string;
183
233
  type: BType;
184
234
  props: Partial<Props<BSchema[BType]["propSchema"]>>;
185
- content: PartialInlineContent[] | string;
235
+ content: BSchema[BType]["node"]["config"]["content"] extends "inline*"
236
+ ? PartialInlineContent[] | string
237
+ : undefined;
186
238
  }>;
187
239
  };
188
240
 
@@ -1,44 +1,16 @@
1
- import { HeadingBlockContent } from "../nodes/BlockContent/HeadingBlockContent/HeadingBlockContent";
2
- import { BulletListItemBlockContent } from "../nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent";
3
- import { NumberedListItemBlockContent } from "../nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent";
4
- import { ParagraphBlockContent } from "../nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent";
5
- import { PropSchema, TypesMatch } from "./blockTypes";
6
-
7
- export const defaultProps = {
8
- backgroundColor: {
9
- default: "transparent" as const,
10
- },
11
- textColor: {
12
- default: "black" as const, // TODO
13
- },
14
- textAlignment: {
15
- default: "left" as const,
16
- values: ["left", "center", "right", "justify"] as const,
17
- },
18
- } satisfies PropSchema;
19
-
20
- export type DefaultProps = typeof defaultProps;
1
+ import { Heading } from "../nodes/BlockContent/HeadingBlockContent/HeadingBlockContent";
2
+ import { BulletListItem } from "../nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent";
3
+ import { NumberedListItem } from "../nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent";
4
+ import { Paragraph } from "../nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent";
5
+ import { Image } from "../nodes/BlockContent/ImageBlockContent/ImageBlockContent";
6
+ import { BlockSchema } from "./blockTypes";
21
7
 
22
8
  export const defaultBlockSchema = {
23
- paragraph: {
24
- propSchema: defaultProps,
25
- node: ParagraphBlockContent,
26
- },
27
- heading: {
28
- propSchema: {
29
- ...defaultProps,
30
- level: { default: "1", values: ["1", "2", "3"] as const },
31
- },
32
- node: HeadingBlockContent,
33
- },
34
- bulletListItem: {
35
- propSchema: defaultProps,
36
- node: BulletListItemBlockContent,
37
- },
38
- numberedListItem: {
39
- propSchema: defaultProps,
40
- node: NumberedListItemBlockContent,
41
- },
42
- } as const;
9
+ paragraph: Paragraph,
10
+ heading: Heading,
11
+ bulletListItem: BulletListItem,
12
+ numberedListItem: NumberedListItem,
13
+ image: Image,
14
+ } as const satisfies BlockSchema;
43
15
 
44
- export type DefaultBlockSchema = TypesMatch<typeof defaultBlockSchema>;
16
+ export type DefaultBlockSchema = typeof defaultBlockSchema;
@@ -0,0 +1,16 @@
1
+ import { Props, PropSchema } from "./blockTypes";
2
+
3
+ export const defaultProps = {
4
+ backgroundColor: {
5
+ default: "default" as const,
6
+ },
7
+ textColor: {
8
+ default: "default" as const,
9
+ },
10
+ textAlignment: {
11
+ default: "left" as const,
12
+ values: ["left", "center", "right", "justify"] as const,
13
+ },
14
+ } satisfies PropSchema;
15
+
16
+ export type DefaultProps = Props<typeof defaultProps>;
@@ -56,20 +56,6 @@ NESTED BLOCKS
56
56
  transition: all 0.2s 0.1s;
57
57
  }
58
58
 
59
- [data-theme="light"]
60
- .blockGroup
61
- .blockGroup
62
- > .blockOuter:not([data-prev-depth-changed])::before {
63
- border-left: 1px solid #AFAFAF;
64
- }
65
-
66
- [data-theme="dark"]
67
- .blockGroup
68
- .blockGroup
69
- > .blockOuter:not([data-prev-depth-changed])::before {
70
- border-left: 1px solid #7F7F7F;
71
- }
72
-
73
59
  .blockGroup .blockGroup > .blockOuter[data-prev-depth-change="-2"]::before {
74
60
  height: 0;
75
61
  }
@@ -235,6 +221,80 @@ NESTED BLOCKS
235
221
  content: "▪";
236
222
  }
237
223
 
224
+ /* IMAGES */
225
+
226
+ [data-content-type="image"] .wrapper {
227
+ display: flex;
228
+ flex-direction: column;
229
+ justify-content: center;
230
+ user-select: none;
231
+ width: 100%;
232
+ }
233
+
234
+ [data-content-type="image"] .addImageButton {
235
+ display: flex;
236
+ flex-direction: row;
237
+ align-items: center;
238
+ gap: 8px;
239
+ background-color: whitesmoke;
240
+ border-radius: 4px;
241
+ cursor: pointer;
242
+ padding: 12px;
243
+ width: 100%;
244
+ }
245
+
246
+ [data-content-type="image"] .addImageButton:hover {
247
+ background-color: gainsboro;
248
+ }
249
+
250
+ [data-content-type="image"] .addImageButtonIcon {
251
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H4V19L13.2923 9.70649C13.6828 9.31595 14.3159 9.31591 14.7065 9.70641L20 15.0104V5ZM2 3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934ZM8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z'%3E%3C/path%3E%3C/svg%3E");
252
+ width: 24px;
253
+ height: 24px;
254
+ }
255
+
256
+ [data-content-type="image"] .addImageButtonText {
257
+ color: black;
258
+ }
259
+
260
+ [data-content-type="image"] .imageAndCaptionWrapper {
261
+ display: flex;
262
+ flex-direction: column;
263
+ border-radius: 4px;
264
+ }
265
+
266
+ [data-content-type="image"] .imageWrapper {
267
+ display: flex;
268
+ flex-direction: row;
269
+ align-items: center;
270
+ position: relative;
271
+ width: fit-content;
272
+ }
273
+
274
+ [data-content-type="image"] .image {
275
+ border-radius: 4px;
276
+ max-width: 100%;
277
+ }
278
+
279
+ [data-content-type="image"] .resizeHandle {
280
+ display: none;
281
+ position: absolute;
282
+ width: 8px;
283
+ height: 30px;
284
+ background-color: black;
285
+ border: 1px solid white;
286
+ border-radius: 4px;
287
+ cursor: ew-resize;
288
+ }
289
+
290
+ [data-content-type="image"] .imageWrapper:hover .resizeHandle {
291
+ display: block;
292
+ }
293
+
294
+ [data-content-type="image"] .caption {
295
+ font-size: 0.8em
296
+ }
297
+
238
298
  /* PLACEHOLDERS*/
239
299
 
240
300
  .isEmpty .inlineContent:before,
@@ -248,16 +308,6 @@ NESTED BLOCKS
248
308
  font-style: italic;
249
309
  }
250
310
 
251
- [data-theme="light"] .isEmpty .inlineContent:before,
252
- .isFilter .inlineContent:before {
253
- color: #CFCFCF;
254
- }
255
-
256
- [data-theme="dark"] .isEmpty .inlineContent:before,
257
- .isFilter .inlineContent:before {
258
- color: #7F7F7F;
259
- }
260
-
261
311
  /* TODO: would be nicer if defined from code */
262
312
 
263
313
  .blockContent.isEmpty.hasAnchor .inlineContent:before {
@@ -278,6 +328,10 @@ NESTED BLOCKS
278
328
  content: "List";
279
329
  }
280
330
 
331
+ .isEmpty .blockContent[data-content-type="captionedImage"] .inlineContent:before {
332
+ content: "Caption";
333
+ }
334
+
281
335
  /* TEXT COLORS */
282
336
  [data-text-color="gray"] {
283
337
  color: #9b9a97;
@@ -1,6 +1,6 @@
1
1
  import { mergeAttributes, Node } from "@tiptap/core";
2
2
  import { Fragment, Node as PMNode, Slice } from "prosemirror-model";
3
- import { TextSelection } from "prosemirror-state";
3
+ import { NodeSelection, TextSelection } from "prosemirror-state";
4
4
  import {
5
5
  blockToNode,
6
6
  inlineContentToNodes,
@@ -16,6 +16,7 @@ import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
16
16
  import styles from "./Block.module.css";
17
17
  import BlockAttributes from "./BlockAttributes";
18
18
  import { mergeCSSClasses } from "../../../shared/utils";
19
+ import { NonEditableBlockPlugin } from "../NonEditableBlockPlugin";
19
20
 
20
21
  declare module "@tiptap/core" {
21
22
  interface Commands<ReturnType> {
@@ -205,14 +206,20 @@ export const BlockContainer = Node.create<{
205
206
  // Replaces the blockContent node with one of the new type and
206
207
  // adds the provided props as attributes. Also preserves all
207
208
  // existing attributes that are compatible with the new type.
208
- state.tr.replaceWith(
209
- startPos,
210
- endPos,
211
- state.schema.nodes[newType].create({
212
- ...contentNode.attrs,
213
- ...block.props,
214
- })
215
- );
209
+ // Need to reset the selection since replacing the block content
210
+ // sets it to the next block.
211
+ state.tr
212
+ .replaceWith(
213
+ startPos,
214
+ endPos,
215
+ state.schema.nodes[newType].create({
216
+ ...contentNode.attrs,
217
+ ...block.props,
218
+ })
219
+ )
220
+ .setSelection(
221
+ new NodeSelection(state.tr.doc.resolve(startPos))
222
+ );
216
223
  } else {
217
224
  // Changes the blockContent node type and adds the provided props
218
225
  // as attributes. Also preserves all existing attributes that are
@@ -404,7 +411,7 @@ export const BlockContainer = Node.create<{
404
411
  },
405
412
 
406
413
  addProseMirrorPlugins() {
407
- return [PreviousBlockTypePlugin()];
414
+ return [PreviousBlockTypePlugin(), NonEditableBlockPlugin()];
408
415
  },
409
416
 
410
417
  addKeyboardShortcuts() {
@@ -577,37 +584,6 @@ export const BlockContainer = Node.create<{
577
584
  this.editor.commands.BNCreateBlock(
578
585
  this.editor.state.selection.anchor + 2
579
586
  ),
580
- "Mod-Alt-1": () =>
581
- this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
582
- type: "heading",
583
- props: {
584
- level: "1",
585
- },
586
- }),
587
- "Mod-Alt-2": () =>
588
- this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
589
- type: "heading",
590
- props: {
591
- level: "2",
592
- },
593
- }),
594
- "Mod-Alt-3": () =>
595
- this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
596
- type: "heading",
597
- props: {
598
- level: "3",
599
- },
600
- }),
601
- "Mod-Shift-7": () =>
602
- this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
603
- type: "bulletListItem",
604
- props: {},
605
- }),
606
- "Mod-Shift-8": () =>
607
- this.editor.commands.BNUpdateBlock(this.editor.state.selection.anchor, {
608
- type: "numberedListItem",
609
- props: {},
610
- }),
611
587
  };
612
588
  },
613
589
  });