@blocknote/core 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/BlockNoteSchema-DmZ6UQfY.cjs +11 -0
  2. package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +1 -0
  3. package/dist/BlockNoteSchema-oR047ACf.js +4275 -0
  4. package/dist/BlockNoteSchema-oR047ACf.js.map +1 -0
  5. package/dist/blocknote.cjs +4 -12
  6. package/dist/blocknote.cjs.map +1 -1
  7. package/dist/blocknote.js +3296 -7187
  8. package/dist/blocknote.js.map +1 -1
  9. package/dist/blocks.cjs +2 -0
  10. package/dist/blocks.cjs.map +1 -0
  11. package/dist/blocks.js +71 -0
  12. package/dist/blocks.js.map +1 -0
  13. package/dist/style.css +1 -1
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/dist/webpack-stats.json +1 -1
  16. package/package.json +19 -16
  17. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +1 -1
  18. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +2 -2
  19. package/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +34 -25
  20. package/src/api/blockManipulation/setupTestEnv.ts +0 -1
  21. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +4 -8
  22. package/src/api/clipboard/toClipboard/copyExtension.ts +1 -1
  23. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +128 -28
  24. package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +101 -41
  25. package/src/api/pmUtil.ts +1 -1
  26. package/src/api/positionMapping.test.ts +58 -15
  27. package/src/api/positionMapping.ts +2 -4
  28. package/src/blocks/Audio/block.ts +174 -0
  29. package/src/blocks/BlockNoteSchema.ts +59 -0
  30. package/src/blocks/Code/block.ts +303 -0
  31. package/src/blocks/Code/shiki.ts +73 -0
  32. package/src/blocks/File/block.ts +98 -0
  33. package/src/blocks/{FileBlockContent → File}/helpers/render/createAddFileButton.ts +5 -2
  34. package/src/blocks/{FileBlockContent → File}/helpers/render/createFileBlockWrapper.ts +15 -6
  35. package/src/blocks/{FileBlockContent → File}/helpers/render/createFileNameWithIcon.ts +15 -2
  36. package/src/blocks/{FileBlockContent → File}/helpers/render/createResizableFileBlockWrapper.ts +21 -2
  37. package/src/blocks/Heading/block.ts +138 -0
  38. package/src/blocks/Image/block.ts +190 -0
  39. package/src/blocks/ListItem/BulletListItem/block.ts +116 -0
  40. package/src/blocks/ListItem/CheckListItem/block.ts +175 -0
  41. package/src/blocks/ListItem/NumberedListItem/IndexingPlugin.ts +173 -0
  42. package/src/blocks/ListItem/NumberedListItem/block.ts +133 -0
  43. package/src/blocks/ListItem/ToggleListItem/block.ts +78 -0
  44. package/src/blocks/PageBreak/block.ts +72 -0
  45. package/src/blocks/{PageBreakBlockContent → PageBreak}/getPageBreakSlashMenuItems.ts +9 -7
  46. package/src/blocks/Paragraph/block.ts +80 -0
  47. package/src/blocks/Quote/block.ts +90 -0
  48. package/src/blocks/{TableBlockContent/TableBlockContent.ts → Table/block.ts} +169 -51
  49. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
  50. package/src/blocks/Video/block.ts +143 -0
  51. package/src/blocks/defaultBlockHelpers.ts +2 -2
  52. package/src/blocks/defaultBlockTypeGuards.ts +143 -174
  53. package/src/blocks/defaultBlocks.ts +107 -35
  54. package/src/blocks/defaultProps.ts +145 -4
  55. package/src/blocks/index.ts +26 -0
  56. package/src/blocks/utils/listItemEnterHandler.ts +42 -0
  57. package/src/editor/Block.css +54 -18
  58. package/src/editor/BlockNoteEditor.ts +251 -210
  59. package/src/editor/BlockNoteExtension.ts +92 -0
  60. package/src/editor/BlockNoteExtensions.ts +18 -17
  61. package/src/editor/defaultColors.ts +2 -2
  62. package/src/exporter/Exporter.ts +1 -1
  63. package/src/exporter/mapping.ts +1 -1
  64. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +3 -20
  65. package/src/extensions/BackgroundColor/BackgroundColorMark.ts +6 -8
  66. package/src/extensions/BlockChange/BlockChangePlugin.ts +2 -1
  67. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +2 -2
  68. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +2 -2
  69. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -1
  70. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -1
  71. package/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.ts +52 -0
  72. package/src/extensions/Collaboration/schemaMigration/migrationRules/index.ts +4 -0
  73. package/src/extensions/Collaboration/schemaMigration/migrationRules/migrationRule.ts +4 -0
  74. package/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts +78 -0
  75. package/src/extensions/Comments/CommentsPlugin.ts +1 -1
  76. package/src/extensions/FilePanel/FilePanelPlugin.ts +5 -10
  77. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +1 -1
  78. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +4 -3
  79. package/src/extensions/Placeholder/PlaceholderPlugin.ts +2 -2
  80. package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +1 -23
  81. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +0 -5
  82. package/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +6 -2
  83. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +24 -17
  84. package/src/extensions/TableHandles/TableHandlesPlugin.ts +2 -2
  85. package/src/extensions/TextAlignment/TextAlignmentExtension.ts +5 -11
  86. package/src/extensions/TextColor/TextColorExtension.ts +3 -17
  87. package/src/extensions/TextColor/TextColorMark.ts +4 -9
  88. package/src/extensions/UniqueID/UniqueID.ts +6 -13
  89. package/src/index.ts +2 -28
  90. package/src/schema/blocks/createSpec.ts +342 -169
  91. package/src/schema/blocks/internal.ts +77 -138
  92. package/src/schema/blocks/types.ts +264 -94
  93. package/src/schema/index.ts +1 -0
  94. package/src/schema/inlineContent/createSpec.ts +99 -21
  95. package/src/schema/inlineContent/internal.ts +16 -7
  96. package/src/schema/inlineContent/types.ts +24 -2
  97. package/src/schema/propTypes.ts +15 -9
  98. package/src/schema/schema.ts +209 -0
  99. package/src/schema/styles/createSpec.ts +79 -31
  100. package/src/schema/styles/internal.ts +61 -2
  101. package/src/schema/styles/types.ts +17 -3
  102. package/src/util/topo-sort.test.ts +125 -0
  103. package/src/util/topo-sort.ts +160 -0
  104. package/types/src/api/blockManipulation/commands/splitBlock/splitBlock.d.ts +2 -1
  105. package/types/src/api/blockManipulation/selections/selection.d.ts +1 -1
  106. package/types/src/api/blockManipulation/setupTestEnv.d.ts +29 -543
  107. package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +1 -1
  108. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +1 -1
  109. package/types/src/api/pmUtil.d.ts +1 -1
  110. package/types/src/blocks/Audio/block.d.ts +58 -0
  111. package/types/src/blocks/BlockNoteSchema.d.ts +18 -0
  112. package/types/src/blocks/{CodeBlockContent/CodeBlockContent.d.ts → Code/block.d.ts} +25 -26
  113. package/types/src/blocks/Code/shiki.d.ts +4 -0
  114. package/types/src/blocks/File/block.d.ts +37 -0
  115. package/types/src/blocks/File/helpers/render/createAddFileButton.d.ts +6 -0
  116. package/types/src/blocks/File/helpers/render/createFileBlockWrapper.d.ts +25 -0
  117. package/types/src/blocks/{FileBlockContent → File}/helpers/render/createFileNameWithIcon.d.ts +6 -2
  118. package/types/src/blocks/File/helpers/render/createResizableFileBlockWrapper.d.ts +31 -0
  119. package/types/src/blocks/Heading/block.d.ts +71 -0
  120. package/types/src/blocks/Image/block.d.ts +102 -0
  121. package/types/src/blocks/ListItem/BulletListItem/block.d.ts +25 -0
  122. package/types/src/blocks/ListItem/CheckListItem/block.d.ts +33 -0
  123. package/types/src/blocks/ListItem/NumberedListItem/IndexingPlugin.d.ts +8 -0
  124. package/types/src/blocks/ListItem/NumberedListItem/block.d.ts +33 -0
  125. package/types/src/blocks/ListItem/ToggleListItem/block.d.ts +25 -0
  126. package/types/src/blocks/PageBreak/block.d.ts +11 -0
  127. package/types/src/blocks/{PageBreakBlockContent → PageBreak}/getPageBreakSlashMenuItems.d.ts +4 -2
  128. package/types/src/blocks/Paragraph/block.d.ts +25 -0
  129. package/types/src/blocks/Quote/block.d.ts +17 -0
  130. package/types/src/blocks/Table/block.d.ts +21 -0
  131. package/types/src/blocks/Video/block.d.ts +67 -0
  132. package/types/src/blocks/defaultBlockHelpers.d.ts +1 -1
  133. package/types/src/blocks/defaultBlockTypeGuards.d.ts +15 -36
  134. package/types/src/blocks/defaultBlocks.d.ts +221 -1060
  135. package/types/src/blocks/defaultProps.d.ts +17 -1
  136. package/types/src/blocks/index.d.ts +24 -0
  137. package/types/src/blocks/utils/listItemEnterHandler.d.ts +2 -0
  138. package/types/src/editor/BlockNoteEditor.d.ts +33 -66
  139. package/types/src/editor/BlockNoteExtension.d.ts +67 -0
  140. package/types/src/editor/BlockNoteExtensions.d.ts +1 -1
  141. package/types/src/editor/defaultColors.d.ts +8 -76
  142. package/types/src/exporter/Exporter.d.ts +1 -1
  143. package/types/src/exporter/mapping.d.ts +1 -1
  144. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +4 -1
  145. package/types/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.d.ts +7 -0
  146. package/types/src/extensions/Collaboration/schemaMigration/migrationRules/index.d.ts +3 -0
  147. package/types/src/extensions/Collaboration/schemaMigration/migrationRules/migrationRule.d.ts +3 -0
  148. package/types/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.d.ts +2 -0
  149. package/types/src/extensions/Comments/CommentsPlugin.d.ts +1 -1
  150. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +4 -4
  151. package/types/src/extensions/TextColor/TextColorMark.d.ts +4 -1
  152. package/types/src/index.d.ts +2 -25
  153. package/types/src/schema/blocks/createSpec.d.ts +16 -36
  154. package/types/src/schema/blocks/internal.d.ts +11 -33
  155. package/types/src/schema/blocks/types.d.ts +181 -57
  156. package/types/src/schema/index.d.ts +1 -0
  157. package/types/src/schema/inlineContent/createSpec.d.ts +36 -2
  158. package/types/src/schema/inlineContent/internal.d.ts +7 -15
  159. package/types/src/schema/inlineContent/types.d.ts +15 -1
  160. package/types/src/schema/propTypes.d.ts +4 -4
  161. package/types/src/schema/schema.d.ts +40 -0
  162. package/types/src/schema/styles/createSpec.d.ts +6 -4
  163. package/types/src/schema/styles/internal.d.ts +6 -3
  164. package/types/src/schema/styles/types.d.ts +11 -2
  165. package/types/src/util/topo-sort.d.ts +18 -0
  166. package/types/src/util/topo-sort.test.d.ts +1 -0
  167. package/src/blocks/AudioBlockContent/AudioBlockContent.ts +0 -144
  168. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +0 -445
  169. package/src/blocks/FileBlockContent/FileBlockContent.ts +0 -100
  170. package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +0 -159
  171. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +0 -159
  172. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +0 -134
  173. package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +0 -299
  174. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.ts +0 -86
  175. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +0 -172
  176. package/src/blocks/ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.ts +0 -104
  177. package/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts +0 -49
  178. package/src/blocks/PageBreakBlockContent/schema.ts +0 -40
  179. package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +0 -78
  180. package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +0 -121
  181. package/src/blocks/VideoBlockContent/VideoBlockContent.ts +0 -158
  182. package/src/editor/BlockNoteSchema.ts +0 -107
  183. package/src/editor/BlockNoteTipTapEditor.ts +0 -335
  184. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +0 -99
  185. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +0 -90
  186. package/types/src/blocks/FileBlockContent/helpers/render/createAddFileButton.d.ts +0 -6
  187. package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +0 -9
  188. package/types/src/blocks/FileBlockContent/helpers/render/createResizableFileBlockWrapper.d.ts +0 -9
  189. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +0 -67
  190. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +0 -131
  191. package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +0 -46
  192. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +0 -55
  193. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListIndexingPlugin.d.ts +0 -2
  194. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +0 -58
  195. package/types/src/blocks/ListItemBlockContent/ToggleListItemBlockContent/ToggleListItemBlockContent.d.ts +0 -46
  196. package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +0 -31
  197. package/types/src/blocks/PageBreakBlockContent/schema.d.ts +0 -86
  198. package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +0 -52
  199. package/types/src/blocks/QuoteBlockContent/QuoteBlockContent.d.ts +0 -52
  200. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +0 -39
  201. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +0 -131
  202. package/types/src/editor/BlockNoteSchema.d.ts +0 -34
  203. package/types/src/editor/BlockNoteTipTapEditor.d.ts +0 -43
  204. /package/src/blocks/{AudioBlockContent → Audio}/parseAudioElement.ts +0 -0
  205. /package/src/blocks/{FileBlockContent → File}/helpers/parse/parseEmbedElement.ts +0 -0
  206. /package/src/blocks/{FileBlockContent → File}/helpers/parse/parseFigureElement.ts +0 -0
  207. /package/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createFigureWithCaption.ts +0 -0
  208. /package/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createLinkWithCaption.ts +0 -0
  209. /package/src/blocks/{FileBlockContent → File/helpers}/uploadToTmpFilesDotOrg_DEV_ONLY.ts +0 -0
  210. /package/src/blocks/{ImageBlockContent → Image}/parseImageElement.ts +0 -0
  211. /package/src/blocks/{ListItemBlockContent → ListItem}/ListItemKeyboardShortcuts.ts +0 -0
  212. /package/src/blocks/{ListItemBlockContent → ListItem}/getListItemContent.ts +0 -0
  213. /package/src/blocks/{TableBlockContent → Table}/TableExtension.ts +0 -0
  214. /package/src/blocks/{VideoBlockContent → Video}/parseVideoElement.ts +0 -0
  215. /package/types/src/blocks/{AudioBlockContent → Audio}/parseAudioElement.d.ts +0 -0
  216. /package/types/src/blocks/{FileBlockContent → File}/helpers/parse/parseEmbedElement.d.ts +0 -0
  217. /package/types/src/blocks/{FileBlockContent → File}/helpers/parse/parseFigureElement.d.ts +0 -0
  218. /package/types/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createFigureWithCaption.d.ts +0 -0
  219. /package/types/src/blocks/{FileBlockContent → File}/helpers/toExternalHTML/createLinkWithCaption.d.ts +0 -0
  220. /package/types/src/blocks/{FileBlockContent → File/helpers}/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +0 -0
  221. /package/types/src/blocks/{ImageBlockContent → Image}/parseImageElement.d.ts +0 -0
  222. /package/types/src/blocks/{ListItemBlockContent → ListItem}/ListItemKeyboardShortcuts.d.ts +0 -0
  223. /package/types/src/blocks/{ListItemBlockContent → ListItem}/getListItemContent.d.ts +0 -0
  224. /package/types/src/blocks/{TableBlockContent → Table}/TableExtension.d.ts +0 -0
  225. /package/types/src/blocks/{VideoBlockContent → Video}/parseVideoElement.d.ts +0 -0
