diff --git a/docs/package.json b/docs/package.json index 5969a94eb9..d723b33489 100644 --- a/docs/package.json +++ b/docs/package.json @@ -11,6 +11,7 @@ "dependencies": { "@blocknote/ariakit": "^0.26.0", "@blocknote/core": "^0.26.0", + "@blocknote/code-block": "^0.26.0", "@blocknote/mantine": "^0.26.0", "@blocknote/react": "^0.26.0", "@blocknote/shadcn": "^0.26.0", diff --git a/docs/pages/docs/advanced/code-blocks.mdx b/docs/pages/docs/advanced/code-blocks.mdx new file mode 100644 index 0000000000..582e91597e --- /dev/null +++ b/docs/pages/docs/advanced/code-blocks.mdx @@ -0,0 +1,88 @@ +--- +title: Code Block Syntax Highlighting +description: This section explains how to add syntax highlighting to code blocks. +imageTitle: Code Block Syntax Highlighting +--- + +import { Example } from "@/components/example"; + +# Code Block Syntax Highlighting + +To enable code block syntax highlighting, you can use the `codeBlock` option in the `useCreateBlockNote` hook. This is excluded by default to reduce bundle size. + +We've created a default setup which automatically includes some of the most common languages in the most optimized way possible. The language syntaxes are loaded on-demand to ensure the smallest bundle size for your users. + +To use it, you can do the following: + +```sh +npm install @blocknote/code-block +``` + +And then you can use it like this: + +```tsx +import { codeBlock } from "@blocknote/code-block"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + codeBlock, + }); + + // Renders the editor instance using a React component. + return ; +} +``` + +See the code block example for a more detailed example. + + + +## Custom Themes & Languages + +To configure a code block highlighting theme and language, you can use the `codeBlock` option in the `useCreateBlockNote` hook. + +This allows you to configure a [shiki](https://shiki.style) highlighter for the code blocks of your editor, allowing you to tailor the themes and languages you would like to use. + +To create a syntax highlighter, you can use the [shiki-codegen](https://shiki.style/packages/codegen) CLI for generating the code to create a syntax highlighter for your languages and themes. + +For example to create a syntax highlighter using the optimized javascript engine, javascript, typescript, vue, with light and dark themes, you can run the following command: + +```bash +npx shiki-codegen --langs javascript,typescript,vue --themes light,dark --engine javascript --precompiled ./shiki.bundle.ts +``` + +This will generate a `shiki.bundle.ts` file that you can use to create a syntax highlighter for your editor. + +Like this: + +```ts +import { createHighlighter } from "./shiki.bundle.js"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + codeBlock: { + indentLineWithTab: true, + defaultLanguage: "typescript", + supportedLanguages: { + typescript: { + name: "TypeScript", + aliases: ["ts"], + }, + }, + createHighlighter: () => + createHighlighter({ + themes: ["light-plus", "dark-plus"], + langs: [], + }), + }, + }); + + return ; +} +``` + +See the custom code block example for a more detailed example. + + diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index d4fd6f2e12..a3b92bafd2 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -5,7 +5,7 @@ import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. - const editor = useCreateBlockNote({}); + const editor = useCreateBlockNote(); // Renders the editor instance using a React component. return ; diff --git a/examples/04-theming/06-code-block/.bnexample.json b/examples/04-theming/06-code-block/.bnexample.json new file mode 100644 index 0000000000..5778b19ace --- /dev/null +++ b/examples/04-theming/06-code-block/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["Basic"] +} diff --git a/examples/04-theming/06-code-block/App.tsx b/examples/04-theming/06-code-block/App.tsx new file mode 100644 index 0000000000..689799758c --- /dev/null +++ b/examples/04-theming/06-code-block/App.tsx @@ -0,0 +1,53 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +// This packages some of the most used languages in on-demand bundle +import { codeBlock } from "@blocknote/code-block"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + codeBlock, + initialContent: [ + { + type: "codeBlock", + props: { + language: "typescript", + }, + content: [ + { + type: "text", + text: "const x = 3 * 4;", + styles: {}, + }, + ], + }, + { + type: "paragraph", + }, + { + type: "heading", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + level: 3, + }, + content: [ + { + type: "text", + text: 'Click on "Typescript" above to see the different supported languages', + styles: {}, + }, + ], + }, + { + type: "paragraph", + }, + ], + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/04-theming/06-code-block/README.md b/examples/04-theming/06-code-block/README.md new file mode 100644 index 0000000000..8a19cb77d7 --- /dev/null +++ b/examples/04-theming/06-code-block/README.md @@ -0,0 +1,8 @@ +# Code Block Syntax Highlighting + +To enable code block syntax highlighting, you can use the `codeBlock` option in the `useCreateBlockNote` hook. This is excluded by default to reduce bundle size. + +**Relevant Docs:** + +- [Code Block Syntax Highlighting](/docs/advanced/code-blocks) +- [Editor Setup](/docs/editor-basics/setup) diff --git a/examples/04-theming/06-code-block/index.html b/examples/04-theming/06-code-block/index.html new file mode 100644 index 0000000000..b58e99d093 --- /dev/null +++ b/examples/04-theming/06-code-block/index.html @@ -0,0 +1,14 @@ + + + + + + Code Block Syntax Highlighting + + +
+ + + diff --git a/examples/04-theming/06-code-block/main.tsx b/examples/04-theming/06-code-block/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/04-theming/06-code-block/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/04-theming/06-code-block/package.json b/examples/04-theming/06-code-block/package.json new file mode 100644 index 0000000000..0a55c50898 --- /dev/null +++ b/examples/04-theming/06-code-block/package.json @@ -0,0 +1,37 @@ +{ + "name": "@blocknote/example-code-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "latest", + "@blocknote/react": "latest", + "@blocknote/ariakit": "latest", + "@blocknote/mantine": "latest", + "@blocknote/shadcn": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.10.0", + "vite": "^5.3.4" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/04-theming/06-code-block/tsconfig.json b/examples/04-theming/06-code-block/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/04-theming/06-code-block/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/04-theming/06-code-block/vite.config.ts b/examples/04-theming/06-code-block/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/04-theming/06-code-block/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/examples/04-theming/07-custom-code-block/.bnexample.json b/examples/04-theming/07-custom-code-block/.bnexample.json new file mode 100644 index 0000000000..5778b19ace --- /dev/null +++ b/examples/04-theming/07-custom-code-block/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["Basic"] +} diff --git a/examples/04-theming/07-custom-code-block/App.tsx b/examples/04-theming/07-custom-code-block/App.tsx new file mode 100644 index 0000000000..d6677aa707 --- /dev/null +++ b/examples/04-theming/07-custom-code-block/App.tsx @@ -0,0 +1,75 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +// Bundle created from `npx shiki-codegen --langs typescript,javascript,react --themes light-plus,dark-plus --engine javascript --precompiled ./shiki.bundle.ts` +import { createHighlighter } from "./shiki.bundle.js"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + codeBlock: { + indentLineWithTab: true, + defaultLanguage: "typescript", + supportedLanguages: { + typescript: { + name: "TypeScript", + aliases: ["ts"], + }, + javascript: { + name: "JavaScript", + aliases: ["js"], + }, + vue: { + name: "Vue", + }, + }, + // This creates a highlighter, it can be asynchronous to load it afterwards + createHighlighter: () => + createHighlighter({ + themes: ["dark-plus", "light-plus"], + langs: [], + }), + }, + initialContent: [ + { + type: "codeBlock", + props: { + language: "typescript", + }, + content: [ + { + type: "text", + text: "const x = 3 * 4;", + styles: {}, + }, + ], + }, + { + type: "paragraph", + }, + { + type: "heading", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + level: 3, + }, + content: [ + { + type: "text", + text: 'Click on "Typescript" above to see the different supported languages', + styles: {}, + }, + ], + }, + { + type: "paragraph", + }, + ], + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/04-theming/07-custom-code-block/README.md b/examples/04-theming/07-custom-code-block/README.md new file mode 100644 index 0000000000..5401f022e7 --- /dev/null +++ b/examples/04-theming/07-custom-code-block/README.md @@ -0,0 +1,49 @@ +# Custom Code Block Theme & Language + +To configure a code block highlighting theme and language, you can use the `codeBlock` option in the `useCreateBlockNote` hook. + +This allows you to configure a shiki highlighter for the code blocks of your editor, allowing you to tailor the themes and languages you would like to use. + +To create a syntax highlighter, you can use the [shiki-codegen](https://shiki.style/packages/codegen) CLI for generating the code to create a syntax highlighter for your languages and themes. + +For example to create a syntax highlighter using the optimized javascript engine, javascript, typescript, vue, with light and dark themes, you can run the following command: + +```bash +npx shiki-codegen --langs javascript,typescript,vue --themes light,dark --engine javascript --precompiled ./shiki.bundle.ts +``` + +This will generate a `shiki.bundle.ts` file that you can use to create a syntax highlighter for your editor. + +Like this: + +```ts +import { createHighlighter } from "./shiki.bundle.js"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + codeBlock: { + indentLineWithTab: true, + defaultLanguage: "typescript", + supportedLanguages: { + typescript: { + name: "TypeScript", + aliases: ["ts"], + }, + }, + createHighlighter: () => + createHighlighter({ + themes: ["light-plus", "dark-plus"], + langs: [], + }), + }, + }); + + return ; +} +``` + +**Relevant Docs:** + +- [Editor Setup](/docs/editor-basics/setup) +- [shiki-codegen](https://shiki.style/packages/codegen) diff --git a/examples/04-theming/07-custom-code-block/index.html b/examples/04-theming/07-custom-code-block/index.html new file mode 100644 index 0000000000..1b7a1f0b09 --- /dev/null +++ b/examples/04-theming/07-custom-code-block/index.html @@ -0,0 +1,14 @@ + + + + + + Custom Code Block Theme & Language + + +
+ + + diff --git a/examples/04-theming/07-custom-code-block/main.tsx b/examples/04-theming/07-custom-code-block/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/04-theming/07-custom-code-block/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/04-theming/07-custom-code-block/package.json b/examples/04-theming/07-custom-code-block/package.json new file mode 100644 index 0000000000..c549ab9864 --- /dev/null +++ b/examples/04-theming/07-custom-code-block/package.json @@ -0,0 +1,37 @@ +{ + "name": "@blocknote/example-custom-code-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "latest", + "@blocknote/react": "latest", + "@blocknote/ariakit": "latest", + "@blocknote/mantine": "latest", + "@blocknote/shadcn": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^8.10.0", + "vite": "^5.3.4" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/04-theming/07-custom-code-block/shiki.bundle.ts b/examples/04-theming/07-custom-code-block/shiki.bundle.ts new file mode 100644 index 0000000000..a5d6eeaceb --- /dev/null +++ b/examples/04-theming/07-custom-code-block/shiki.bundle.ts @@ -0,0 +1,63 @@ +/* Generate by @shikijs/codegen */ +import type { + DynamicImportLanguageRegistration, + DynamicImportThemeRegistration, + HighlighterGeneric, +} from "@shikijs/types"; +import { + createSingletonShorthands, + createdBundledHighlighter, +} from "@shikijs/core"; +import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript"; + +type BundledLanguage = "typescript" | "ts" | "javascript" | "js" | "vue"; +type BundledTheme = "light-plus" | "dark-plus"; +type Highlighter = HighlighterGeneric; + +const bundledLanguages = { + typescript: () => import("@shikijs/langs-precompiled/typescript"), + ts: () => import("@shikijs/langs-precompiled/typescript"), + javascript: () => import("@shikijs/langs-precompiled/javascript"), + js: () => import("@shikijs/langs-precompiled/javascript"), + vue: () => import("@shikijs/langs-precompiled/vue"), +} as Record; + +const bundledThemes = { + "light-plus": () => import("@shikijs/themes/light-plus"), + "dark-plus": () => import("@shikijs/themes/dark-plus"), +} as Record; + +const createHighlighter = /* @__PURE__ */ createdBundledHighlighter< + BundledLanguage, + BundledTheme +>({ + langs: bundledLanguages, + themes: bundledThemes, + engine: () => createJavaScriptRegexEngine(), +}); + +const { + codeToHtml, + codeToHast, + codeToTokensBase, + codeToTokens, + codeToTokensWithThemes, + getSingletonHighlighter, + getLastGrammarState, +} = /* @__PURE__ */ createSingletonShorthands( + createHighlighter +); + +export { + bundledLanguages, + bundledThemes, + codeToHast, + codeToHtml, + codeToTokens, + codeToTokensBase, + codeToTokensWithThemes, + createHighlighter, + getLastGrammarState, + getSingletonHighlighter, +}; +export type { BundledLanguage, BundledTheme, Highlighter }; diff --git a/examples/04-theming/07-custom-code-block/tsconfig.json b/examples/04-theming/07-custom-code-block/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/04-theming/07-custom-code-block/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/04-theming/07-custom-code-block/vite.config.ts b/examples/04-theming/07-custom-code-block/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/04-theming/07-custom-code-block/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/package-lock.json b/package-lock.json index 87692758d1..716825fd4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "version": "0.26.0", "dependencies": { "@blocknote/ariakit": "^0.26.0", + "@blocknote/code-block": "^0.26.0", "@blocknote/core": "^0.26.0", "@blocknote/mantine": "^0.26.0", "@blocknote/react": "^0.26.0", @@ -3324,6 +3325,10 @@ "resolved": "packages/ariakit", "link": true }, + "node_modules/@blocknote/code-block": { + "resolved": "packages/code-block", + "link": true + }, "node_modules/@blocknote/core": { "resolved": "packages/core", "link": true @@ -9592,24 +9597,20 @@ } }, "node_modules/@shikijs/core": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.1.tgz", - "integrity": "sha512-bqAhT/Ri5ixV4oYsvJNH8UJjpjbINWlWyXY6tBTsP4OmD6XnFv43nRJ+lTdxd2rmG5pgam/x+zGR6kLRXrpEKA==", - "license": "MIT", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.2.1.tgz", + "integrity": "sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==", "dependencies": { - "@shikijs/engine-javascript": "1.22.1", - "@shikijs/engine-oniguruma": "1.22.1", - "@shikijs/types": "1.22.1", - "@shikijs/vscode-textmate": "^9.3.0", + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.3" + "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/core/node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", "dependencies": { "@types/unist": "*" } @@ -9617,14 +9618,12 @@ "node_modules/@shikijs/core/node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@shikijs/core/node_modules/hast-util-to-html": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", - "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", - "license": "MIT", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -9633,7 +9632,7 @@ "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", - "property-information": "^6.0.0", + "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" @@ -9647,7 +9646,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -9656,34 +9654,59 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/@shikijs/core/node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@shikijs/engine-javascript": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.1.tgz", - "integrity": "sha512-540pyoy0LWe4jj2BVbgELwOFu1uFvRI7lg4hdsExrSXA9x7gqfzZ/Nnh4RfX86aDAgJ647gx4TCmRwACbnQSvw==", - "license": "MIT", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.2.1.tgz", + "integrity": "sha512-eMdcUzN3FMQYxOmRf2rmU8frikzoSHbQDFH2hIuXsrMO+IBOCI9BeeRkCiBkcLDHeRKbOCtYMJK3D6U32ooU9Q==", "dependencies": { - "@shikijs/types": "1.22.1", - "@shikijs/vscode-textmate": "^9.3.0", - "oniguruma-to-js": "0.4.3" + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.1.0" } }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.1.tgz", - "integrity": "sha512-L+1Vmd+a2kk8HtogUFymQS6BjUfJnzcWoUp1BUgxoDiklbKSMvrsMuLZGevTOP1m0rEjgnC5MsDmsr8lX1lC+Q==", - "license": "MIT", + "node_modules/@shikijs/langs": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.2.1.tgz", + "integrity": "sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==", "dependencies": { - "@shikijs/types": "1.22.1", - "@shikijs/vscode-textmate": "^9.3.0" + "@shikijs/types": "3.2.1" + } + }, + "node_modules/@shikijs/langs-precompiled": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs-precompiled/-/langs-precompiled-3.2.1.tgz", + "integrity": "sha512-M8AQBjYRVk6seMMwlz7FNIWolAE0bZalBLOwFUPNuM8g8LL7o+GjD/2vKmV7/vxaJ79P0XyZ3I1tFAbWS1wCHQ==", + "dependencies": { + "@shikijs/types": "3.2.1", + "oniguruma-to-es": "^4.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.2.1.tgz", + "integrity": "sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==", + "dependencies": { + "@shikijs/types": "3.2.1" } }, "node_modules/@shikijs/types": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.1.tgz", - "integrity": "sha512-+45f8mu/Hxqs6Kyhfm98Nld5n7Q7lwhjU8UtdQwrOPs7BnM4VAb929O3IQ2ce+4D7SlNFlZGd8CnKRSnwbQreQ==", - "license": "MIT", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.2.1.tgz", + "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", "dependencies": { - "@shikijs/vscode-textmate": "^9.3.0", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, @@ -9691,16 +9714,14 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", "dependencies": { "@types/unist": "*" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", - "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", - "license": "MIT" + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==" }, "node_modules/@shuding/opentype.js": { "version": "1.4.0-beta.0", @@ -15351,6 +15372,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==" + }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -17239,22 +17265,20 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, "node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -23968,16 +23992,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/oniguruma-to-js": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", - "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", - "license": "MIT", + "node_modules/oniguruma-parser": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.5.4.tgz", + "integrity": "sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==" + }, + "node_modules/oniguruma-to-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.1.0.tgz", + "integrity": "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==", "dependencies": { - "regex": "^4.3.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "emoji-regex-xs": "^1.0.0", + "oniguruma-parser": "^0.5.4", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/oniguruma-to-es/node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dependencies": { + "regex-utilities": "^2.3.0" } }, "node_modules/open": { @@ -24216,6 +24252,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, "node_modules/pacote": { "version": "13.6.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", @@ -26636,11 +26677,18 @@ "@babel/runtime": "^7.8.4" } }, - "node_modules/regex": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz", - "integrity": "sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==", - "license": "MIT" + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", @@ -31212,12 +31260,39 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/code-block": { + "name": "@blocknote/code-block", + "version": "0.26.0", + "license": "MPL-2.0", + "dependencies": { + "@shikijs/core": "^3.2.1", + "@shikijs/engine-javascript": "^3.2.1", + "@shikijs/langs": "^3.2.1", + "@shikijs/langs-precompiled": "^3.2.1", + "@shikijs/themes": "^3.2.1", + "@shikijs/types": "^3.2.1" + }, + "devDependencies": { + "@blocknote/core": "^0.26.0", + "eslint": "^8.10.0", + "prettier": "^2.7.1", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.0.4", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + }, + "peerDependencies": { + "@blocknote/core": "^0.26.0" + } + }, "packages/core": { "name": "@blocknote/core", "version": "0.26.0", "license": "MPL-2.0", "dependencies": { "@emoji-mart/data": "^1.2.1", + "@shikijs/types": "3.2.1", "@tiptap/core": "^2.11.5", "@tiptap/extension-bold": "^2.11.5", "@tiptap/extension-code": "^2.11.5", @@ -31240,7 +31315,7 @@ "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", "prosemirror-dropcursor": "^1.8.1", - "prosemirror-highlight": "^0.9.0", + "prosemirror-highlight": "^0.13.0", "prosemirror-model": "^1.24.1", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", @@ -31254,7 +31329,6 @@ "remark-parse": "^10.0.1", "remark-rehype": "^10.1.0", "remark-stringify": "^10.0.2", - "shiki": "^1.22.0", "unified": "^10.1.2", "uuid": "^8.3.2", "y-prosemirror": "1.2.17", @@ -31288,20 +31362,27 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "devOptional": true, "license": "MIT", "dependencies": { "@types/unist": "*" } }, + "packages/core/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "devOptional": true + }, "packages/core/node_modules/prosemirror-highlight": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/prosemirror-highlight/-/prosemirror-highlight-0.9.0.tgz", - "integrity": "sha512-ujJ1M4JgHop8xZ1uyjFDDg8DkOfXC87tJMQAVDTgR9dQOsIv9MoSA6jGcP7xM84P0ecbu1bqVVe9fqbY2zJDSQ==", - "license": "MIT", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/prosemirror-highlight/-/prosemirror-highlight-0.13.0.tgz", + "integrity": "sha512-GIC2VCTUnukNdsEGLQWWOVpYPl/7/KrVp4xs7XMB48/4rhUrHK8hp8TEog4Irmv+2kmjx24RLnaisGOCP6U8jw==", "funding": { "url": "https://github.com/sponsors/ocavue" }, "peerDependencies": { + "@shikijs/types": "^1.29.2 || ^2.0.0 || ^3.0.0", "@types/hast": "^3.0.0", "highlight.js": "^11.9.0", "lowlight": "^3.1.0", @@ -31309,11 +31390,13 @@ "prosemirror-state": "^1.4.3", "prosemirror-transform": "^1.8.0", "prosemirror-view": "^1.32.4", - "refractor": "^4.8.1", - "shiki": "^1.9.0", - "sugar-high": "^0.6.1" + "refractor": "^5.0.0", + "sugar-high": "^0.6.1 || ^0.7.0 || ^0.8.0 || ^0.9.0" }, "peerDependenciesMeta": { + "@shikijs/types": { + "optional": true + }, "@types/hast": { "optional": true }, @@ -31338,9 +31421,6 @@ "refractor": { "optional": true }, - "shiki": { - "optional": true - }, "sugar-high": { "optional": true } @@ -31363,20 +31443,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/core/node_modules/shiki": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.1.tgz", - "integrity": "sha512-PbJ6XxrWLMwB2rm3qdjIHNm3zq4SfFnOx0B3rEoi4AN8AUngsdyZ1tRe5slMPtn6jQkbUURLNZPpLR7Do3k78g==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.22.1", - "@shikijs/engine-javascript": "1.22.1", - "@shikijs/engine-oniguruma": "1.22.1", - "@shikijs/types": "1.22.1", - "@shikijs/vscode-textmate": "^9.3.0", - "@types/hast": "^3.0.4" - } - }, "packages/dev-scripts": { "name": "@blocknote/dev-scripts", "version": "0.26.0", @@ -32024,6 +32090,7 @@ "@aws-sdk/client-s3": "^3.609.0", "@aws-sdk/s3-request-presigner": "^3.609.0", "@blocknote/ariakit": "^0.26.0", + "@blocknote/code-block": "^0.26.0", "@blocknote/core": "^0.26.0", "@blocknote/mantine": "^0.26.0", "@blocknote/react": "^0.26.0", diff --git a/packages/code-block/.gitignore b/packages/code-block/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/packages/code-block/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/code-block/package.json b/packages/code-block/package.json new file mode 100644 index 0000000000..898dffe25e --- /dev/null +++ b/packages/code-block/package.json @@ -0,0 +1,77 @@ +{ + "name": "@blocknote/code-block", + "homepage": "https://github.com/TypeCellOS/BlockNote", + "private": false, + "sideEffects": false, + "license": "MPL-2.0", + "version": "0.26.0", + "files": [ + "dist", + "types", + "src" + ], + "keywords": [ + "react", + "javascript", + "editor", + "typescript", + "prosemirror", + "wysiwyg", + "rich-text-editor", + "notion", + "yjs", + "block-based", + "tiptap" + ], + "description": "A \"Notion-style\" block-based extensible text editor built on top of Prosemirror and Tiptap.", + "type": "module", + "source": "src/index.ts", + "types": "./types/src/index.d.ts", + "main": "./dist/blocknote-code-block.cjs", + "module": "./dist/blocknote-code-block.js", + "exports": { + ".": { + "types": "./types/src/index.d.ts", + "import": "./dist/blocknote-code-block.js", + "require": "./dist/blocknote-code-block.cjs" + } + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint src --max-warnings 0", + "test": "vitest --run", + "test-watch": "vitest watch" + }, + "dependencies": { + "@shikijs/core": "^3.2.1", + "@shikijs/engine-javascript": "^3.2.1", + "@shikijs/langs-precompiled": "^3.2.1", + "@shikijs/langs": "^3.2.1", + "@shikijs/themes": "^3.2.1", + "@shikijs/types": "^3.2.1" + }, + "devDependencies": { + "@blocknote/core": "^0.26.0", + "eslint": "^8.10.0", + "prettier": "^2.7.1", + "rollup-plugin-webpack-stats": "^0.2.2", + "typescript": "^5.0.4", + "vite": "^5.3.4", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.0.3" + }, + "peerDependencies": { + "@blocknote/core": "^0.26.0" + }, + "eslintConfig": { + "extends": [ + "../../.eslintrc.js" + ] + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "gitHead": "37614ab348dcc7faa830a9a88437b37197a2162d" +} diff --git a/packages/code-block/src/index.test.ts b/packages/code-block/src/index.test.ts new file mode 100644 index 0000000000..22618f9f2a --- /dev/null +++ b/packages/code-block/src/index.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { codeBlock } from "./index.js"; + +describe("codeBlock", () => { + it("should exist", () => { + expect(codeBlock).toBeDefined(); + }); + it("should have defaultLanguage", () => { + expect(codeBlock.defaultLanguage).toBeDefined(); + }); + it("should have supportedLanguages", () => { + expect(codeBlock.supportedLanguages).toBeDefined(); + }); + it("should have createHighlighter", () => { + expect(codeBlock.createHighlighter).toBeDefined(); + }); +}); diff --git a/packages/code-block/src/index.ts b/packages/code-block/src/index.ts new file mode 100644 index 0000000000..e6a200e6c1 --- /dev/null +++ b/packages/code-block/src/index.ts @@ -0,0 +1,205 @@ +import type { CodeBlockOptions } from "@blocknote/core"; +import { createHighlighter } from "./shiki.bundle.js"; + +export const codeBlock = { + defaultLanguage: "javascript", + supportedLanguages: { + text: { + name: "Plain Text", + aliases: ["text", "txt", "plain"], + }, + c: { + name: "C", + aliases: ["c"], + }, + cpp: { + name: "C++", + aliases: ["cpp", "c++"], + }, + css: { + name: "CSS", + aliases: ["css"], + }, + glsl: { + name: "GLSL", + aliases: ["glsl"], + }, + graphql: { + name: "GraphQL", + aliases: ["graphql", "gql"], + }, + haml: { + name: "Ruby Haml", + aliases: ["haml"], + }, + html: { + name: "HTML", + aliases: ["html"], + }, + java: { + name: "Java", + aliases: ["java"], + }, + javascript: { + name: "JavaScript", + aliases: ["javascript", "js"], + }, + json: { + name: "JSON", + aliases: ["json"], + }, + jsonc: { + name: "JSON with Comments", + aliases: ["jsonc"], + }, + jsonl: { + name: "JSON Lines", + aliases: ["jsonl"], + }, + jsx: { + name: "JSX", + aliases: ["jsx"], + }, + julia: { + name: "Julia", + aliases: ["julia", "jl"], + }, + less: { + name: "Less", + aliases: ["less"], + }, + markdown: { + name: "Markdown", + aliases: ["markdown", "md"], + }, + mdx: { + name: "MDX", + aliases: ["mdx"], + }, + php: { + name: "PHP", + aliases: ["php"], + }, + postcss: { + name: "PostCSS", + aliases: ["postcss"], + }, + pug: { + name: "Pug", + aliases: ["pug", "jade"], + }, + python: { + name: "Python", + aliases: ["python", "py"], + }, + r: { + name: "R", + aliases: ["r"], + }, + regexp: { + name: "RegExp", + aliases: ["regexp", "regex"], + }, + sass: { + name: "Sass", + aliases: ["sass"], + }, + scss: { + name: "SCSS", + aliases: ["scss"], + }, + shellscript: { + name: "Shell", + aliases: ["shellscript", "bash", "sh", "shell", "zsh"], + }, + sql: { + name: "SQL", + aliases: ["sql"], + }, + svelte: { + name: "Svelte", + aliases: ["svelte"], + }, + typescript: { + name: "TypeScript", + aliases: ["typescript", "ts"], + }, + vue: { + name: "Vue", + aliases: ["vue"], + }, + "vue-html": { + name: "Vue HTML", + aliases: ["vue-html"], + }, + wasm: { + name: "WebAssembly", + aliases: ["wasm"], + }, + wgsl: { + name: "WGSL", + aliases: ["wgsl"], + }, + xml: { + name: "XML", + aliases: ["xml"], + }, + yaml: { + name: "YAML", + aliases: ["yaml", "yml"], + }, + tsx: { + name: "TSX", + aliases: ["tsx", "typescriptreact"], + }, + haskell: { + name: "Haskell", + aliases: ["haskell", "hs"], + }, + csharp: { + name: "C#", + aliases: ["c#", "csharp", "cs"], + }, + latex: { + name: "LaTeX", + aliases: ["latex"], + }, + lua: { + name: "Lua", + aliases: ["lua"], + }, + mermaid: { + name: "Mermaid", + aliases: ["mermaid", "mmd"], + }, + ruby: { + name: "Ruby", + aliases: ["ruby", "rb"], + }, + rust: { + name: "Rust", + aliases: ["rust", "rs"], + }, + scala: { + name: "Scala", + aliases: ["scala"], + }, + swift: { + name: "Swift", + aliases: ["swift"], + }, + kotlin: { + name: "Kotlin", + aliases: ["kotlin", "kt", "kts"], + }, + "objective-c": { + name: "Objective C", + aliases: ["objective-c", "objc"], + }, + }, + createHighlighter: () => + createHighlighter({ + themes: ["github-dark", "github-light"], + langs: [], + }), +} as CodeBlockOptions; diff --git a/packages/code-block/src/shiki.bundle.ts b/packages/code-block/src/shiki.bundle.ts new file mode 100644 index 0000000000..75962c807f --- /dev/null +++ b/packages/code-block/src/shiki.bundle.ts @@ -0,0 +1,104 @@ +/* Generate by @shikijs/codegen */ +import type { + DynamicImportLanguageRegistration, + DynamicImportThemeRegistration, + HighlighterGeneric, +} from "@shikijs/types"; +import { createdBundledHighlighter } from "@shikijs/core"; +import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript"; + +type BundledLanguage = "typescript" | "ts" | "javascript" | "js" | "vue"; +type BundledTheme = "github-light" | "github-dark"; +type Highlighter = HighlighterGeneric; + +const bundledLanguages = { + c: () => import("@shikijs/langs-precompiled/c"), + cpp: () => import("@shikijs/langs-precompiled/cpp"), + "c++": () => import("@shikijs/langs-precompiled/cpp"), + css: () => import("@shikijs/langs-precompiled/css"), + glsl: () => import("@shikijs/langs-precompiled/glsl"), + graphql: () => import("@shikijs/langs-precompiled/graphql"), + gql: () => import("@shikijs/langs-precompiled/graphql"), + haml: () => import("@shikijs/langs-precompiled/haml"), + html: () => import("@shikijs/langs-precompiled/html"), + java: () => import("@shikijs/langs-precompiled/java"), + javascript: () => import("@shikijs/langs-precompiled/javascript"), + js: () => import("@shikijs/langs-precompiled/javascript"), + json: () => import("@shikijs/langs-precompiled/json"), + jsonc: () => import("@shikijs/langs-precompiled/jsonc"), + jsonl: () => import("@shikijs/langs-precompiled/jsonl"), + jsx: () => import("@shikijs/langs-precompiled/jsx"), + julia: () => import("@shikijs/langs-precompiled/julia"), + jl: () => import("@shikijs/langs-precompiled/julia"), + less: () => import("@shikijs/langs-precompiled/less"), + markdown: () => import("@shikijs/langs-precompiled/markdown"), + md: () => import("@shikijs/langs-precompiled/markdown"), + mdx: () => import("@shikijs/langs-precompiled/mdx"), + php: () => import("@shikijs/langs-precompiled/php"), + postcss: () => import("@shikijs/langs-precompiled/postcss"), + pug: () => import("@shikijs/langs-precompiled/pug"), + jade: () => import("@shikijs/langs-precompiled/pug"), + python: () => import("@shikijs/langs-precompiled/python"), + py: () => import("@shikijs/langs-precompiled/python"), + r: () => import("@shikijs/langs-precompiled/r"), + regexp: () => import("@shikijs/langs-precompiled/regexp"), + regex: () => import("@shikijs/langs-precompiled/regexp"), + sass: () => import("@shikijs/langs-precompiled/sass"), + scss: () => import("@shikijs/langs-precompiled/scss"), + shellscript: () => import("@shikijs/langs-precompiled/shellscript"), + bash: () => import("@shikijs/langs-precompiled/shellscript"), + sh: () => import("@shikijs/langs-precompiled/shellscript"), + shell: () => import("@shikijs/langs-precompiled/shellscript"), + zsh: () => import("@shikijs/langs-precompiled/shellscript"), + sql: () => import("@shikijs/langs-precompiled/sql"), + svelte: () => import("@shikijs/langs-precompiled/svelte"), + typescript: () => import("@shikijs/langs-precompiled/typescript"), + ts: () => import("@shikijs/langs-precompiled/typescript"), + vue: () => import("@shikijs/langs-precompiled/vue"), + "vue-html": () => import("@shikijs/langs-precompiled/vue-html"), + wasm: () => import("@shikijs/langs-precompiled/wasm"), + wgsl: () => import("@shikijs/langs-precompiled/wgsl"), + xml: () => import("@shikijs/langs-precompiled/xml"), + yaml: () => import("@shikijs/langs-precompiled/yaml"), + yml: () => import("@shikijs/langs-precompiled/yaml"), + tsx: () => import("@shikijs/langs-precompiled/tsx"), + typescriptreact: () => import("@shikijs/langs-precompiled/tsx"), + haskell: () => import("@shikijs/langs-precompiled/haskell"), + hs: () => import("@shikijs/langs-precompiled/haskell"), + "c#": () => import("@shikijs/langs-precompiled/csharp"), + csharp: () => import("@shikijs/langs-precompiled/csharp"), + cs: () => import("@shikijs/langs-precompiled/csharp"), + latex: () => import("@shikijs/langs-precompiled/latex"), + lua: () => import("@shikijs/langs-precompiled/lua"), + mermaid: () => import("@shikijs/langs-precompiled/mermaid"), + mmd: () => import("@shikijs/langs-precompiled/mermaid"), + ruby: () => import("@shikijs/langs-precompiled/ruby"), + rb: () => import("@shikijs/langs-precompiled/ruby"), + rust: () => import("@shikijs/langs-precompiled/rust"), + rs: () => import("@shikijs/langs-precompiled/rust"), + scala: () => import("@shikijs/langs-precompiled/scala"), + // Swift does not support pre-compilation right now + swift: () => import("@shikijs/langs/swift"), + kotlin: () => import("@shikijs/langs-precompiled/kotlin"), + kt: () => import("@shikijs/langs-precompiled/kotlin"), + kts: () => import("@shikijs/langs-precompiled/kotlin"), + "objective-c": () => import("@shikijs/langs-precompiled/objective-c"), + objc: () => import("@shikijs/langs-precompiled/objective-c"), +} as Record; + +const bundledThemes = { + "github-dark": () => import("@shikijs/themes/github-dark"), + "github-light": () => import("@shikijs/themes/github-light"), +} as Record; + +const createHighlighter = /* @__PURE__ */ createdBundledHighlighter< + BundledLanguage, + BundledTheme +>({ + langs: bundledLanguages, + themes: bundledThemes, + engine: () => createJavaScriptRegexEngine(), +}); + +export { createHighlighter }; +export type { BundledLanguage, BundledTheme, Highlighter }; diff --git a/packages/code-block/tsconfig.json b/packages/code-block/tsconfig.json new file mode 100644 index 0000000000..30ab55f7ca --- /dev/null +++ b/packages/code-block/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "outDir": "dist", + "declaration": true, + "declarationDir": "types", + "composite": true, + "skipLibCheck": true, + "emitDeclarationOnly": true, + "paths": { + "@shikijs/types": ["../../node_modules/@shikijs/types"] + } + }, + "include": ["src"] +} diff --git a/packages/code-block/vite.config.ts b/packages/code-block/vite.config.ts new file mode 100644 index 0000000000..954be159ec --- /dev/null +++ b/packages/code-block/vite.config.ts @@ -0,0 +1,63 @@ +import * as path from "path"; +import { webpackStats } from "rollup-plugin-webpack-stats"; +import { defineConfig } from "vite"; +import pkg from "./package.json"; +// import eslintPlugin from "vite-plugin-eslint"; + +const deps = Object.keys({ + ...pkg.dependencies, + ...pkg.peerDependencies, + ...pkg.devDependencies, +}); + +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + test: { + setupFiles: ["./vitestSetup.ts"], + }, + plugins: [webpackStats() as any], + // used so that vitest resolves the core package from the sources instead of the built version + resolve: { + alias: + conf.command === "build" + ? ({} as Record) + : ({ + // load live from sources with live reload working + "@blocknote/core": path.resolve(__dirname, "../core/src/"), + "@blocknote/react": path.resolve(__dirname, "../react/src/"), + } as Record), + }, + build: { + sourcemap: true, + lib: { + entry: { + "blocknote-code-block": path.resolve(__dirname, "src/index.ts"), + }, + name: "blocknote-code-block", + formats: ["es", "cjs"], + fileName: (format, entryName) => + format === "es" ? `${entryName}.js` : `${entryName}.cjs`, + }, + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: (source: string) => { + if (deps.includes(source)) { + return true; + } + + if (source.startsWith("@shikijs/")) { + return true; + } + + return false; + }, + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: {}, + interop: "compat", // https://rollupjs.org/migration/#changed-defaults + }, + }, + }, +})); diff --git a/packages/code-block/vitestSetup.ts b/packages/code-block/vitestSetup.ts new file mode 100644 index 0000000000..a946b5fc3a --- /dev/null +++ b/packages/code-block/vitestSetup.ts @@ -0,0 +1,10 @@ +import { afterEach, beforeEach } from "vitest"; + +beforeEach(() => { + globalThis.window = globalThis.window || ({} as any); + (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {}; +}); + +afterEach(() => { + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; +}); diff --git a/packages/core/package.json b/packages/core/package.json index f99e435d17..9e77998d89 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -63,6 +63,7 @@ }, "dependencies": { "@emoji-mart/data": "^1.2.1", + "@shikijs/types": "3.2.1", "@tiptap/core": "^2.11.5", "@tiptap/extension-bold": "^2.11.5", "@tiptap/extension-code": "^2.11.5", @@ -85,7 +86,7 @@ "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", "prosemirror-dropcursor": "^1.8.1", - "prosemirror-highlight": "^0.9.0", + "prosemirror-highlight": "^0.13.0", "prosemirror-model": "^1.24.1", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", @@ -99,7 +100,6 @@ "remark-parse": "^10.0.1", "remark-rehype": "^10.1.0", "remark-stringify": "^10.0.2", - "shiki": "^1.22.0", "unified": "^10.1.2", "uuid": "^8.3.2", "y-prosemirror": "1.2.17", diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html index 8c7757e46a..ca208287e2 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocks.html @@ -1 +1 @@ -

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Add image

\ No newline at end of file +

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Add image

\ No newline at end of file diff --git a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html index 4397413824..eeaec68e38 100644 --- a/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +++ b/packages/core/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html @@ -1 +1 @@ -

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

1280px-Placeholder_view_vector.svg.png
Placeholder

\ No newline at end of file +

Paragraph

Heading

  1. Numbered List Item

  • Bullet List Item

  • Check List Item

console.log("Hello World");

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

Table Cell

1280px-Placeholder_view_vector.svg.png
Placeholder

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html index 580c7cf6cf..8de18f9605 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html @@ -1 +1 @@ -
const hello = 'world';
console.log(hello);
\ No newline at end of file +
const hello = 'world';
console.log(hello);
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html index 48fd641e09..d8a93830eb 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html @@ -1,3 +1,3 @@ -
const hello = 'world';
+
const hello = 'world';
 console.log(hello);
 
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html index 411e8e3b89..8db77070b5 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html @@ -1 +1 @@ -
console.log('Hello, world!');
\ No newline at end of file +
console.log('Hello, world!');
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html index 43aae52f35..ef3a373f9a 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html @@ -1 +1 @@ -
console.log('Hello, world!');
\ No newline at end of file +
console.log('Hello, world!');
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html index 1cffb258fb..22baaf1757 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html index cd1f144f1b..abe6b97dc8 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/external.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/external.html index e7ee13c56b..4872e5904f 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/external.html @@ -1 +1 @@ -
print('Hello, world!')
\ No newline at end of file +
print('Hello, world!')
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html index 22e04a4e7e..fee165d05a 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html @@ -1 +1 @@ -
print('Hello, world!')
\ No newline at end of file +
print('Hello, world!')
\ No newline at end of file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md index eca2b94e33..f5b118ae95 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md @@ -1,3 +1,3 @@ -```javascript +```text console.log('Hello, world!'); ``` diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md index 04144d877f..b5c9416ec5 100644 --- a/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md +++ b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md @@ -1,2 +1,2 @@ -```javascript +```text ``` diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts index 2eb7f67851..1b9e93c414 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts @@ -71,7 +71,7 @@ describe("markdownExporter", () => { for (const document of testCase.documents) { // eslint-disable-next-line no-loop-func - it("Convert " + document.name + " to HTML", async () => { + it("Convert " + document.name + " to Markdown", async () => { const nameSplit = document.name.split("/"); await convertToMarkdownAndCompareSnapshots( editor, diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index 245ab3ba9c..cfc33073f3 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -699,7 +699,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { - "language": "javascript", + "language": "text", }, "content": [ { @@ -724,7 +724,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert "content": [ { "attrs": { - "language": "javascript", + "language": "text", }, "type": "codeBlock", }, diff --git a/packages/core/src/api/parsers/html/__snapshots__/parse-codeblocks.json b/packages/core/src/api/parsers/html/__snapshots__/parse-codeblocks.json index 1481967f47..6387ab020d 100644 --- a/packages/core/src/api/parsers/html/__snapshots__/parse-codeblocks.json +++ b/packages/core/src/api/parsers/html/__snapshots__/parse-codeblocks.json @@ -3,7 +3,7 @@ "id": "1", "type": "codeBlock", "props": { - "language": "javascript" + "language": "text" }, "content": [ { diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index 2dbfaffb5c..2b1b47e5ec 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -23,6 +23,18 @@ export const defaultSchemaTestCases: EditorTestCases< return BlockNoteEditor.create({ schema: withPageBreak(BlockNoteSchema.create()), uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, + codeBlock: { + supportedLanguages: { + javascript: { + name: "JavaScript", + aliases: ["js"], + }, + python: { + name: "Python", + aliases: ["py"], + }, + }, + }, }); }, documents: [ @@ -213,6 +225,7 @@ export const defaultSchemaTestCases: EditorTestCases< blocks: [ { type: "codeBlock", + props: { language: "javascript" }, content: "const hello = 'world';\nconsole.log(hello);\n", }, ], diff --git a/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts b/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts index 2143d9f686..094a0b65e3 100644 --- a/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts +++ b/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts @@ -2,28 +2,65 @@ import { InputRule, isTextSelection } from "@tiptap/core"; import { TextSelection } from "@tiptap/pm/state"; import { createHighlightPlugin, Parser } from "prosemirror-highlight"; import { createParser } from "prosemirror-highlight/shiki"; -import { - BundledLanguage, - bundledLanguagesInfo, - createHighlighter, - Highlighter, -} from "shiki"; import { createBlockSpecFromStronglyTypedTiptapNode, createStronglyTypedTiptapNode, PropSchema, } from "../../schema/index.js"; import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js"; -import { - defaultSupportedLanguages, - SupportedLanguageConfig, -} from "./defaultSupportedLanguages.js"; - -interface CodeBlockOptions { - defaultLanguage: string; - indentLineWithTab: boolean; - supportedLanguages: SupportedLanguageConfig[]; -} +import type { HighlighterGeneric } from "@shikijs/types"; +import { BlockNoteEditor } from "../../index.js"; + +export type CodeBlockOptions = { + /** + * Whether to indent lines with a tab when the user presses `Tab` in a code block. + * + * @default true + */ + indentLineWithTab?: boolean; + /** + * The default language to use for code blocks. + * + * @default "text" + */ + defaultLanguage?: string; + /** + * The languages that are supported in the editor. + * + * @example + * { + * javascript: { + * name: "JavaScript", + * aliases: ["js"], + * }, + * typescript: { + * name: "TypeScript", + * aliases: ["ts"], + * }, + * } + */ + supportedLanguages: Record< + string, + { + /** + * The display name of the language. + */ + name: string; + /** + * Aliases for this language. + */ + aliases?: string[]; + } + >; + /** + * The highlighter to use for code blocks. + */ + createHighlighter?: () => Promise>; +}; + +type CodeBlockConfigOptions = { + editor: BlockNoteEditor; +}; export const shikiParserSymbol = Symbol.for("blocknote.shikiParser"); export const shikiHighlighterPromiseSymbol = Symbol.for( @@ -31,8 +68,7 @@ export const shikiHighlighterPromiseSymbol = Symbol.for( ); export const defaultCodeBlockPropSchema = { language: { - default: "javascript", - values: [...defaultSupportedLanguages.map((lang) => lang.id)], + default: "text", }, } satisfies PropSchema; @@ -45,18 +81,17 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ defining: true, addOptions() { return { - defaultLanguage: "javascript", + defaultLanguage: "text", indentLineWithTab: true, - supportedLanguages: defaultSupportedLanguages, + supportedLanguages: {}, }; }, addAttributes() { - const supportedLanguages = this.options - .supportedLanguages as SupportedLanguageConfig[]; + const options = this.options as CodeBlockConfigOptions; return { language: { - default: this.options.defaultLanguage, + default: options.editor.settings.codeBlock.defaultLanguage, parseHTML: (inputElement) => { let element = inputElement as HTMLElement | null; let language: string | null = null; @@ -91,17 +126,13 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ return null; } - return ( - supportedLanguages.find(({ match }) => { - return match.includes(language); - })?.id || this.options.defaultLanguage - ); + return getLanguageId(options.editor.settings.codeBlock, language); }, renderHTML: (attributes) => { - // TODO: Use `data-language="..."` instead for easier parsing - return attributes.language && attributes.language !== "text" + return attributes.language ? { class: `language-${attributes.language}`, + "data-language": attributes.language, } : {}; }, @@ -143,8 +174,7 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ }; }, addNodeView() { - const supportedLanguages = this.options - .supportedLanguages as SupportedLanguageConfig[]; + const options = this.options as CodeBlockConfigOptions; return ({ editor, node, getPos, HTMLAttributes }) => { const pre = document.createElement("pre"); @@ -169,7 +199,9 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ }); }; - supportedLanguages.forEach(({ id, name }) => { + Object.entries( + options.editor.settings.codeBlock.supportedLanguages + ).forEach(([id, { name }]) => { const option = document.createElement("option"); option.value = id; @@ -178,7 +210,9 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ }); selectWrapper.contentEditable = "false"; - select.value = node.attrs.language || this.options.defaultLanguage; + select.value = + node.attrs.language || + options.editor.settings.codeBlock.defaultLanguage; dom.removeChild(contentDOM); dom.appendChild(selectWrapper); dom.appendChild(pre); @@ -203,24 +237,30 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ }; }, addProseMirrorPlugins() { - const supportedLanguages = this.options - .supportedLanguages as SupportedLanguageConfig[]; + const options = this.options as CodeBlockConfigOptions; const globalThisForShiki = globalThis as { - [shikiHighlighterPromiseSymbol]?: Promise; + [shikiHighlighterPromiseSymbol]?: Promise>; [shikiParserSymbol]?: Parser; }; - let highlighter: Highlighter | undefined; + let highlighter: HighlighterGeneric | undefined; let parser: Parser | undefined; - - const lazyParser: Parser = (options) => { + let hasWarned = false; + const lazyParser: Parser = (parserOptions) => { + if (!options.editor.settings.codeBlock.createHighlighter) { + if (process.env.NODE_ENV === "development" && !hasWarned) { + // eslint-disable-next-line no-console + console.log( + "For syntax highlighting of code blocks, you must provide a highlighter function" + ); + hasWarned = true; + } + return []; + } if (!highlighter) { globalThisForShiki[shikiHighlighterPromiseSymbol] = globalThisForShiki[shikiHighlighterPromiseSymbol] || - createHighlighter({ - themes: ["github-dark"], - langs: [], - }); + options.editor.settings.codeBlock.createHighlighter(); return globalThisForShiki[shikiHighlighterPromiseSymbol].then( (createdHighlighter) => { @@ -229,25 +269,25 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ ); } - const language = options.language; + const language = parserOptions.language; if ( language && language !== "text" && !highlighter.getLoadedLanguages().includes(language) && - supportedLanguages.find(({ id }) => id === language) && - bundledLanguagesInfo.find(({ id }) => id === language) + language in options.editor.settings.codeBlock.supportedLanguages ) { - return highlighter.loadLanguage(language as BundledLanguage); + return highlighter.loadLanguage(language); } if (!parser) { parser = - globalThisForShiki[shikiParserSymbol] || createParser(highlighter); + globalThisForShiki[shikiParserSymbol] || + createParser(highlighter as any); globalThisForShiki[shikiParserSymbol] = parser; } - return parser(options); + return parser(parserOptions); }; const shikiLazyPlugin = createHighlightPlugin({ @@ -259,8 +299,7 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ return [shikiLazyPlugin]; }, addInputRules() { - const supportedLanguages = this.options - .supportedLanguages as SupportedLanguageConfig[]; + const options = this.options as CodeBlockConfigOptions; return [ new InputRule({ @@ -269,10 +308,10 @@ const CodeBlockContent = createStronglyTypedTiptapNode({ const $start = state.doc.resolve(range.from); const languageName = match[1].trim(); const attributes = { - language: - supportedLanguages.find(({ match }) => { - return match.includes(languageName); - })?.id || this.options.defaultLanguage, + language: getLanguageId( + options.editor.settings.codeBlock, + languageName + ), }; if ( @@ -383,18 +422,10 @@ export const CodeBlock = createBlockSpecFromStronglyTypedTiptapNode( defaultCodeBlockPropSchema ); -export function customizeCodeBlock(options: Partial) { - return createBlockSpecFromStronglyTypedTiptapNode( - CodeBlockContent.configure(options), - { - language: { - default: - options.defaultLanguage || - defaultCodeBlockPropSchema.language.default, - values: - options.supportedLanguages?.map((lang) => lang.id) || - defaultCodeBlockPropSchema.language.values, - }, - } +function getLanguageId(options: CodeBlockOptions, languageName: string) { + return ( + Object.entries(options.supportedLanguages).find(([id, { aliases }]) => { + return aliases?.includes(languageName) || id === languageName; + })?.[0] || languageName ); } diff --git a/packages/core/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts b/packages/core/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts deleted file mode 100644 index b5da23b0cd..0000000000 --- a/packages/core/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { bundledLanguagesInfo } from "shiki"; - -export type SupportedLanguageConfig = { - id: string; - name: string; - match: string[]; -}; - -export const defaultSupportedLanguages: SupportedLanguageConfig[] = [ - { - id: "text", - name: "Plain Text", - match: ["text", "txt", "plain"], - }, - ...bundledLanguagesInfo - .filter((lang) => { - return [ - "c", - "cpp", - "css", - "glsl", - "graphql", - "haml", - "html", - "java", - "javascript", - "json", - "jsonc", - "jsonl", - "jsx", - "julia", - "less", - "markdown", - "mdx", - "php", - "postcss", - "pug", - "python", - "r", - "regexp", - "sass", - "scss", - "shellscript", - "sql", - "svelte", - "typescript", - "vue", - "vue-html", - "wasm", - "wgsl", - "xml", - "yaml", - ].includes(lang.id); - }) - .map((lang) => ({ - match: [lang.id, ...(lang.aliases || [])], - id: lang.id, - name: lang.name, - })), - { id: "tsx", name: "TSX", match: ["tsx", "typescriptreact"] }, - { - id: "haskell", - name: "Haskell", - match: ["haskell", "hs"], - }, - { - id: "csharp", - name: "C#", - match: ["c#", "csharp", "cs"], - }, - { - id: "latex", - name: "LaTeX", - match: ["latex"], - }, - { - id: "lua", - name: "Lua", - match: ["lua"], - }, - { - id: "mermaid", - name: "Mermaid", - match: ["mermaid", "mmd"], - }, - { - id: "ruby", - name: "Ruby", - match: ["ruby", "rb"], - }, - { - id: "rust", - name: "Rust", - match: ["rust", "rs"], - }, - { - id: "scala", - name: "Scala", - match: ["scala"], - }, - { - id: "swift", - name: "Swift", - match: ["swift"], - }, - { - id: "kotlin", - name: "Kotlin", - match: ["kotlin", "kt", "kts"], - }, - { - id: "objective-c", - name: "Objective C", - match: ["objective-c", "objc"], - }, -]; diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 81dc0e49ab..e57e59f489 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -32,8 +32,6 @@ import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent.js"; import { Table } from "./TableBlockContent/TableBlockContent.js"; import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js"; -export { customizeCodeBlock } from "./CodeBlockContent/CodeBlockContent.js"; - export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index a0fd0a1d53..72582272e0 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -101,6 +101,7 @@ import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js"; import type { ThreadStore, User } from "../comments/index.js"; import "../style.css"; import { EventEmitter } from "../util/EventEmitter.js"; +import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js"; export type BlockNoteExtensionFactory = ( editor: BlockNoteEditor @@ -156,6 +157,11 @@ export type BlockNoteEditorOptions< showCursorLabels?: "always" | "activity"; }; + /** + * Options for code blocks. + */ + codeBlock?: CodeBlockOptions; + comments: { threadStore: ThreadStore; }; @@ -442,6 +448,7 @@ export class BlockNoteEditor< cellTextColor: boolean; headers: boolean; }; + codeBlock: CodeBlockOptions; }; public static create< @@ -489,6 +496,12 @@ export class BlockNoteEditor< cellTextColor: options?.tables?.cellTextColor ?? false, headers: options?.tables?.headers ?? false, }, + codeBlock: { + indentLineWithTab: options?.codeBlock?.indentLineWithTab ?? true, + defaultLanguage: options?.codeBlock?.defaultLanguage ?? "text", + supportedLanguages: options?.codeBlock?.supportedLanguages ?? {}, + createHighlighter: options?.codeBlock?.createHighlighter ?? undefined, + }, }; // apply defaults diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3ec64b0dc6..7439917132 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -56,6 +56,8 @@ export * from "./util/esmDependencies.js"; export * from "./util/table.js"; export * from "./util/string.js"; export * from "./util/typescript.js"; + +export type { CodeBlockOptions } from "./blocks/CodeBlockContent/CodeBlockContent.js"; export { UnreachableCaseError, assertEmpty } from "./util/typescript.js"; export { locales }; @@ -69,4 +71,3 @@ export * from "./extensions/UniqueID/UniqueID.js"; export * from "./api/exporters/markdown/markdownExporter.js"; export * from "./api/parsers/html/parseHTML.js"; export * from "./api/parsers/markdown/parseMarkdown.js"; - diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index a894e90f9e..bbfc728065 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -4,7 +4,7 @@ "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], - "moduleResolution": "Node", + "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "sourceMap": true, diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts index c5f9b60463..57d6d3a2f0 100644 --- a/packages/core/vite.config.ts +++ b/packages/core/vite.config.ts @@ -32,7 +32,11 @@ export default defineConfig({ if (deps.includes(source)) { return true; } - return source.startsWith("prosemirror-") || source.startsWith("shiki/"); + return ( + source.startsWith("prosemirror-") || + source.startsWith("@shikijs/lang") || + source.startsWith("@shikijs/theme") + ); }, output: { // Provide global variables to use in the UMD build diff --git a/playground/package.json b/playground/package.json index 5e8552dc8f..670d3a1592 100644 --- a/playground/package.json +++ b/playground/package.json @@ -13,6 +13,7 @@ "@aws-sdk/client-s3": "^3.609.0", "@aws-sdk/s3-request-presigner": "^3.609.0", "@blocknote/ariakit": "^0.26.0", + "@blocknote/code-block": "^0.26.0", "@blocknote/core": "^0.26.0", "@blocknote/mantine": "^0.26.0", "@blocknote/react": "^0.26.0", diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 122f6c5c9a..5d1fc918c2 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -798,6 +798,42 @@ "pathFromRoot": "examples/04-theming", "slug": "theming" } + }, + { + "projectSlug": "code-block", + "fullSlug": "theming/code-block", + "pathFromRoot": "examples/04-theming/06-code-block", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Basic" + ] + }, + "title": "Code Block Syntax Highlighting", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" + } + }, + { + "projectSlug": "custom-code-block", + "fullSlug": "theming/custom-code-block", + "pathFromRoot": "examples/04-theming/07-custom-code-block", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Basic" + ] + }, + "title": "Custom Code Block Theme & Language", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" + } } ] },