@blocknote/core 0.17.1 → 0.18.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 (212) hide show
  1. package/dist/angular-html-HQGguTAE.js +33 -0
  2. package/dist/angular-html-HQGguTAE.js.map +1 -0
  3. package/dist/angular-ts-q9PqJiJb.js +22 -0
  4. package/dist/angular-ts-q9PqJiJb.js.map +1 -0
  5. package/dist/astro-0iWgpDaK.js +17 -0
  6. package/dist/astro-0iWgpDaK.js.map +1 -0
  7. package/dist/blade-C3Z8AhvY.js +19 -0
  8. package/dist/blade-C3Z8AhvY.js.map +1 -0
  9. package/dist/blocknote.js +2338 -1491
  10. package/dist/blocknote.js.map +1 -1
  11. package/dist/blocknote.umd.cjs +10 -7
  12. package/dist/blocknote.umd.cjs.map +1 -1
  13. package/dist/c-TKJGJdXV.js +7 -0
  14. package/dist/c-TKJGJdXV.js.map +1 -0
  15. package/dist/coffee-CN_y6cG3.js +9 -0
  16. package/dist/coffee-CN_y6cG3.js.map +1 -0
  17. package/dist/cpp-Be_e67JE.js +19 -0
  18. package/dist/cpp-Be_e67JE.js.map +1 -0
  19. package/dist/css-DHLSoXzW.js +7 -0
  20. package/dist/css-DHLSoXzW.js.map +1 -0
  21. package/dist/glsl-8qSUIm5B.js +9 -0
  22. package/dist/glsl-8qSUIm5B.js.map +1 -0
  23. package/dist/graphql-D7_Dk2ma.js +15 -0
  24. package/dist/graphql-D7_Dk2ma.js.map +1 -0
  25. package/dist/haml-S3dmcfEW.js +11 -0
  26. package/dist/haml-S3dmcfEW.js.map +1 -0
  27. package/dist/handlebars-DaIrqVg3.js +15 -0
  28. package/dist/handlebars-DaIrqVg3.js.map +1 -0
  29. package/dist/html-Bx3A18fV.js +11 -0
  30. package/dist/html-Bx3A18fV.js.map +1 -0
  31. package/dist/html-derivative-Cf3KTZBS.js +9 -0
  32. package/dist/html-derivative-Cf3KTZBS.js.map +1 -0
  33. package/dist/http-BphR83YX.js +15 -0
  34. package/dist/http-BphR83YX.js.map +1 -0
  35. package/dist/imba-CmP25v0O.js +9 -0
  36. package/dist/imba-CmP25v0O.js.map +1 -0
  37. package/dist/java-Dg4kxH6C.js +7 -0
  38. package/dist/java-Dg4kxH6C.js.map +1 -0
  39. package/dist/javascript-CipAzIn1.js +7 -0
  40. package/dist/javascript-CipAzIn1.js.map +1 -0
  41. package/dist/jinja-tioldiz6.js +12 -0
  42. package/dist/jinja-tioldiz6.js.map +1 -0
  43. package/dist/jison-DWJFEE_I.js +9 -0
  44. package/dist/jison-DWJFEE_I.js.map +1 -0
  45. package/dist/json-_04EL6MS.js +7 -0
  46. package/dist/json-_04EL6MS.js.map +1 -0
  47. package/dist/json5-Dwmp5XFI.js +7 -0
  48. package/dist/json5-Dwmp5XFI.js.map +1 -0
  49. package/dist/jsonc-LqD5auy0.js +7 -0
  50. package/dist/jsonc-LqD5auy0.js.map +1 -0
  51. package/dist/jsonl-B4yVuYQH.js +7 -0
  52. package/dist/jsonl-B4yVuYQH.js.map +1 -0
  53. package/dist/jsx-Mg4WaD5k.js +7 -0
  54. package/dist/jsx-Mg4WaD5k.js.map +1 -0
  55. package/dist/julia-CWi-ZdpN.js +17 -0
  56. package/dist/julia-CWi-ZdpN.js.map +1 -0
  57. package/dist/less-BBvTHIGe.js +7 -0
  58. package/dist/less-BBvTHIGe.js.map +1 -0
  59. package/dist/markdown-DAXqtk9a.js +7 -0
  60. package/dist/markdown-DAXqtk9a.js.map +1 -0
  61. package/dist/marko-0aaNgUGV.js +15 -0
  62. package/dist/marko-0aaNgUGV.js.map +1 -0
  63. package/dist/mdc-hXJ2B4O0.js +13 -0
  64. package/dist/mdc-hXJ2B4O0.js.map +1 -0
  65. package/dist/mdx-CSCKbb_f.js +7 -0
  66. package/dist/mdx-CSCKbb_f.js.map +1 -0
  67. package/dist/php-B_-4RJ09.js +19 -0
  68. package/dist/php-B_-4RJ09.js.map +1 -0
  69. package/dist/postcss-SJfTvo_B.js +7 -0
  70. package/dist/postcss-SJfTvo_B.js.map +1 -0
  71. package/dist/pug-3q2tx0nf.js +13 -0
  72. package/dist/pug-3q2tx0nf.js.map +1 -0
  73. package/dist/python-Dpup1-fE.js +7 -0
  74. package/dist/python-Dpup1-fE.js.map +1 -0
  75. package/dist/r-Chyv38Fv.js +7 -0
  76. package/dist/r-Chyv38Fv.js.map +1 -0
  77. package/dist/regexp-BF0hfxNW.js +7 -0
  78. package/dist/regexp-BF0hfxNW.js.map +1 -0
  79. package/dist/sass-p2RMoqDT.js +7 -0
  80. package/dist/sass-p2RMoqDT.js.map +1 -0
  81. package/dist/scss-DmlHSoOY.js +9 -0
  82. package/dist/scss-DmlHSoOY.js.map +1 -0
  83. package/dist/shellscript-CZpPN8_x.js +7 -0
  84. package/dist/shellscript-CZpPN8_x.js.map +1 -0
  85. package/dist/sql-DtlkUz2m.js +7 -0
  86. package/dist/sql-DtlkUz2m.js.map +1 -0
  87. package/dist/style.css +1 -1
  88. package/dist/stylus-DEr8eSLm.js +7 -0
  89. package/dist/stylus-DEr8eSLm.js.map +1 -0
  90. package/dist/svelte-BRIJF62h.js +15 -0
  91. package/dist/svelte-BRIJF62h.js.map +1 -0
  92. package/dist/ts-tags-qkUtuI0N.js +42 -0
  93. package/dist/ts-tags-qkUtuI0N.js.map +1 -0
  94. package/dist/tsx-DTfbgJxi.js +7 -0
  95. package/dist/tsx-DTfbgJxi.js.map +1 -0
  96. package/dist/typescript-CCd4aQHh.js +7 -0
  97. package/dist/typescript-CCd4aQHh.js.map +1 -0
  98. package/dist/vue-B3TdbERm.js +32 -0
  99. package/dist/vue-B3TdbERm.js.map +1 -0
  100. package/dist/vue-html-BGmTBZk0.js +11 -0
  101. package/dist/vue-html-BGmTBZk0.js.map +1 -0
  102. package/dist/wasm-VDIDph3E.js +7 -0
  103. package/dist/wasm-VDIDph3E.js.map +1 -0
  104. package/dist/webpack-stats.json +1 -1
  105. package/dist/wgsl-2np_U3Z8.js +7 -0
  106. package/dist/wgsl-2np_U3Z8.js.map +1 -0
  107. package/dist/xml-CNyphW9R.js +9 -0
  108. package/dist/xml-CNyphW9R.js.map +1 -0
  109. package/dist/yaml-DxFiVFcM.js +7 -0
  110. package/dist/yaml-DxFiVFcM.js.map +1 -0
  111. package/package.json +6 -4
  112. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +30 -0
  113. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +25 -0
  114. package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +40 -0
  115. package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +10 -0
  116. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +40 -0
  117. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +30 -0
  118. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +87 -0
  119. package/src/api/clipboard/clipboard.test.ts +1 -1
  120. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -0
  121. package/src/api/clipboard/fromClipboard/handleVSCodePaste.ts +49 -0
  122. package/src/api/clipboard/fromClipboard/pasteExtension.ts +6 -0
  123. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html +1 -0
  124. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html +1 -0
  125. package/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html +1 -0
  126. package/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html +1 -0
  127. package/src/api/exporters/html/__snapshots__/codeBlock/python/external.html +1 -0
  128. package/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html +1 -0
  129. package/src/api/exporters/html/__snapshots__/image/basic/internal.html +1 -1
  130. package/src/api/exporters/html/__snapshots__/image/nested/internal.html +1 -1
  131. package/src/api/exporters/html/__snapshots__/image/noName/internal.html +1 -1
  132. package/src/api/exporters/html/__snapshots__/image/noPreview/internal.html +1 -1
  133. package/src/api/exporters/html/__snapshots__/lists/basic/internal.html +1 -1
  134. package/src/api/exporters/html/__snapshots__/lists/nested/internal.html +1 -1
  135. package/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +1 -1
  136. package/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html +1 -1
  137. package/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +1 -1
  138. package/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html +1 -1
  139. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html +1 -1
  140. package/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html +1 -1
  141. package/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html +1 -1
  142. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html +1 -1
  143. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html +1 -1
  144. package/src/api/exporters/html/__snapshots__/table/allColWidths/external.html +1 -0
  145. package/src/api/exporters/html/__snapshots__/table/allColWidths/internal.html +1 -0
  146. package/src/api/exporters/html/__snapshots__/table/basic/external.html +1 -0
  147. package/src/api/exporters/html/__snapshots__/table/basic/internal.html +1 -0
  148. package/src/api/exporters/html/__snapshots__/table/mixedColWidths/external.html +1 -0
  149. package/src/api/exporters/html/__snapshots__/table/mixedColWidths/internal.html +1 -0
  150. package/src/api/exporters/html/internalHTMLSerializer.ts +2 -7
  151. package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +20 -15
  152. package/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md +3 -0
  153. package/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md +2 -0
  154. package/src/api/exporters/markdown/__snapshots__/codeBlock/python/markdown.md +3 -0
  155. package/src/api/exporters/markdown/__snapshots__/table/allColWidths/markdown.md +5 -0
  156. package/src/api/exporters/markdown/__snapshots__/table/basic/markdown.md +5 -0
  157. package/src/api/exporters/markdown/__snapshots__/table/mixedColWidths/markdown.md +5 -0
  158. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +711 -0
  159. package/src/api/nodeConversions/blockToNode.ts +13 -2
  160. package/src/api/nodeConversions/nodeToBlock.ts +11 -1
  161. package/src/api/parsers/html/__snapshots__/parse-notion-html.json +5 -0
  162. package/src/api/parsers/markdown/parseMarkdown.ts +1 -1
  163. package/src/api/testUtil/cases/defaultSchema.ts +95 -0
  164. package/src/api/testUtil/partialBlockTestUtil.ts +26 -1
  165. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +369 -0
  166. package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +96 -0
  167. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +7 -0
  168. package/src/blocks/TableBlockContent/TableBlockContent.ts +83 -8
  169. package/src/blocks/TableBlockContent/TableExtension.ts +10 -1
  170. package/src/blocks/defaultBlocks.ts +4 -0
  171. package/src/editor/Block.css +49 -2
  172. package/src/editor/editor.css +33 -5
  173. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +8 -0
  174. package/src/extensions/Placeholder/PlaceholderPlugin.ts +5 -0
  175. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +5 -0
  176. package/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +2 -1
  177. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +18 -0
  178. package/src/extensions/TableHandles/TableHandlesPlugin.ts +224 -119
  179. package/src/i18n/locales/ar.ts +6 -0
  180. package/src/i18n/locales/de.ts +299 -294
  181. package/src/i18n/locales/en.ts +6 -0
  182. package/src/i18n/locales/es.ts +310 -274
  183. package/src/i18n/locales/fr.ts +6 -0
  184. package/src/i18n/locales/is.ts +6 -0
  185. package/src/i18n/locales/ja.ts +6 -0
  186. package/src/i18n/locales/ko.ts +6 -0
  187. package/src/i18n/locales/nl.ts +6 -0
  188. package/src/i18n/locales/pl.ts +6 -0
  189. package/src/i18n/locales/pt.ts +6 -0
  190. package/src/i18n/locales/ru.ts +6 -0
  191. package/src/i18n/locales/vi.ts +6 -0
  192. package/src/i18n/locales/zh.ts +6 -0
  193. package/src/index.ts +10 -5
  194. package/src/schema/blocks/types.ts +2 -0
  195. package/types/src/api/blockManipulation/setupTestEnv.d.ts +22 -8
  196. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  197. package/types/src/api/clipboard/fromClipboard/handleVSCodePaste.d.ts +3 -0
  198. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +2 -3
  199. package/types/src/api/testUtil/cases/customBlocks.d.ts +22 -8
  200. package/types/src/api/testUtil/cases/customInlineContent.d.ts +22 -8
  201. package/types/src/api/testUtil/cases/customStyles.d.ts +22 -8
  202. package/types/src/blocks/CodeBlockContent/CodeBlockContent.d.ts +57 -0
  203. package/types/src/blocks/CodeBlockContent/defaultSupportedLanguages.d.ts +6 -0
  204. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +0 -12
  205. package/types/src/blocks/TableBlockContent/TableExtension.d.ts +3 -0
  206. package/types/src/blocks/defaultBlocks.d.ts +45 -16
  207. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +9 -5
  208. package/types/src/i18n/locales/de.d.ts +6 -0
  209. package/types/src/i18n/locales/en.d.ts +6 -0
  210. package/types/src/i18n/locales/es.d.ts +6 -0
  211. package/types/src/index.d.ts +7 -5
  212. package/types/src/schema/blocks/types.d.ts +2 -0