@@ -1,13 +1,13 @@
1
1
  import { Node } from "@tiptap/core";
2
2
  import { PropSchema, Props } from "../propTypes.js";
3
3
  import { StyleSchema, Styles } from "../styles/types.js";
4
+ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
5
+ import { ViewMutationRecord } from "prosemirror-view";
4
6
 
5
7
  export type CustomInlineContentConfig = {
6
8
  type: string;
7
9
  content: "styled" | "none"; // | "plain"
8
- draggable?: boolean;
9
10
  readonly propSchema: PropSchema;
10
- // content: "inline" | "none" | "table";
11
11
  };
12
12
  // InlineContentConfig contains the "schema" info about an InlineContent type
13
13
  // i.e. what props it supports, what content it supports, etc.
@@ -20,7 +20,29 @@ export type InlineContentImplementation<T extends InlineContentConfig> =
20
20
  T extends "link" | "text"
21
21
  ? undefined
22
22
  : {
23
+ meta?: {
24
+ draggable?: boolean;
25
+ };
23
26
  node: Node;
27
+ toExternalHTML?: (
28
+ inlineContent: any,
29
+ editor: BlockNoteEditor<any, any, any>,
30
+ ) =>
31
+ | {
32
+ dom: HTMLElement | DocumentFragment;
33
+ contentDOM?: HTMLElement;
34
+ }
35
+ | undefined;
36
+ render: (
37
+ inlineContent: any,
38
+ updateInlineContent: (update: any) => void,
39
+ editor: BlockNoteEditor<any, any, any>,
40
+ ) => {
41
+ dom: HTMLElement | DocumentFragment;
42
+ contentDOM?: HTMLElement;
43
+ ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
44
+ destroy?: () => void;
45
+ };
24
46
  };