@@ -161,7 +161,8 @@ export function tableContentToNodes<
161
161
 
162
162
  for (const row of tableContent.rows) {
163
163
  const columnNodes: Node[] = [];
164
- for (const cell of row.cells) {
164
+ for (let i = 0; i < row.cells.length; i++) {
165
+ const cell = row.cells[i];
165
166
  let pNode: Node;
166
167
  if (!cell) {
167
168
  pNode = schema.nodes["tableParagraph"].create({});
@@ -172,7 +173,17 @@ export function tableContentToNodes<
172
173
  pNode = schema.nodes["tableParagraph"].create({}, textNodes);
173
174
  }
174
175
 
175
- const cellNode = schema.nodes["tableCell"].create({}, pNode);
176
+ const cellNode = schema.nodes["tableCell"].create(
177
+ {
178
+ // The colwidth array should have multiple values when the colspan of
179
+ // a cell is greater than 1. However, this is not yet implemented so
180
+ // we can always assume a length of 1.
181
+ colwidth: tableContent.columnWidths?.[i]
182
+ ? [tableContent.columnWidths[i]]
183
+ : null,
184
+ },
185
+ pNode
186
+ );
176
187
  columnNodes.push(cellNode);
177
188
  }
178
189
  const rowNode = schema.nodes["tableRow"].create({}, columnNodes);
@@ -30,14 +30,24 @@ export function contentNodeToTableContent<
30
30
  >(contentNode: Node, inlineContentSchema: I, styleSchema: S) {
31
31
  const ret: TableContent<I, S> = {
32
32
  type: "tableContent",
33
+ columnWidths: [],
33
34
  rows: [],
34
35
  };
35
36
 
36
- contentNode.content.forEach((rowNode) => {
37
+ contentNode.content.forEach((rowNode, _offset, index) => {
37
38
  const row: TableContent<I, S>["rows"][0] = {
38
39
  cells: [],
39
40
  };
40
41
 
42
+ if (index === 0) {
43
+ rowNode.content.forEach((cellNode) => {
44
+ // The colwidth array should have multiple values when the colspan of a
45
+ // cell is greater than 1. However, this is not yet implemented so we
46
+ // can always assume a length of 1.
47
+ ret.columnWidths.push(cellNode.attrs.colwidth?.[0] || undefined);
48
+ });
49
+ }
50
+
41
51
  rowNode.content.forEach((cellNode) => {
42
52
  row.cells.push(
43
53
  contentNodeToInlineContent(
@@ -370,6 +370,11 @@
370
370
  },
371
371
  "content": {
372
372
  "type": "tableContent",
373
+ "columnWidths": [
374
+ null,
375
+ null,
376
+ null
377
+ ],
373
378
  "rows": [
374
379
  {
375
380
  "cells": [
@@ -12,7 +12,7 @@ import { HTMLToBlocks } from "../html/parseHTML.js";
12
12
  // modified version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js
13
13
  // that outputs a data-language attribute instead of a CSS class (e.g.: language-typescript)
14
14
  function code(state: any, node: any) {
15
- const value = node.value ? node.value + "\n" : "";
15
+ const value = node.value ? node.value : "";
16
16
  /** @type {Properties} */
17
17
  const properties: any = {};
18
18
 
@@ -175,6 +175,33 @@ export const defaultSchemaTestCases: EditorTestCases<
175
175
  },
176
176
  ],
177
177
  },
178
+ {
179
+ name: "codeBlock/empty",
180
+ blocks: [
181
+ {
182
+ type: "codeBlock",
183
+ },
184
+ ],
185
+ },
186
+ {
187
+ name: "codeBlock/defaultLanguage",
188
+ blocks: [
189
+ {
190
+ type: "codeBlock",
191
+ content: "console.log('Hello, world!');",
192
+ },
193
+ ],
194
+ },
195
+ {
196
+ name: "codeBlock/python",
197
+ blocks: [
198
+ {
199
+ type: "codeBlock",
200
+ props: { language: "python" },
201
+ content: "print('Hello, world!')",
202
+ },
203
+ ],
204
+ },
178
205
  {
179
206
  name: "file/button",
180
207
  blocks: [
@@ -332,6 +359,74 @@ export const defaultSchemaTestCases: EditorTestCases<
332
359
  },
333
360
  ],
334
361
  },
362
+ {
363
+ name: "table/basic",
364
+ blocks: [
365
+ {
366
+ type: "table",
367
+ content: {
368
+ type: "tableContent",
369
+ rows: [
370
+ {
371
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
372
+ },
373
+ {
374
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
375
+ },
376
+ {
377
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
378
+ },
379
+ ],
380
+ },
381
+ },
382
+ ],
383
+ },
384
+ {
385
+ name: "table/allColWidths",
386
+ blocks: [
387
+ {
388
+ type: "table",
389
+ content: {
390
+ type: "tableContent",
391
+ columnWidths: [100, 200, 300],
392
+ rows: [
393
+ {
394
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
395
+ },
396
+ {
397
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
398
+ },
399
+ {
400
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
401
+ },
402
+ ],
403
+ },
404
+ },
405
+ ],
406
+ },
407
+ {
408
+ name: "table/mixedColWidths",
409
+ blocks: [
410
+ {
411
+ type: "table",
412
+ content: {
413
+ type: "tableContent",
414
+ columnWidths: [100, undefined, 300],
415
+ rows: [
416
+ {
417
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
418
+ },
419
+ {
420
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
421
+ },
422
+ {
423
+ cells: ["Table Cell", "Table Cell", "Table Cell"],
424
+ },
425
+ ],
426
+ },
427
+ },
428
+ ],
429
+ },
335
430
  {
336
431
  name: "link/basic",
337
432
  blocks: [
@@ -80,12 +80,19 @@ export function partialBlockToBlockForTesting<
80
80
  schema: BSchema,
81
81
  partialBlock: PartialBlock<BSchema, I, S>
82
82
  ): Block<BSchema, I, S> {
83
+ const contentType: "inline" | "table" | "none" =
84
+ schema[partialBlock.type!].content;
85
+
83
86
  const withDefaults: Block<BSchema, I, S> = {
84
87
  id: "",
85
88
  type: partialBlock.type!,
86
89
  props: {} as any,
87
90
  content:
88
- schema[partialBlock.type!].content === "inline" ? [] : (undefined as any),
91
+ contentType === "inline"
92
+ ? []
93
+ : contentType === "table"
94
+ ? { type: "tableContent", columnWidths: [], rows: [] }
95
+ : (undefined as any),
89
96
  children: [] as any,
90
97
  ...partialBlock,
91
98
  };
@@ -98,6 +105,24 @@ export function partialBlockToBlockForTesting<
98
105
  }
99
106
  );
100
107
 
108
+ if (contentType === "inline") {
109
+ const content = withDefaults.content as InlineContent<I, S>[] | undefined;
110
+ withDefaults.content = partialContentToInlineContent(content) as any;
111
+ } else if (contentType === "table") {
112
+ const content = withDefaults.content as TableContent<I, S> | undefined;
113
+ withDefaults.content = {
114
+ type: "tableContent",
115
+ columnWidths:
116
+ content?.columnWidths ||
117
+ content?.rows[0]?.cells.map(() => undefined) ||
118
+ [],
119
+ rows:
120
+ content?.rows.map((row) => ({
121
+ cells: row.cells.map((cell) => partialContentToInlineContent(cell)),
122
+ })) || [],
123
+ } as any;
124
+ }
125
+
101
126
  return {
102
127
  ...withDefaults,
103
128
  content: partialContentToInlineContent(withDefaults.content),
@@ -0,0 +1,369 @@
1
+ import { InputRule, isTextSelection } from "@tiptap/core";
2
+ import { TextSelection } from "@tiptap/pm/state";
3
+ import { createHighlightPlugin, Parser } from "prosemirror-highlight";
4
+ import { createParser } from "prosemirror-highlight/shiki";
5
+ import {
6
+ BundledLanguage,
7
+ bundledLanguagesInfo,
8
+ createHighlighter,
9
+ Highlighter,
10
+ } from "shiki";
11
+ import {
12
+ createBlockSpecFromStronglyTypedTiptapNode,
13
+ createStronglyTypedTiptapNode,
14
+ PropSchema,
15
+ } from "../../schema/index.js";
16
+ import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
17
+ import {
18
+ defaultSupportedLanguages,
19
+ SupportedLanguageConfig,
20
+ } from "./defaultSupportedLanguages.js";
21
+
22
+ interface CodeBlockOptions {
23
+ defaultLanguage: string;
24
+ indentLineWithTab: boolean;
25
+ supportedLanguages: SupportedLanguageConfig[];
26
+ }
27
+
28
+ export const defaultCodeBlockPropSchema = {
29
+ language: {
30
+ default: "javascript",
31
+ values: [...defaultSupportedLanguages.map((lang) => lang.id)],
32
+ },
33
+ } satisfies PropSchema;
34
+
35
+ const CodeBlockContent = createStronglyTypedTiptapNode({
36
+ name: "codeBlock",
37
+ content: "inline*",
38
+ group: "blockContent",
39
+ marks: "",
40
+ code: true,
41
+ defining: true,
42
+ addOptions() {
43
+ return {
44
+ defaultLanguage: "javascript",
45
+ indentLineWithTab: true,
46
+ supportedLanguages: defaultSupportedLanguages,
47
+ };
48
+ },
49
+ addAttributes() {
50
+ return {
51
+ language: {
52
+ default: this.options.defaultLanguage,
53
+ parseHTML: (inputElement) => {
54
+ let element = inputElement as HTMLElement | null;
55
+
56
+ if (
57
+ element?.tagName === "DIV" &&
58
+ element?.dataset.contentType === "codeBlock"
59
+ ) {
60
+ element = element.children[0] as HTMLElement | null;
61
+ }
62
+
63
+ if (element?.tagName === "PRE") {
64
+ element = element?.children[0] as HTMLElement | null;
65
+ }
66
+
67
+ const dataLanguage = element?.getAttribute("data-language");
68
+
69
+ if (dataLanguage) {
70
+ return dataLanguage.toLowerCase();
71
+ }
72
+
73
+ const classNames = [...(element?.className.split(" ") || [])];
74
+ const languages = classNames
75
+ .filter((className) => className.startsWith("language-"))
76
+ .map((className) => className.replace("language-", ""));
77
+ const [language] = languages;
78
+
79
+ if (!language) {
80
+ return null;
81
+ }
82
+
83
+ return language.toLowerCase();
84
+ },
85
+ renderHTML: (attributes) => {
86
+ return attributes.language && attributes.language !== "text"
87
+ ? {
88
+ class: `language-${attributes.language}`,
89
+ }
90
+ : {};
91
+ },
92
+ },
93
+ };
94
+ },
95
+ parseHTML() {
96
+ return [
97
+ {
98
+ tag: "div[data-content-type=" + this.name + "]",
99
+ },
100
+ {
101
+ tag: "pre",
102
+ preserveWhitespace: "full",
103
+ },
104
+ ];
105
+ },
106
+ renderHTML({ HTMLAttributes }) {
107
+ const pre = document.createElement("pre");
108
+ const { dom, contentDOM } = createDefaultBlockDOMOutputSpec(
109
+ this.name,
110
+ "code",
111
+ this.options.domAttributes?.blockContent || {},
112
+ {
113
+ ...(this.options.domAttributes?.inlineContent || {}),
114
+ ...HTMLAttributes,
115
+ }
116
+ );
117
+
118
+ dom.removeChild(contentDOM);
119
+ dom.appendChild(pre);
120
+ pre.appendChild(contentDOM);
121
+
122
+ return {
123
+ dom,
124
+ contentDOM,
125
+ };
126
+ },
127
+ addNodeView() {
128
+ const supportedLanguages = this.options
129
+ .supportedLanguages as SupportedLanguageConfig[];
130
+
131
+ return ({ editor, node, getPos, HTMLAttributes }) => {
132
+ const pre = document.createElement("pre");
133
+ const select = document.createElement("select");
134
+ const selectWrapper = document.createElement("div");
135
+ const { dom, contentDOM } = createDefaultBlockDOMOutputSpec(
136
+ this.name,
137
+ "code",
138
+ {
139
+ ...(this.options.domAttributes?.blockContent || {}),
140
+ ...HTMLAttributes,
141
+ },
142
+ this.options.domAttributes?.inlineContent || {}
143
+ );
144
+ const handleLanguageChange = (event: Event) => {
145
+ const language = (event.target as HTMLSelectElement).value;
146
+
147
+ editor.commands.command(({ tr }) => {
148
+ tr.setNodeAttribute(getPos(), "language", language);
149
+
150
+ return true;
151
+ });
152
+ };
153
+
154
+ supportedLanguages.forEach(({ id, name }) => {
155
+ const option = document.createElement("option");
156
+
157
+ option.value = id;
158
+ option.text = name;
159
+ select.appendChild(option);
160
+ });
161
+
162
+ selectWrapper.contentEditable = "false";
163
+ select.value = node.attrs.language || this.options.defaultLanguage;
164
+ dom.removeChild(contentDOM);
165
+ dom.appendChild(selectWrapper);
166
+ dom.appendChild(pre);
167
+ pre.appendChild(contentDOM);
168
+ selectWrapper.appendChild(select);
169
+ select.addEventListener("change", handleLanguageChange);
170
+
171
+ return {
172
+ dom,
173
+ contentDOM,
174
+ update: (newNode) => {
175
+ if (newNode.type !== this.type) {
176
+ return false;
177
+ }
178
+
179
+ return true;
180
+ },
181
+ destroy: () => {
182
+ select.removeEventListener("change", handleLanguageChange);
183
+ },
184
+ };
185
+ };
186
+ },
187
+ addProseMirrorPlugins() {
188
+ let highlighter: Highlighter | undefined;
189
+ let parser: Parser | undefined;
190
+
191
+ const supportedLanguages = this.options
192
+ .supportedLanguages as SupportedLanguageConfig[];
193
+ const lazyParser: Parser = (options) => {
194
+ if (!highlighter) {
195
+ return createHighlighter({
196
+ themes: ["github-dark"],
197
+ langs: [],
198
+ }).then((createdHighlighter) => {
199
+ highlighter = createdHighlighter;
200
+ });
201
+ }
202
+
203
+ const language = options.language;
204
+
205
+ if (
206
+ language &&
207
+ language !== "text" &&
208
+ !highlighter.getLoadedLanguages().includes(language) &&
209
+ supportedLanguages.find(({ id }) => id === language) &&
210
+ bundledLanguagesInfo.find(({ id }) => id === language)
211
+ ) {
212
+ return highlighter.loadLanguage(language as BundledLanguage);
213
+ }
214
+
215
+ if (!parser) {
216
+ parser = createParser(highlighter);
217
+ }
218
+
219
+ return parser(options);
220
+ };
221
+
222
+ const shikiLazyPlugin = createHighlightPlugin({
223
+ parser: lazyParser,
224
+ languageExtractor: (node) => node.attrs.language,
225
+ nodeTypes: [this.name],
226
+ });
227
+
228
+ return [shikiLazyPlugin];
229
+ },
230
+ addInputRules() {
231
+ const supportedLanguages = this.options
232
+ .supportedLanguages as SupportedLanguageConfig[];
233
+
234
+ return [
235
+ new InputRule({
236
+ find: /^```(.*?)\s$/,
237
+ handler: ({ state, range, match }) => {
238
+ const $start = state.doc.resolve(range.from);
239
+ const languageName = match[1].trim();
240
+ const attributes = {
241
+ language:
242
+ supportedLanguages.find(({ match }) => {
243
+ return match.includes(languageName);
244
+ })?.id || this.options.defaultLanguage,
245
+ };
246
+
247
+ if (
248
+ !$start
249
+ .node(-1)
250
+ .canReplaceWith(
251
+ $start.index(-1),
252
+ $start.indexAfter(-1),
253
+ this.type
254
+ )
255
+ ) {
256
+ return null;
257
+ }
258
+
259
+ state.tr
260
+ .delete(range.from, range.to)
261
+ .setBlockType(range.from, range.from, this.type, attributes)
262
+ .setSelection(TextSelection.create(state.tr.doc, range.from));
263
+
264
+ return;
265
+ },
266
+ }),
267
+ ];
268
+ },
269
+ addKeyboardShortcuts() {
270
+ return {
271
+ Delete: ({ editor }) => {
272
+ const { selection } = editor.state;
273
+ const { $from } = selection;
274
+
275
+ // When inside empty codeblock, on `DELETE` key press, delete the codeblock
276
+ if (
277
+ editor.isActive(this.name) &&
278
+ !$from.parent.textContent &&
279
+ isTextSelection(selection)
280
+ ) {
281
+ // Get the start position of the codeblock for node selection
282
+ const from = $from.pos - $from.parentOffset - 2;
283
+
284
+ editor.chain().setNodeSelection(from).deleteSelection().run();
285
+
286
+ return true;
287
+ }
288
+
289
+ return false;
290
+ },
291
+ Tab: ({ editor }) => {
292
+ if (!this.options.indentLineWithTab) {
293
+ return false;
294
+ }
295
+ if (editor.isActive(this.name)) {
296
+ editor.commands.insertContent(" ");
297
+ return true;
298
+ }
299
+
300
+ return false;
301
+ },
302
+ Enter: ({ editor }) => {
303
+ const { $from } = editor.state.selection;
304
+
305
+ if (!editor.isActive(this.name)) {
306
+ return false;
307
+ }
308
+
309
+ const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
310
+ const endsWithDoubleNewline = $from.parent.textContent.endsWith("\n\n");
311
+
312
+ if (!isAtEnd || !endsWithDoubleNewline) {
313
+ editor.commands.insertContent("\n");
314
+ return true;
315
+ }
316
+
317
+ return editor
318
+ .chain()
319
+ .command(({ tr }) => {
320
+ tr.delete($from.pos - 2, $from.pos);
321
+
322
+ return true;
323
+ })
324
+ .exitCode()
325
+ .run();
326
+ },
327
+ "Shift-Enter": ({ editor }) => {
328
+ const { $from } = editor.state.selection;
329
+
330
+ if (!editor.isActive(this.name)) {
331
+ return false;
332
+ }
333
+
334
+ editor
335
+ .chain()
336
+ .insertContentAt(
337
+ $from.pos - $from.parentOffset + $from.parent.nodeSize,
338
+ {
339
+ type: "paragraph",
340
+ }
341
+ )
342
+ .run();
343
+
344
+ return true;
345
+ },
346
+ };
347
+ },
348
+ });
349
+
350
+ export const CodeBlock = createBlockSpecFromStronglyTypedTiptapNode(
351
+ CodeBlockContent,
352
+ defaultCodeBlockPropSchema
353
+ );
354
+
355
+ export function customizeCodeBlock(options: Partial<CodeBlockOptions>) {
356
+ return createBlockSpecFromStronglyTypedTiptapNode(
357
+ CodeBlockContent.configure(options),
358
+ {
359
+ language: {
360
+ default:
361
+ options.defaultLanguage ||
362
+ defaultCodeBlockPropSchema.language.default,
363
+ values:
364
+ options.supportedLanguages?.map((lang) => lang.id) ||
365
+ defaultCodeBlockPropSchema.language.values,
366
+ },
367
+ }
368
+ );
369
+ }
@@ -0,0 +1,96 @@
1
+ import { bundledLanguagesInfo } from "shiki/bundle/web";
2
+
3
+ export type SupportedLanguageConfig = {
4
+ id: string;
5
+ name: string;
6
+ match: string[];
7
+ };
8
+
9
+ export const defaultSupportedLanguages: SupportedLanguageConfig[] = [
10
+ {
11
+ id: "text",
12
+ name: "Plain Text",
13
+ match: ["text", "txt", "plain"],
14
+ },
15
+ ...bundledLanguagesInfo
16
+ .filter((lang) => {
17
+ return ![
18
+ "angular-html",
19
+ "angular-ts",
20
+ "astro",
21
+ "blade",
22
+ "coffee",
23
+ "handlebars",
24
+ "html-derivative",
25
+ "http",
26
+ "imba",
27
+ "jinja",
28
+ "jison",
29
+ "json5",
30
+ "marko",
31
+ "mdc",
32
+ "stylus",
33
+ "ts-tags",
34
+ ].includes(lang.id);
35
+ })
36
+ .map((lang) => ({
37
+ match: [lang.id, ...(lang.aliases || [])],
38
+ id: lang.id,
39
+ name: lang.name,
40
+ })),
41
+ {
42
+ id: "haskell",
43
+ name: "Haskell",
44
+ match: ["haskell", "hs"],
45
+ },
46
+ {
47
+ id: "csharp",
48
+ name: "C#",
49
+ match: ["c#", "csharp", "cs"],
50
+ },
51
+ {
52
+ id: "latex",
53
+ name: "LaTeX",
54
+ match: ["latex"],
55
+ },
56
+ {
57
+ id: "lua",
58
+ name: "Lua",
59
+ match: ["lua"],
60
+ },
61
+ {
62
+ id: "mermaid",
63
+ name: "Mermaid",
64
+ match: ["mermaid", "mmd"],
65
+ },
66
+ {
67
+ id: "ruby",
68
+ name: "Ruby",
69
+ match: ["ruby", "rb"],
70
+ },
71
+ {
72
+ id: "rust",
73
+ name: "Rust",
74
+ match: ["rust", "rs"],
75
+ },
76
+ {
77
+ id: "scala",
78
+ name: "Scala",
79
+ match: ["scala"],
80
+ },
81
+ {
82
+ id: "swift",
83
+ name: "Swift",
84
+ match: ["swift"],
85
+ },
86
+ {
87
+ id: "kotlin",
88
+ name: "Kotlin",
89
+ match: ["kotlin", "kt", "kts"],
90
+ },
91
+ {
92
+ id: "objective-c",
93
+ name: "Objective C",
94
+ match: ["objective-c", "objc"],
95
+ },
96
+ ];
@@ -108,6 +108,13 @@ export const createFileAndCaptionWrapper = (
108
108
  caption.className = "bn-file-caption";
109
109
  caption.textContent = block.props.caption;
110
110
 
111
+ if (typeof block.props.previewWidth === "number" &&
112
+ block.props.previewWidth > 0 &&
113
+ block.props.caption !== undefined
114
+ ) {
115
+ caption.style.width = `${block.props.previewWidth}px`
116
+ }
117
+
111
118
  fileAndCaptionWrapper.appendChild(file);
112
119
  fileAndCaptionWrapper.appendChild(caption);
113
120