25
47
 
26
48
  export type InlineContentSchemaWithInlineContent<
@@ -34,17 +34,23 @@ export type Props<PSchema extends PropSchema> = {
34
34
  // for required props, get type from type of "default" value,
35
35
  // and if values are specified, get type from values
36
36
  [PName in keyof PSchema]: (
37
- PSchema[PName] extends { default: boolean } | { type: "boolean" }
38
- ? PSchema[PName]["values"] extends readonly boolean[]
39
- ? PSchema[PName]["values"][number]
37
+ NonNullable<PSchema[PName]> extends
38
+ | { default: boolean }
39
+ | { type: "boolean" }
40
+ ? NonNullable<PSchema[PName]>["values"] extends readonly boolean[]
41
+ ? NonNullable<PSchema[PName]>["values"][number]
40
42
  : boolean
41
- : PSchema[PName] extends { default: number } | { type: "number" }
42
- ? PSchema[PName]["values"] extends readonly number[]
43
- ? PSchema[PName]["values"][number]
43
+ : NonNullable<PSchema[PName]> extends
44
+ | { default: number }
45
+ | { type: "number" }
46
+ ? NonNullable<PSchema[PName]>["values"] extends readonly number[]
47
+ ? NonNullable<PSchema[PName]>["values"][number]
44
48
  : number
45
- : PSchema[PName] extends { default: string } | { type: "string" }
46
- ? PSchema[PName]["values"] extends readonly string[]
47
- ? PSchema[PName]["values"][number]
49
+ : NonNullable<PSchema[PName]> extends
50
+ | { default: string }
51
+ | { type: "string" }
52
+ ? NonNullable<PSchema[PName]>["values"] extends readonly string[]
53
+ ? NonNullable<PSchema[PName]>["values"][number]
48
54
  : string
49
55
  : never
50
56
  ) extends infer T
@@ -0,0 +1,209 @@
1
+ import { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
2
+ import { createDependencyGraph, toposortReverse } from "../util/topo-sort.js";
3
+ import {
4
+ BlockNoDefaults,
5
+ BlockSchema,
6
+ BlockSpecs,
7
+ InlineContentConfig,
8
+ InlineContentSchema,
9
+ InlineContentSpec,
10
+ InlineContentSpecs,
11
+ LooseBlockSpec,
12
+ PartialBlockNoDefaults,
13
+ StyleSchema,
14
+ StyleSpecs,
15
+ addNodeAndExtensionsToSpec,
16
+ getInlineContentSchemaFromSpecs,
17
+ getStyleSchemaFromSpecs,
18
+ } from "./index.js";
19
+
20
+ function removeUndefined<T extends Record<string, any> | undefined>(obj: T): T {
21
+ if (!obj) {
22
+ return obj;
23
+ }
24
+ return Object.fromEntries(
25
+ Object.entries(obj).filter(([, value]) => value !== undefined),
26
+ ) as T;
27
+ }
28
+
29
+ export class CustomBlockNoteSchema<
30
+ BSchema extends BlockSchema,
31
+ ISchema extends InlineContentSchema,
32
+ SSchema extends StyleSchema,
33
+ > {
34
+ // Helper so that you can use typeof schema.BlockNoteEditor
35
+ public readonly BlockNoteEditor: BlockNoteEditor<BSchema, ISchema, SSchema> =
36
+ "only for types" as any;
37
+
38
+ public readonly Block: BlockNoDefaults<BSchema, ISchema, SSchema> =
39
+ "only for types" as any;
40
+
41
+ public readonly PartialBlock: PartialBlockNoDefaults<
42
+ BSchema,
43
+ ISchema,
44
+ SSchema
45
+ > = "only for types" as any;
46
+
47
+ public inlineContentSpecs: InlineContentSpecs;
48
+ public styleSpecs: StyleSpecs;
49
+ public blockSpecs: {
50
+ [K in keyof BSchema]: K extends string
51
+ ? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]>
52
+ : never;
53
+ };
54
+
55
+ public blockSchema: BSchema;
56
+ public inlineContentSchema: ISchema;
57
+ public styleSchema: SSchema;
58
+
59
+ constructor(
60
+ private opts: {
61
+ blockSpecs: BlockSpecs;
62
+ inlineContentSpecs: InlineContentSpecs;
63
+ styleSpecs: StyleSpecs;
64
+ },
65
+ ) {
66
+ const {
67
+ blockSpecs,
68
+ inlineContentSpecs,
69
+ styleSpecs,
70
+ blockSchema,
71
+ inlineContentSchema,
72
+ styleSchema,
73
+ } = this.init();
74
+ this.blockSpecs = blockSpecs;
75
+ this.styleSpecs = styleSpecs;
76
+ this.styleSchema = styleSchema;
77
+ this.inlineContentSpecs = inlineContentSpecs;
78
+ this.blockSchema = blockSchema;
79
+ this.inlineContentSchema = inlineContentSchema;
80
+ }
81
+
82
+ private init() {
83
+ const dag = createDependencyGraph();
84
+ const defaultSet = new Set<string>();
85
+ dag.set("default", defaultSet);
86
+
87
+ for (const [key, specDef] of Object.entries(this.opts.blockSpecs)) {
88
+ if (specDef.implementation.runsBefore) {
89
+ dag.set(key, new Set(specDef.implementation.runsBefore));
90
+ } else {
91
+ defaultSet.add(key);
92
+ }
93
+ }
94
+ const sortedSpecs = toposortReverse(dag);
95
+ const defaultIndex = sortedSpecs.findIndex((set) => set.has("default"));
96
+
97
+ /**
98
+ * The priority of a block is described relative to the "default" block (an arbitrary block which can be used as the reference)
99
+ *
100
+ * Since blocks are topologically sorted, we can see what their relative position is to the "default" block
101
+ * Each layer away from the default block is 10 priority points (arbitrarily chosen)
102
+ * The default block is fixed at 101 (1 point higher than any tiptap extension, giving priority to custom blocks than any defaults)
103
+ *
104
+ * This is a bit of a hack, but it's a simple way to ensure that custom blocks are always rendered with higher priority than default blocks
105
+ * and that custom blocks are rendered in the order they are defined in the schema
106
+ */
107
+ const getPriority = (key: string) => {
108
+ const index = sortedSpecs.findIndex((set) => set.has(key));
109
+ // the default index should map to 101
110
+ // one before the default index is 91
111
+ // one after is 111
112
+ return 91 + (index + defaultIndex) * 10;
113
+ };
114
+
115
+ const blockSpecs = Object.fromEntries(
116
+ Object.entries(this.opts.blockSpecs).map(([key, blockSpec]) => {
117
+ return [
118
+ key,
119
+ addNodeAndExtensionsToSpec(
120
+ blockSpec.config,
121
+ blockSpec.implementation,
122
+ blockSpec.extensions,
123
+ getPriority(key),
124
+ ),
125
+ ];
126
+ }),
127
+ ) as {
128
+ [K in keyof BSchema]: K extends string
129
+ ? LooseBlockSpec<K, BSchema[K]["propSchema"], BSchema[K]["content"]>
130
+ : never;
131
+ };
132
+
133
+ return {
134
+ blockSpecs,
135
+ blockSchema: Object.fromEntries(
136
+ Object.entries(blockSpecs).map(([key, blockDef]) => {
137
+ return [key, blockDef.config];
138
+ }),
139
+ ) as any,
140
+ inlineContentSpecs: removeUndefined(this.opts.inlineContentSpecs),
141
+ styleSpecs: removeUndefined(this.opts.styleSpecs),
142
+ inlineContentSchema: getInlineContentSchemaFromSpecs(
143
+ this.opts.inlineContentSpecs,
144
+ ) as any,
145
+ styleSchema: getStyleSchemaFromSpecs(this.opts.styleSpecs) as any,
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Adds additional block specs to the current schema in a builder pattern.
151
+ * This method allows extending the schema after it has been created.
152
+ *
153
+ * @param additionalBlockSpecs - Additional block specs to add to the schema
154
+ * @returns The current schema instance for chaining
155
+ */
156
+ public extend<
157
+ AdditionalBlockSpecs extends BlockSpecs = Record<string, never>,
158
+ AdditionalInlineContentSpecs extends Record<
159
+ string,
160
+ InlineContentSpec<InlineContentConfig>
161
+ > = Record<string, never>,
162
+ AdditionalStyleSpecs extends StyleSpecs = Record<string, never>,
163
+ >(opts: {
164
+ blockSpecs?: AdditionalBlockSpecs;
165
+ inlineContentSpecs?: AdditionalInlineContentSpecs;
166
+ styleSpecs?: AdditionalStyleSpecs;
167
+ }): CustomBlockNoteSchema<
168
+ AdditionalBlockSpecs extends undefined | Record<string, never>
169
+ ? BSchema
170
+ : BSchema & {
171
+ [K in keyof AdditionalBlockSpecs]: K extends string
172
+ ? AdditionalBlockSpecs[K]["config"]
173
+ : never;
174
+ },
175
+ AdditionalInlineContentSpecs extends undefined | Record<string, never>
176
+ ? ISchema
177
+ : ISchema & {
178
+ [K in keyof AdditionalInlineContentSpecs]: AdditionalInlineContentSpecs[K]["config"];
179
+ },
180
+ AdditionalStyleSpecs extends undefined | Record<string, never>
181
+ ? SSchema
182
+ : SSchema & {
183
+ [K in keyof AdditionalStyleSpecs]: AdditionalStyleSpecs[K]["config"];
184
+ }
185
+ > {
186
+ // Merge the new specs with existing ones
187
+ Object.assign(this.opts.blockSpecs, opts.blockSpecs);
188
+ Object.assign(this.opts.inlineContentSpecs, opts.inlineContentSpecs);
189
+ Object.assign(this.opts.styleSpecs, opts.styleSpecs);
190
+
191
+ // Reinitialize the block specs with the merged specs
192
+ const {
193
+ blockSpecs,
194
+ inlineContentSpecs,
195
+ styleSpecs,
196
+ blockSchema,
197
+ inlineContentSchema,
198
+ styleSchema,
199
+ } = this.init();
200
+ this.blockSpecs = blockSpecs;
201
+ this.styleSpecs = styleSpecs;
202
+ this.styleSchema = styleSchema;
203
+ this.inlineContentSpecs = inlineContentSpecs;
204
+ this.blockSchema = blockSchema;
205
+ this.inlineContentSchema = inlineContentSchema;
206
+
207
+ return this as any;
208
+ }
209
+ }
@@ -1,7 +1,6 @@
1
1
  import { Mark } from "@tiptap/core";
2
2
 
3
- import { ParseRule } from "@tiptap/pm/model";
4
- import { UnreachableCaseError } from "../../util/typescript.js";
3
+ import { ParseRule, TagParseRule } from "@tiptap/pm/model";
5
4
  import {
6
5
  addStyleAttributes,
7
6
  createInternalStyleSpec,
@@ -10,21 +9,26 @@ import {
10
9
  import { StyleConfig, StyleSpec } from "./types.js";
11
10
 
12
11
  export type CustomStyleImplementation<T extends StyleConfig> = {
13
- render: T["propSchema"] extends "boolean"
14
- ? () => {
15
- dom: HTMLElement;
16
- contentDOM?: HTMLElement;
17
- }
18
- : (value: string) => {
19
- dom: HTMLElement;
20
- contentDOM?: HTMLElement;
21
- };
12
+ render: (value: T["propSchema"] extends "boolean" ? undefined : string) => {
13
+ dom: HTMLElement;
14
+ contentDOM?: HTMLElement;
15
+ };
16
+ toExternalHTML?: (
17
+ value: T["propSchema"] extends "boolean" ? undefined : string,
18
+ ) => {
19
+ dom: HTMLElement;
20
+ contentDOM?: HTMLElement;
21
+ };
22
+ parse?: (
23
+ element: HTMLElement,
24
+ ) => (T["propSchema"] extends "boolean" ? true : string) | undefined;
22
25
  };
23
26
 
24
- // TODO: support serialization
25
-
26
- export function getStyleParseRules(config: StyleConfig): ParseRule[] {
27
- return [
27
+ export function getStyleParseRules<T extends StyleConfig>(
28
+ config: T,
29
+ customParseFunction?: CustomStyleImplementation<T>["parse"],
30
+ ): ParseRule[] {
31
+ const rules: TagParseRule[] = [
28
32
  {
29
33
  tag: `[data-style-type="${config.type}"]`,
30
34
  contentElement: (element) => {
@@ -38,9 +42,29 @@ export function getStyleParseRules(config: StyleConfig): ParseRule[] {
38
42
  },
39
43
  },
40
44
  ];
45
+
46
+ if (customParseFunction) {
47
+ rules.push({
48
+ tag: "*",
49
+ getAttrs(node: string | HTMLElement) {
50
+ if (typeof node === "string") {
51
+ return false;
52
+ }
53
+
54
+ const stringValue = customParseFunction?.(node);
55
+
56
+ if (stringValue === undefined) {
57
+ return false;
58
+ }
59
+
60
+ return { stringValue };
61
+ },
62
+ });
63
+ }
64
+ return rules;
41
65
  }
42
66
 
43
- export function createStyleSpec<T extends StyleConfig>(
67
+ export function createStyleSpec<const T extends StyleConfig>(
44
68
  styleConfig: T,
45
69
  styleImplementation: CustomStyleImplementation<T>,
46
70
  ): StyleSpec<T> {
@@ -52,25 +76,14 @@ export function createStyleSpec<T extends StyleConfig>(
52
76
  },
53
77
 
54
78
  parseHTML() {
55
- return getStyleParseRules(styleConfig);
79
+ return getStyleParseRules(styleConfig, styleImplementation.parse);
56
80
  },
57
81
 
58
82
  renderHTML({ mark }) {
59
- let renderResult: {
60
- dom: HTMLElement;
61
- contentDOM?: HTMLElement;
62
- };
83
+ const renderResult = (
84
+ styleImplementation.toExternalHTML || styleImplementation.render
85
+ )(mark.attrs.stringValue);
63
86
 
64
- if (styleConfig.propSchema === "boolean") {
65
- // @ts-ignore not sure why this is complaining
66
- renderResult = styleImplementation.render();
67
- } else if (styleConfig.propSchema === "string") {
68
- renderResult = styleImplementation.render(mark.attrs.stringValue);
69
- } else {
70
- throw new UnreachableCaseError(styleConfig.propSchema);
71
- }
72
-
73
- // const renderResult = styleImplementation.render();
74
87
  return addStyleAttributes(
75
88
  renderResult,
76
89
  styleConfig.type,
@@ -78,9 +91,44 @@ export function createStyleSpec<T extends StyleConfig>(
78
91
  styleConfig.propSchema,
79
92
  );
80
93
  },
94
+
95
+ addMarkView() {
96
+ return ({ mark }) => {
97
+ const renderResult = styleImplementation.render(mark.attrs.stringValue);
98
+
99
+ return addStyleAttributes(
100
+ renderResult,
101
+ styleConfig.type,
102
+ mark.attrs.stringValue,
103
+ styleConfig.propSchema,
104
+ );
105
+ };
106
+ },
81
107
  });
82
108
 
83
109
  return createInternalStyleSpec(styleConfig, {
84
110
  mark,
111
+ render: (value) => {
112
+ const renderResult = styleImplementation.render(value as any);
113
+
114
+ return addStyleAttributes(
115
+ renderResult,
116
+ styleConfig.type,
117
+ value,
118
+ styleConfig.propSchema,
119
+ );
120
+ },
121
+ toExternalHTML: (value) => {
122
+ const renderResult = (
123
+ styleImplementation.toExternalHTML || styleImplementation.render
124
+ )(value as any);
125
+
126
+ return addStyleAttributes(
127
+ renderResult,
128
+ styleConfig.type,
129
+ value,
130
+ styleConfig.propSchema,
131
+ );
132
+ },
85
133
  });
86
134
  }
@@ -1,4 +1,5 @@
1
1
  import { Attributes, Mark } from "@tiptap/core";
2
+ import { DOMSerializer } from "@tiptap/pm/model";
2
3
  import {
3
4
  StyleConfig,
4
5
  StyleImplementation,
@@ -55,7 +56,7 @@ export function addStyleAttributes<
55
56
  element.dom.setAttribute("data-value", styleValue as string);
56
57
  }
57
58
 
58
- if (element.contentDOM !== undefined) {
59
+ if (element.contentDOM) {
59
60
  element.contentDOM.setAttribute("data-editable", "");
60
61
  }
61
62
 
@@ -66,7 +67,7 @@ export function addStyleAttributes<
66
67
  // config and implementation that conform to the type of Config
67
68
  export function createInternalStyleSpec<T extends StyleConfig>(
68
69
  config: T,
69
- implementation: StyleImplementation,
70
+ implementation: StyleImplementation<T>,
70
71
  ) {
71
72
  return {
72
73
  config,
@@ -85,6 +86,64 @@ export function createStyleSpecFromTipTapMark<
85
86
  },
86
87
  {
87
88
  mark,
89
+ render(value, editor) {
90
+ const toDOM = editor.pmSchema.marks[mark.name].spec.toDOM;
91
+
92
+ if (toDOM === undefined) {
93
+ throw new Error(
94
+ "This block has no default HTML serialization as its corresponding TipTap node doesn't implement `renderHTML`.",
95
+ );
96
+ }
97
+
98
+ const markInstance = editor.pmSchema.mark(mark.name, {
99
+ stringValue: value,
100
+ });
101
+
102
+ const renderSpec = DOMSerializer.renderSpec(
103
+ document,
104
+ toDOM(markInstance, true),
105
+ );
106
+
107
+ if (typeof renderSpec !== "object" || !("dom" in renderSpec)) {
108
+ throw new Error(
109
+ "Cannot use this block's default HTML serialization as its corresponding TipTap mark's `renderHTML` function does not return an object with the `dom` property.",
110
+ );
111
+ }
112
+
113
+ return renderSpec as {
114
+ dom: HTMLElement;
115
+ contentDOM?: HTMLElement;
116
+ };
117
+ },
118
+ toExternalHTML(value, editor) {
119
+ const toDOM = editor.pmSchema.marks[mark.name].spec.toDOM;
120
+
121
+ if (toDOM === undefined) {
122
+ throw new Error(
123
+ "This block has no default HTML serialization as its corresponding TipTap node doesn't implement `renderHTML`.",
124
+ );
125
+ }
126
+
127
+ const markInstance = editor.pmSchema.mark(mark.name, {
128
+ stringValue: value,
129
+ });
130
+
131
+ const renderSpec = DOMSerializer.renderSpec(
132
+ document,
133
+ toDOM(markInstance, true),
134
+ );
135
+
136
+ if (typeof renderSpec !== "object" || !("dom" in renderSpec)) {
137
+ throw new Error(
138
+ "Cannot use this block's default HTML serialization as its corresponding TipTap mark's `renderHTML` function does not return an object with the `dom` property.",
139
+ );
140
+ }
141
+
142
+ return renderSpec as {
143
+ dom: HTMLElement;
144
+ contentDOM?: HTMLElement;
145
+ };
146
+ },
88
147
  },
89
148
  );
90
149
  }
@@ -1,4 +1,5 @@
1
1
  import { Mark } from "@tiptap/core";
2
+ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
2
3
 
3
4
  export type StylePropSchema = "boolean" | "string"; // TODO: use PropSchema as name? Use objects as type similar to blocks?
4
5
 
@@ -7,20 +8,33 @@ export type StylePropSchema = "boolean" | "string"; // TODO: use PropSchema as n
7
8
  export type StyleConfig = {
8
9
  type: string;
9
10
  readonly propSchema: StylePropSchema;
10
- // content: "inline" | "none" | "table";
11
11
  };
12
12
 
13
13
  // StyleImplementation contains the "implementation" info about a Style element.
14
14
  // Currently, the implementation is always a TipTap Mark
15
- export type StyleImplementation = {
15
+ export type StyleImplementation<T extends StyleConfig> = {
16
16
  mark: Mark;
17
+ render: (
18
+ value: T["propSchema"] extends "boolean" ? undefined : string,
19
+ editor: BlockNoteEditor<any, any, any>,
20
+ ) => {
21
+ dom: HTMLElement;
22
+ contentDOM?: HTMLElement;
23
+ };
24
+ toExternalHTML?: (
25
+ value: T["propSchema"] extends "boolean" ? undefined : string,
26
+ editor: BlockNoteEditor<any, any, any>,
27
+ ) => {
28
+ dom: HTMLElement;
29
+ contentDOM?: HTMLElement;
30
+ };
17
31
  };
18
32
 
19
33
  // Container for both the config and implementation of a Style,
20
34
  // and the type of `implementation` is based on that of the config
21
35
  export type StyleSpec<T extends StyleConfig> = {
22
36
  config: T;
23
- implementation: StyleImplementation;
37
+ implementation: StyleImplementation<T>;
24
38
  };
25
39
 
26
40
  // A Schema contains all the types (Configs) supported in an editor
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { toposort as batchingToposort, toposortReverse } from "./topo-sort.js";
3
+
4
+ describe("toposort", () => {
5
+ it("toposorts an empty graph", () => {
6
+ expect(batchingToposort(new Map())).toEqual([]);
7
+ });
8
+
9
+ it("toposorts a simple DAG", () => {
10
+ expect(
11
+ batchingToposort(
12
+ new Map([
13
+ ["a", ["b"]],
14
+ ["b", ["c"]],
15
+ ["c", []],
16
+ ]),
17
+ ),
18
+ ).toEqual([new Set(["a"]), new Set(["b"]), new Set(["c"])]);
19
+ });
20
+
21
+ it("toposorts a richer DAG", () => {
22
+ expect(
23
+ batchingToposort(
24
+ new Map([
25
+ ["a", ["c"]],
26
+ ["b", ["c"]],
27
+ ["c", []],
28
+ ]),
29
+ ),
30
+ ).toEqual([new Set(["a", "b"]), new Set(["c"])]);
31
+ });
32
+
33
+ it("toposorts a complex DAG", () => {
34
+ expect(
35
+ batchingToposort(
36
+ new Map([
37
+ ["a", ["c", "f"]],
38
+ ["b", ["d", "e"]],
39
+ ["c", ["f"]],
40
+ ["d", ["f", "g"]],
41
+ ["e", ["h"]],
42
+ ["f", ["i"]],
43
+ ["g", ["j"]],
44
+ ["h", ["j"]],
45
+ ["i", []],
46
+ ["j", []],
47
+ ]),
48
+ ),
49
+ ).toEqual([
50
+ new Set(["a", "b"]),
51
+ new Set(["c", "d", "e"]),
52
+ new Set(["f", "g", "h"]),
53
+ new Set(["i", "j"]),
54
+ ]);
55
+ });
56
+
57
+ it("errors on a small cyclic graph", () => {
58
+ const dg = new Map([
59
+ ["a", ["b"]],
60
+ ["b", ["a"]],
61
+ ["c", []],
62
+ ]);
63
+ const sortCyclicGraph = () => {
64
+ batchingToposort(dg);
65
+ };
66
+ expect(sortCyclicGraph).toThrowError(Error);
67
+ });
68
+
69
+ it("errors on a larger cyclic graph", () => {
70
+ const dg = new Map([
71
+ ["a", ["b", "c"]],
72
+ ["b", ["c"]],
73
+ ["c", ["d", "e"]],
74
+ ["d", ["b"]],
75
+ ["e", []],
76
+ ]);
77
+ const sortCyclicGraph = () => {
78
+ batchingToposort(dg);
79
+ };
80
+ expect(sortCyclicGraph).toThrowError(Error);
81
+ });
82
+
83
+ it("can sort a graph with missing dependencies", () => {
84
+ const dg = new Map([
85
+ ["a", ["non-existent-node"]],
86
+ ["b", ["c"]],
87
+ ["c", []],
88
+ ]);
89
+ const result = batchingToposort(dg);
90
+ expect(result).toEqual([
91
+ new Set(["a", "b"]),
92
+ new Set(["non-existent-node", "c"]),
93
+ ]);
94
+ });
95
+ });
96
+
97
+ describe("toposortReverse", () => {
98
+ it("can sort stuff", () => {
99
+ const graph = new Map([
100
+ ["floss", ["brushTeeth"]],
101
+ ["drinkCoffee", ["wakeUp"]],
102
+ ["wakeUp", []],
103
+ ["brushTeeth", ["drinkCoffee", "eatBreakfast"]],
104
+ ["eatBreakfast", ["wakeUp"]],
105
+ ]);
106
+ const result = toposortReverse(graph);
107
+ expect(result).toMatchInlineSnapshot(`
108
+ [
109
+ Set {
110
+ "wakeUp",
111
+ },
112
+ Set {
113
+ "drinkCoffee",
114
+ "eatBreakfast",
115
+ },
116
+ Set {
117
+ "brushTeeth",
118
+ },
119
+ Set {
120
+ "floss",
121
+ },
122
+ ]
123
+ `);
124
+ });
125
+ });