diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx index 2fe5509438..7176c8b82d 100644 --- a/examples/01-basic/03-all-blocks/App.tsx +++ b/examples/01-basic/03-all-blocks/App.tsx @@ -44,6 +44,11 @@ export default function App() { type: "checkListItem", content: "Check List Item", }, + { + type: "codeBlock", + props: { language: "javascript" }, + content: "console.log('Hello, world!');", + }, { type: "table", content: { diff --git a/package-lock.json b/package-lock.json index b06a74c12b..8be6162258 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8067,6 +8067,117 @@ "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", "dev": true }, + "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", + "dependencies": { + "@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", + "hast-util-to-html": "^9.0.3" + } + }, + "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": "*" + } + }, + "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" + }, + "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", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@shikijs/core/node_modules/hast-util-whitespace": { + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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", + "dependencies": { + "@shikijs/types": "1.22.1", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-js": "0.4.3" + } + }, + "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", + "dependencies": { + "@shikijs/types": "1.22.1", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "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", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/types/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": "*" + } + }, + "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" + }, "node_modules/@shuding/opentype.js": { "version": "1.4.0-beta.0", "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", @@ -21633,6 +21744,18 @@ "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", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -24114,6 +24237,12 @@ "@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/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -28561,6 +28690,7 @@ "@tiptap/pm": "^2.7.1", "emoji-mart": "^5.6.0", "hast-util-from-dom": "^4.2.0", + "prosemirror-highlight": "^0.9.0", "prosemirror-model": "^1.21.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.1", @@ -28574,6 +28704,7 @@ "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.12", @@ -28582,7 +28713,7 @@ }, "devDependencies": { "@types/emoji-mart": "^3.0.14", - "@types/hast": "^2.3.4", + "@types/hast": "^3.0.0", "@types/uuid": "^8.3.4", "eslint": "^8.10.0", "jsdom": "^21.1.0", @@ -28595,6 +28726,68 @@ "vitest": "^2.0.3" } }, + "packages/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": "*" + } + }, + "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", + "funding": { + "url": "https://github.com/sponsors/ocavue" + }, + "peerDependencies": { + "@types/hast": "^3.0.0", + "highlight.js": "^11.9.0", + "lowlight": "^3.1.0", + "prosemirror-model": "^1.19.3", + "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" + }, + "peerDependenciesMeta": { + "@types/hast": { + "optional": true + }, + "highlight.js": { + "optional": true + }, + "lowlight": { + "optional": true + }, + "prosemirror-model": { + "optional": true + }, + "prosemirror-state": { + "optional": true + }, + "prosemirror-transform": { + "optional": true + }, + "prosemirror-view": { + "optional": true + }, + "refractor": { + "optional": true + }, + "shiki": { + "optional": true + }, + "sugar-high": { + "optional": true + } + } + }, "packages/core/node_modules/rimraf": { "version": "5.0.7", "dev": true, @@ -28612,6 +28805,20 @@ "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.17.1", diff --git a/packages/core/package.json b/packages/core/package.json index b116b4d6f3..7d71429cbd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -97,11 +97,13 @@ "uuid": "^8.3.2", "y-prosemirror": "1.2.12", "y-protocols": "^1.0.6", - "yjs": "^13.6.15" + "yjs": "^13.6.15", + "prosemirror-highlight": "^0.9.0", + "shiki": "^1.22.0" }, "devDependencies": { "@types/emoji-mart": "^3.0.14", - "@types/hast": "^2.3.4", + "@types/hast": "^3.0.0", "@types/uuid": "^8.3.4", "eslint": "^8.10.0", "jsdom": "^21.1.0", diff --git a/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts b/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts index 9876281a76..b5b3fbe546 100644 --- a/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +++ b/packages/core/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts @@ -1,4 +1,5 @@ export const acceptedMIMETypes = [ + "vscode-editor-data", "blocknote/html", "Files", "text/html", diff --git a/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts b/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts new file mode 100644 index 0000000000..697a880dec --- /dev/null +++ b/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts @@ -0,0 +1,49 @@ +import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../../schema/index.js"; + +export async function handleVSCodePaste< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>(event: ClipboardEvent, editor: BlockNoteEditor) { + const view = editor.prosemirrorView; + const { schema } = view.state; + + if (!event.clipboardData) { + return false; + } + + const text = event.clipboardData!.getData("text/plain"); + const vscode = event.clipboardData!.getData("vscode-editor-data"); + const vscodeData = vscode ? JSON.parse(vscode) : undefined; + const language = vscodeData?.mode; + + if (!text) { + return false; + } + + if (!schema.nodes.codeBlock) { + view.pasteText(text); + + return true; + } + + if (!language) { + return false; + } + + // strip carriage return chars from text pasted as code + // see: https://github.com/ProseMirror/prosemirror-view/commit/a50a6bcceb4ce52ac8fcc6162488d8875613aacd + editor._tiptapEditor.view.pasteHTML( + `
${text.replace(
+      /\r\n?/g,
+      "\n"
+    )}
` + ); + + return true; +} diff --git a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts index 51d3fcb35e..bd180363ba 100644 --- a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts +++ b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts @@ -10,6 +10,7 @@ import { import { nestedListsToBlockNoteStructure } from "../../parsers/html/util/nestedLists.js"; import { acceptedMIMETypes } from "./acceptedMIMETypes.js"; import { handleFileInsertion } from "./handleFileInsertion.js"; +import { handleVSCodePaste } from "./handleVSCodePaste.js"; export const createPasteFromClipboardExtension = < BSchema extends BlockSchema, @@ -43,6 +44,11 @@ export const createPasteFromClipboardExtension = < return true; } + if (format === "vscode-editor-data") { + handleVSCodePaste(event, editor); + return true; + } + if (format === "Files") { handleFileInsertion(event, editor); return true; 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 new file mode 100644 index 0000000000..411e8e3b89 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html @@ -0,0 +1 @@ +
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 new file mode 100644 index 0000000000..43aae52f35 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html @@ -0,0 +1 @@ +
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 new file mode 100644 index 0000000000..1cffb258fb --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html @@ -0,0 +1 @@ +
\ 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 new file mode 100644 index 0000000000..cd1f144f1b --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html @@ -0,0 +1 @@ +
\ 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 new file mode 100644 index 0000000000..e7ee13c56b --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/external.html @@ -0,0 +1 @@ +
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 new file mode 100644 index 0000000000..22e04a4e7e --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html @@ -0,0 +1 @@ +
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 new file mode 100644 index 0000000000..eca2b94e33 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md @@ -0,0 +1,3 @@ +```javascript +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 new file mode 100644 index 0000000000..04144d877f --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md @@ -0,0 +1,2 @@ +```javascript +``` diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/python/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/python/markdown.md new file mode 100644 index 0000000000..c1908c03b2 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/codeBlock/python/markdown.md @@ -0,0 +1,3 @@ +```python +print('Hello, world!') +``` 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 942a525b4c..4b6cebdb80 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -662,6 +662,75 @@ exports[`Test BlockNote-Prosemirror conversion > Case: custom style schema > Con } `; +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert codeBlock/defaultLanguage to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "language": "javascript", + }, + "content": [ + { + "text": "console.log('Hello, world!');", + "type": "text", + }, + ], + "type": "codeBlock", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert codeBlock/empty to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "language": "javascript", + }, + "type": "codeBlock", + }, + ], + "type": "blockContainer", +} +`; + +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert codeBlock/python to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "attrs": { + "language": "python", + }, + "content": [ + { + "text": "print('Hello, world!')", + "type": "text", + }, + ], + "type": "codeBlock", + }, + ], + "type": "blockContainer", +} +`; + exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert complex/misc to/from prosemirror 1`] = ` { "attrs": { diff --git a/packages/core/src/api/parsers/markdown/parseMarkdown.ts b/packages/core/src/api/parsers/markdown/parseMarkdown.ts index 0cebb80856..202393b288 100644 --- a/packages/core/src/api/parsers/markdown/parseMarkdown.ts +++ b/packages/core/src/api/parsers/markdown/parseMarkdown.ts @@ -12,7 +12,7 @@ import { HTMLToBlocks } from "../html/parseHTML.js"; // modified version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js // that outputs a data-language attribute instead of a CSS class (e.g.: language-typescript) function code(state: any, node: any) { - const value = node.value ? node.value + "\n" : ""; + const value = node.value ? node.value : ""; /** @type {Properties} */ const properties: any = {}; diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index da8c790e8d..2510f4e040 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -175,6 +175,33 @@ export const defaultSchemaTestCases: EditorTestCases< }, ], }, + { + name: "codeBlock/empty", + blocks: [ + { + type: "codeBlock", + }, + ], + }, + { + name: "codeBlock/defaultLanguage", + blocks: [ + { + type: "codeBlock", + content: "console.log('Hello, world!');", + }, + ], + }, + { + name: "codeBlock/python", + blocks: [ + { + type: "codeBlock", + props: { language: "python" }, + content: "print('Hello, world!')", + }, + ], + }, { name: "file/button", blocks: [ diff --git a/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts b/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts new file mode 100644 index 0000000000..139cd5fbf5 --- /dev/null +++ b/packages/core/src/blocks/CodeBlockContent/CodeBlockContent.ts @@ -0,0 +1,369 @@ +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[]; +} + +export const defaultCodeBlockPropSchema = { + language: { + default: "javascript", + values: [...defaultSupportedLanguages.map((lang) => lang.id)], + }, +} satisfies PropSchema; + +const CodeBlockContent = createStronglyTypedTiptapNode({ + name: "codeBlock", + content: "inline*", + group: "blockContent", + marks: "", + code: true, + defining: true, + addOptions() { + return { + defaultLanguage: "javascript", + indentLineWithTab: true, + supportedLanguages: defaultSupportedLanguages, + }; + }, + addAttributes() { + return { + language: { + default: this.options.defaultLanguage, + parseHTML: (inputElement) => { + let element = inputElement as HTMLElement | null; + + if ( + element?.tagName === "DIV" && + element?.dataset.contentType === "codeBlock" + ) { + element = element.children[0] as HTMLElement | null; + } + + if (element?.tagName === "PRE") { + element = element?.children[0] as HTMLElement | null; + } + + const dataLanguage = element?.getAttribute("data-language"); + + if (dataLanguage) { + return dataLanguage.toLowerCase(); + } + + const classNames = [...(element?.className.split(" ") || [])]; + const languages = classNames + .filter((className) => className.startsWith("language-")) + .map((className) => className.replace("language-", "")); + const [language] = languages; + + if (!language) { + return null; + } + + return language.toLowerCase(); + }, + renderHTML: (attributes) => { + return attributes.language && attributes.language !== "text" + ? { + class: `language-${attributes.language}`, + } + : {}; + }, + }, + }; + }, + parseHTML() { + return [ + { + tag: "div[data-content-type=" + this.name + "]", + }, + { + tag: "pre", + preserveWhitespace: "full", + }, + ]; + }, + renderHTML({ HTMLAttributes }) { + const pre = document.createElement("pre"); + const { dom, contentDOM } = createDefaultBlockDOMOutputSpec( + this.name, + "code", + this.options.domAttributes?.blockContent || {}, + { + ...(this.options.domAttributes?.inlineContent || {}), + ...HTMLAttributes, + } + ); + + dom.removeChild(contentDOM); + dom.appendChild(pre); + pre.appendChild(contentDOM); + + return { + dom, + contentDOM, + }; + }, + addNodeView() { + const supportedLanguages = this.options + .supportedLanguages as SupportedLanguageConfig[]; + + return ({ editor, node, getPos, HTMLAttributes }) => { + const pre = document.createElement("pre"); + const select = document.createElement("select"); + const selectWrapper = document.createElement("div"); + const { dom, contentDOM } = createDefaultBlockDOMOutputSpec( + this.name, + "code", + { + ...(this.options.domAttributes?.blockContent || {}), + ...HTMLAttributes, + }, + this.options.domAttributes?.inlineContent || {} + ); + const handleLanguageChange = (event: Event) => { + const language = (event.target as HTMLSelectElement).value; + + editor.commands.command(({ tr }) => { + tr.setNodeAttribute(getPos(), "language", language); + + return true; + }); + }; + + supportedLanguages.forEach(({ id, name }) => { + const option = document.createElement("option"); + + option.value = id; + option.text = name; + select.appendChild(option); + }); + + selectWrapper.contentEditable = "false"; + select.value = node.attrs.language || this.options.defaultLanguage; + dom.removeChild(contentDOM); + dom.appendChild(selectWrapper); + dom.appendChild(pre); + pre.appendChild(contentDOM); + selectWrapper.appendChild(select); + select.addEventListener("change", handleLanguageChange); + + return { + dom, + contentDOM, + update: (newNode) => { + if (newNode.type !== this.type) { + return false; + } + + return true; + }, + destroy: () => { + select.removeEventListener("change", handleLanguageChange); + }, + }; + }; + }, + addProseMirrorPlugins() { + let highlighter: Highlighter | undefined; + let parser: Parser | undefined; + + const supportedLanguages = this.options + .supportedLanguages as SupportedLanguageConfig[]; + const lazyParser: Parser = (options) => { + if (!highlighter) { + return createHighlighter({ + themes: ["github-dark"], + langs: [], + }).then((createdHighlighter) => { + highlighter = createdHighlighter; + }); + } + + const language = options.language; + + if ( + language && + language !== "text" && + !highlighter.getLoadedLanguages().includes(language) && + supportedLanguages.find(({ id }) => id === language) && + bundledLanguagesInfo.find(({ id }) => id === language) + ) { + return highlighter.loadLanguage(language as BundledLanguage); + } + + if (!parser) { + parser = createParser(highlighter); + } + + return parser(options); + }; + + const shikiLazyPlugin = createHighlightPlugin({ + parser: lazyParser, + languageExtractor: (node) => node.attrs.language, + nodeTypes: [this.name], + }); + + return [shikiLazyPlugin]; + }, + addInputRules() { + const supportedLanguages = this.options + .supportedLanguages as SupportedLanguageConfig[]; + + return [ + new InputRule({ + find: /^```(.*?)\s$/, + handler: ({ state, range, match }) => { + 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, + }; + + if ( + !$start + .node(-1) + .canReplaceWith( + $start.index(-1), + $start.indexAfter(-1), + this.type + ) + ) { + return null; + } + + state.tr + .delete(range.from, range.to) + .setBlockType(range.from, range.from, this.type, attributes) + .setSelection(TextSelection.create(state.tr.doc, range.from)); + + return; + }, + }), + ]; + }, + addKeyboardShortcuts() { + return { + Delete: ({ editor }) => { + const { selection } = editor.state; + const { $from } = selection; + + // When inside empty codeblock, on `DELETE` key press, delete the codeblock + if ( + editor.isActive(this.name) && + !$from.parent.textContent && + isTextSelection(selection) + ) { + // Get the start position of the codeblock for node selection + const from = $from.pos - $from.parentOffset - 2; + + editor.chain().setNodeSelection(from).deleteSelection().run(); + + return true; + } + + return false; + }, + Tab: ({ editor }) => { + if (!this.options.indentLineWithTab) { + return false; + } + if (editor.isActive(this.name)) { + editor.commands.insertContent(" "); + return true; + } + + return false; + }, + Enter: ({ editor }) => { + const { $from } = editor.state.selection; + + if (!editor.isActive(this.name)) { + return false; + } + + const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2; + const endsWithDoubleNewline = $from.parent.textContent.endsWith("\n\n"); + + if (!isAtEnd || !endsWithDoubleNewline) { + editor.commands.insertContent("\n"); + return true; + } + + return editor + .chain() + .command(({ tr }) => { + tr.delete($from.pos - 2, $from.pos); + + return true; + }) + .exitCode() + .run(); + }, + "Shift-Enter": ({ editor }) => { + const { $from } = editor.state.selection; + + if (!editor.isActive(this.name)) { + return false; + } + + editor + .chain() + .insertContentAt( + $from.pos - $from.parentOffset + $from.parent.nodeSize, + { + type: "paragraph", + } + ) + .run(); + + return true; + }, + }; + }, +}); + +export const CodeBlock = createBlockSpecFromStronglyTypedTiptapNode( + CodeBlockContent, + 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, + }, + } + ); +} diff --git a/packages/core/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts b/packages/core/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts new file mode 100644 index 0000000000..36fa7e712e --- /dev/null +++ b/packages/core/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts @@ -0,0 +1,96 @@ +import { bundledLanguagesInfo } from "shiki/bundle/web"; + +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 ![ + "angular-html", + "angular-ts", + "astro", + "blade", + "coffee", + "handlebars", + "html-derivative", + "http", + "imba", + "jinja", + "jison", + "json5", + "marko", + "mdc", + "stylus", + "ts-tags", + ].includes(lang.id); + }) + .map((lang) => ({ + match: [lang.id, ...(lang.aliases || [])], + id: lang.id, + name: lang.name, + })), + { + 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 c51384db79..81dc0e49ab 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -21,6 +21,7 @@ import { } from "../schema/index.js"; import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; +import { CodeBlock } from "./CodeBlockContent/CodeBlockContent.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -31,9 +32,12 @@ 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, + codeBlock: CodeBlock, bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index cc9df706d9..40cf64cd14 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -196,7 +196,8 @@ NESTED BLOCKS cursor: pointer; } -.bn-block-content[data-content-type="checkListItem"][data-checked="true"] .bn-inline-content { +.bn-block-content[data-content-type="checkListItem"][data-checked="true"] + .bn-inline-content { text-decoration: line-through; } @@ -259,6 +260,51 @@ NESTED BLOCKS content: "▪"; } +/* CODE BLOCKS */ +.bn-block-content[data-content-type="codeBlock"] { + position: relative; + + background-color: rgb(22 22 22); + color: white; + border-radius: 8px; +} +.bn-block-content[data-content-type="codeBlock"] > pre { + white-space: pre; + overflow-x: auto; + margin: 0; + width: 100%; + tab-size: 2; + + padding: 24px; +} +.bn-block-content[data-content-type="codeBlock"] > div { + outline: none !important; +} +.bn-block-content[data-content-type="codeBlock"] > div > select { + outline: none !important; + appearance: none; + user-select: none; + border: none; + cursor: pointer; + background-color: transparent; + + position: absolute; + top: 8px; + left: 18px; + + font-size: 0.8em; + color: white; + + opacity: 0; + transition: opacity 0.3s; + transition-delay: 1s; +} +.bn-block-content[data-content-type="codeBlock"]:hover > div > select, +.bn-block-content[data-content-type="codeBlock"] > div > select:focus { + opacity: 0.5; + transition-delay: 0.1s; +} + /* FILES */ /* Add block button & default preview */ @@ -349,7 +395,8 @@ NESTED BLOCKS cursor: ew-resize; } -[data-content-type="audio"] > .bn-file-block-content-wrapper, .bn-audio { +[data-content-type="audio"] > .bn-file-block-content-wrapper, +.bn-audio { width: 100%; } diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts index f779b5b6c3..70fcafc06f 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts @@ -35,6 +35,14 @@ export class FormattingToolbarView implements PluginView { const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection); + // Don't show toolbar inside code blocks + if ( + selection.$from.parent.type.spec.code || + (isNodeSelection(selection) && selection.node.type.spec.code) + ) { + return false; + } + // check view.hasFocus so that the toolbar doesn't show up when the editor is not focused or when for example a code block is focused return !(!view.hasFocus() || empty || isEmptyTextBlock); }; diff --git a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts index 166d0c1f21..0265e02012 100644 --- a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts +++ b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts @@ -88,6 +88,11 @@ export const PlaceholderPlugin = ( return; } + // Don't show placeholder when the cursor is inside a code block + if (selection.$from.parent.type.spec.code) { + return; + } + const $pos = selection.$anchor; const node = $pos.parent; diff --git a/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts b/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts index f0634c6eab..332b408592 100644 --- a/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts +++ b/packages/core/src/extensions/SuggestionMenu/SuggestionPlugin.ts @@ -202,6 +202,11 @@ export class SuggestionMenuProseMirrorPlugin< return prev; } + // Ignore transactions in code blocks. + if (transaction.selection.$from.parent.type.spec.code) { + return prev; + } + // Either contains the trigger character if the menu should be shown, // or null if it should be hidden. const suggestionPluginTransactionMeta: { diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index 8a2bec0eaa..269babc8ff 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -175,6 +175,24 @@ export function getDefaultSlashMenuItems< }); } + if (checkDefaultBlockTypeInSchema("codeBlock", editor)) { + items.push({ + onItemClick: () => { + const pos = editor._tiptapEditor.state.selection.from; + + insertOrUpdateBlock(editor, { + type: "codeBlock", + }); + + // Move the cursor inside the code block + editor._tiptapEditor.commands.setTextSelection(pos); + }, + badge: formatKeyboardShortcut("Mod-Alt-c"), + key: "code_block", + ...editor.dictionary.slash_menu.code_block, + }); + } + if (checkDefaultBlockTypeInSchema("table", editor)) { items.push({ onItemClick: () => { diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts index c00df232e7..b18a202599 100644 --- a/packages/core/src/i18n/locales/ar.ts +++ b/packages/core/src/i18n/locales/ar.ts @@ -52,6 +52,12 @@ export const ar: Dictionary = { aliases: ["ف", "فقرة"], group: "الكتل الأساسية", }, + code_block: { + title: "كود", + subtext: "يستخدم لعرض الكود مع تحديد الصيغة", + aliases: ["كود", "مسبق"], + group: "الكتل الأساسية", + }, table: { title: "جدول", subtext: "يستخدم للجداول", diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts index b679171286..8644895035 100644 --- a/packages/core/src/i18n/locales/de.ts +++ b/packages/core/src/i18n/locales/de.ts @@ -1,307 +1,312 @@ export const de = { - slash_menu: { - heading: { - title: "Überschrift 1", - subtext: "Hauptebene Überschrift", - aliases: ["h", "überschrift1", "h1"], - group: "Überschriften", - }, - heading_2: { - title: "Überschrift 2", - subtext: "Wichtige Abschnittsüberschrift", - aliases: ["h2", "überschrift2", "unterüberschrift"], - group: "Überschriften", - }, - heading_3: { - title: "Überschrift 3", - subtext: "Unterabschnitts- und Gruppenüberschrift", - aliases: ["h3", "überschrift3", "unterüberschrift"], - group: "Überschriften", - }, - numbered_list: { - title: "Nummerierte Liste", - subtext: "Liste mit nummerierten Elementen", - aliases: ["ol", "li", "liste", "nummerierteliste", "nummerierte liste"], - group: "Grundlegende Blöcke", - }, - bullet_list: { - title: "Aufzählungsliste", - subtext: "Liste mit unnummerierten Elementen", - aliases: ["ul", "li", "liste", "aufzählungsliste", "aufzählung liste"], - group: "Grundlegende Blöcke", - }, - check_list: { - title: "Checkliste", - subtext: "Liste mit Kontrollkästchen", - aliases: [ - "ul", - "li", - "liste", - "checkliste", - "check liste", - "geprüfte liste", - "kontrollkästchen", - ], - group: "Grundlegende Blöcke", - }, - paragraph: { - title: "Absatz", - subtext: "Der Hauptteil Ihres Dokuments", - aliases: ["p", "absatz"], - group: "Grundlegende Blöcke", - }, - table: { - title: "Tabelle", - subtext: "Tabelle mit editierbaren Zellen", - aliases: ["tabelle"], - group: "Erweitert", - }, - image: { - title: "Bild", - subtext: "Größenveränderbares Bild mit Beschriftung", - aliases: [ - "bild", - "bildhochladen", - "hochladen", - "img", - "bild", - "medien", - "url", - ], - group: "Medien", - }, - video: { - title: "Video", - subtext: "Größenveränderbares Video mit Beschriftung", - aliases: [ - "video", - "videoupload", - "hochladen", - "mp4", - "film", - "medien", - "url", - ], - group: "Medien", - }, - audio: { - title: "Audio", - subtext: "Eingebettetes Audio mit Beschriftung", - aliases: [ - "audio", - "audioupload", - "hochladen", - "mp3", - "ton", - "medien", - "url", - ], - group: "Medien", - }, - file: { - title: "Datei", - subtext: "Eingebettete Datei", - aliases: ["datei", "hochladen", "einbetten", "medien", "url"], - group: "Medien", - }, - emoji: { - title: "Emoji", - subtext: "Nach Emoji suchen und einfügen", - aliases: ["emoji", "emote", "emotion", "gesicht"], - group: "Andere", - }, + slash_menu: { + heading: { + title: "Überschrift 1", + subtext: "Hauptebene Überschrift", + aliases: ["h", "überschrift1", "h1"], + group: "Überschriften", }, - placeholders: { - default: "Text eingeben oder '/' für Befehle tippen", - heading: "Überschrift", - bulletListItem: "Liste", - numberedListItem: "Liste", - checkListItem: "Liste", + heading_2: { + title: "Überschrift 2", + subtext: "Wichtige Abschnittsüberschrift", + aliases: ["h2", "überschrift2", "unterüberschrift"], + group: "Überschriften", }, - file_blocks: { - image: { - add_button_text: "Bild hinzufügen", - }, - video: { - add_button_text: "Video hinzufügen", - }, - audio: { - add_button_text: "Audio hinzufügen", - }, - file: { - add_button_text: "Datei hinzufügen", - }, + heading_3: { + title: "Überschrift 3", + subtext: "Unterabschnitts- und Gruppenüberschrift", + aliases: ["h3", "überschrift3", "unterüberschrift"], + group: "Überschriften", }, - side_menu: { - add_block_label: "Block hinzufügen", - drag_handle_label: "Blockmenü öffnen", - }, - drag_handle: { - delete_menuitem: "Löschen", - colors_menuitem: "Farben", - }, - table_handle: { - delete_column_menuitem: "Spalte löschen", - delete_row_menuitem: "Zeile löschen", - add_left_menuitem: "Spalte links hinzufügen", - add_right_menuitem: "Spalte rechts hinzufügen", - add_above_menuitem: "Zeile oberhalb hinzufügen", - add_below_menuitem: "Zeile unterhalb hinzufügen", - }, - suggestion_menu: { - no_items_title: "Keine Elemente gefunden", - loading: "Laden…", - }, - color_picker: { - text_title: "Text", - background_title: "Hintergrund", - colors: { - default: "Standard", - gray: "Grau", - brown: "Braun", - red: "Rot", - orange: "Orange", - yellow: "Gelb", - green: "Grün", - blue: "Blau", - purple: "Lila", - pink: "Rosa", - }, + numbered_list: { + title: "Nummerierte Liste", + subtext: "Liste mit nummerierten Elementen", + aliases: ["ol", "li", "liste", "nummerierteliste", "nummerierte liste"], + group: "Grundlegende Blöcke", }, - formatting_toolbar: { - bold: { - tooltip: "Fett", - secondary_tooltip: "Mod+B", - }, - italic: { - tooltip: "Kursiv", - secondary_tooltip: "Mod+I", - }, - underline: { - tooltip: "Unterstrichen", - secondary_tooltip: "Mod+U", - }, - strike: { - tooltip: "Durchgestrichen", - secondary_tooltip: "Mod+Shift+S", - }, - code: { - tooltip: "Code", - secondary_tooltip: "", - }, - colors: { - tooltip: "Farben", - }, - link: { - tooltip: "Link erstellen", - secondary_tooltip: "Mod+K", - }, - file_caption: { - tooltip: "Beschriftung bearbeiten", - input_placeholder: "Beschriftung bearbeiten", - }, - file_replace: { - tooltip: { - image: "Bild ersetzen", - video: "Video ersetzen", - audio: "Audio ersetzen", - file: "Datei ersetzen", - }, - }, - file_rename: { - tooltip: { - image: "Bild umbenennen", - video: "Video umbenennen", - audio: "Audio umbenennen", - file: "Datei umbenennen", - }, - input_placeholder: { - image: "Bild umbenennen", - video: "Video umbenennen", - audio: "Audio umbenennen", - file: "Datei umbenennen", - }, - }, - file_download: { - tooltip: { - image: "Bild herunterladen", - video: "Video herunterladen", - audio: "Audio herunterladen", - file: "Datei herunterladen", - }, - }, - file_delete: { - tooltip: { - image: "Bild löschen", - video: "Video löschen", - audio: "Audio löschen", - file: "Datei löschen", - }, - }, - file_preview_toggle: { - tooltip: "Vorschau umschalten", - }, - nest: { - tooltip: "Block verschachteln", - secondary_tooltip: "Tab", - }, - unnest: { - tooltip: "Block entnesten", - secondary_tooltip: "Shift+Tab", - }, - align_left: { - tooltip: "Text linksbündig", - }, - align_center: { - tooltip: "Text zentrieren", - }, - align_right: { - tooltip: "Text rechtsbündig", - }, - align_justify: { - tooltip: "Text Blocksatz", - }, + bullet_list: { + title: "Aufzählungsliste", + subtext: "Liste mit unnummerierten Elementen", + aliases: ["ul", "li", "liste", "aufzählungsliste", "aufzählung liste"], + group: "Grundlegende Blöcke", }, - file_panel: { - upload: { - title: "Hochladen", - file_placeholder: { - image: "Bild hochladen", - video: "Video hochladen", - audio: "Audio hochladen", - file: "Datei hochladen", - }, - upload_error: "Fehler: Hochladen fehlgeschlagen", - }, - embed: { - title: "Einbetten", - embed_button: { - image: "Bild einbetten", - video: "Video einbetten", - audio: "Audio einbetten", - file: "Datei einbetten", - }, - url_placeholder: "URL eingeben", - }, + check_list: { + title: "Checkliste", + subtext: "Liste mit Kontrollkästchen", + aliases: [ + "ul", + "li", + "liste", + "checkliste", + "check liste", + "geprüfte liste", + "kontrollkästchen", + ], + group: "Grundlegende Blöcke", + }, + paragraph: { + title: "Absatz", + subtext: "Der Hauptteil Ihres Dokuments", + aliases: ["p", "absatz"], + group: "Grundlegende Blöcke", + }, + code_block: { + title: "Codeblock", + subtext: "Codeblock mit Syntaxhervorhebung", + aliases: ["code", "pre"], + group: "Grundlegende Blöcke", + }, + table: { + title: "Tabelle", + subtext: "Tabelle mit editierbaren Zellen", + aliases: ["tabelle"], + group: "Erweitert", + }, + image: { + title: "Bild", + subtext: "Größenveränderbares Bild mit Beschriftung", + aliases: [ + "bild", + "bildhochladen", + "hochladen", + "img", + "bild", + "medien", + "url", + ], + group: "Medien", + }, + video: { + title: "Video", + subtext: "Größenveränderbares Video mit Beschriftung", + aliases: [ + "video", + "videoupload", + "hochladen", + "mp4", + "film", + "medien", + "url", + ], + group: "Medien", + }, + audio: { + title: "Audio", + subtext: "Eingebettetes Audio mit Beschriftung", + aliases: [ + "audio", + "audioupload", + "hochladen", + "mp3", + "ton", + "medien", + "url", + ], + group: "Medien", + }, + file: { + title: "Datei", + subtext: "Eingebettete Datei", + aliases: ["datei", "hochladen", "einbetten", "medien", "url"], + group: "Medien", + }, + emoji: { + title: "Emoji", + subtext: "Nach Emoji suchen und einfügen", + aliases: ["emoji", "emote", "emotion", "gesicht"], + group: "Andere", + }, + }, + placeholders: { + default: "Text eingeben oder '/' für Befehle tippen", + heading: "Überschrift", + bulletListItem: "Liste", + numberedListItem: "Liste", + checkListItem: "Liste", + }, + file_blocks: { + image: { + add_button_text: "Bild hinzufügen", + }, + video: { + add_button_text: "Video hinzufügen", + }, + audio: { + add_button_text: "Audio hinzufügen", + }, + file: { + add_button_text: "Datei hinzufügen", + }, + }, + side_menu: { + add_block_label: "Block hinzufügen", + drag_handle_label: "Blockmenü öffnen", + }, + drag_handle: { + delete_menuitem: "Löschen", + colors_menuitem: "Farben", + }, + table_handle: { + delete_column_menuitem: "Spalte löschen", + delete_row_menuitem: "Zeile löschen", + add_left_menuitem: "Spalte links hinzufügen", + add_right_menuitem: "Spalte rechts hinzufügen", + add_above_menuitem: "Zeile oberhalb hinzufügen", + add_below_menuitem: "Zeile unterhalb hinzufügen", + }, + suggestion_menu: { + no_items_title: "Keine Elemente gefunden", + loading: "Laden…", + }, + color_picker: { + text_title: "Text", + background_title: "Hintergrund", + colors: { + default: "Standard", + gray: "Grau", + brown: "Braun", + red: "Rot", + orange: "Orange", + yellow: "Gelb", + green: "Grün", + blue: "Blau", + purple: "Lila", + pink: "Rosa", + }, + }, + formatting_toolbar: { + bold: { + tooltip: "Fett", + secondary_tooltip: "Mod+B", + }, + italic: { + tooltip: "Kursiv", + secondary_tooltip: "Mod+I", + }, + underline: { + tooltip: "Unterstrichen", + secondary_tooltip: "Mod+U", + }, + strike: { + tooltip: "Durchgestrichen", + secondary_tooltip: "Mod+Shift+S", + }, + code: { + tooltip: "Code", + secondary_tooltip: "", }, - link_toolbar: { - delete: { - tooltip: "Link entfernen", + colors: { + tooltip: "Farben", + }, + link: { + tooltip: "Link erstellen", + secondary_tooltip: "Mod+K", + }, + file_caption: { + tooltip: "Beschriftung bearbeiten", + input_placeholder: "Beschriftung bearbeiten", + }, + file_replace: { + tooltip: { + image: "Bild ersetzen", + video: "Video ersetzen", + audio: "Audio ersetzen", + file: "Datei ersetzen", }, - edit: { - text: "Link bearbeiten", - tooltip: "Bearbeiten", + }, + file_rename: { + tooltip: { + image: "Bild umbenennen", + video: "Video umbenennen", + audio: "Audio umbenennen", + file: "Datei umbenennen", + }, + input_placeholder: { + image: "Bild umbenennen", + video: "Video umbenennen", + audio: "Audio umbenennen", + file: "Datei umbenennen", }, - open: { - tooltip: "In neuem Tab öffnen", + }, + file_download: { + tooltip: { + image: "Bild herunterladen", + video: "Video herunterladen", + audio: "Audio herunterladen", + file: "Datei herunterladen", }, - form: { - title_placeholder: "Titel bearbeiten", - url_placeholder: "URL bearbeiten", + }, + file_delete: { + tooltip: { + image: "Bild löschen", + video: "Video löschen", + audio: "Audio löschen", + file: "Datei löschen", }, }, - generic: { - ctrl_shortcut: "Strg", + file_preview_toggle: { + tooltip: "Vorschau umschalten", + }, + nest: { + tooltip: "Block verschachteln", + secondary_tooltip: "Tab", + }, + unnest: { + tooltip: "Block entnesten", + secondary_tooltip: "Shift+Tab", + }, + align_left: { + tooltip: "Text linksbündig", + }, + align_center: { + tooltip: "Text zentrieren", + }, + align_right: { + tooltip: "Text rechtsbündig", + }, + align_justify: { + tooltip: "Text Blocksatz", + }, + }, + file_panel: { + upload: { + title: "Hochladen", + file_placeholder: { + image: "Bild hochladen", + video: "Video hochladen", + audio: "Audio hochladen", + file: "Datei hochladen", + }, + upload_error: "Fehler: Hochladen fehlgeschlagen", + }, + embed: { + title: "Einbetten", + embed_button: { + image: "Bild einbetten", + video: "Video einbetten", + audio: "Audio einbetten", + file: "Datei einbetten", + }, + url_placeholder: "URL eingeben", + }, + }, + link_toolbar: { + delete: { + tooltip: "Link entfernen", + }, + edit: { + text: "Link bearbeiten", + tooltip: "Bearbeiten", + }, + open: { + tooltip: "In neuem Tab öffnen", + }, + form: { + title_placeholder: "Titel bearbeiten", + url_placeholder: "URL bearbeiten", }, - }; - \ No newline at end of file + }, + generic: { + ctrl_shortcut: "Strg", + }, +}; diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index b7dc7ac432..e23d0e2638 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -50,6 +50,12 @@ export const en = { aliases: ["p", "paragraph"], group: "Basic blocks", }, + code_block: { + title: "Code Block", + subtext: "Code block with syntax highlighting", + aliases: ["code", "pre"], + group: "Basic blocks", + }, table: { title: "Table", subtext: "Table with editable cells", diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts index dca1809859..2ffc5587fc 100644 --- a/packages/core/src/i18n/locales/es.ts +++ b/packages/core/src/i18n/locales/es.ts @@ -1,275 +1,311 @@ export const es = { - slash_menu: { - heading: { - title: 'Encabezado 1', - subtext: 'Encabezado de primer nivel', - aliases: ['h', 'encabezado1', 'h1'], - group: 'Encabezados', - }, - heading_2: { - title: 'Encabezado 2', - subtext: 'Encabezado de sección principal', - aliases: ['h2', 'encabezado2', 'subencabezado'], - group: 'Encabezados', - }, - heading_3: { - title: 'Encabezado 3', - subtext: 'Encabezado de subsección y grupo', - aliases: ['h3', 'encabezado3', 'subencabezado'], - group: 'Encabezados', - }, - numbered_list: { - title: 'Lista Numerada', - subtext: 'Lista con elementos ordenados', - aliases: ['ol', 'li', 'lista', 'lista numerada'], - group: 'Bloques básicos', - }, - bullet_list: { - title: 'Lista con Viñetas', - subtext: 'Lista con elementos no ordenados', - aliases: ['ul', 'li', 'lista', 'lista con viñetas'], - group: 'Bloques básicos', - }, - check_list: { - title: 'Lista de Verificación', - subtext: 'Lista con casillas de verificación', - aliases: ['ul', 'li', 'lista', 'lista de verificación', 'lista de chequeo', 'checkbox'], - group: 'Bloques básicos', - }, - paragraph: { - title: 'Párrafo', - subtext: 'El cuerpo de tu documento', - aliases: ['p', 'párrafo'], - group: 'Bloques básicos', - }, - table: { - title: 'Tabla', - subtext: 'Tabla con celdas editables', - aliases: ['tabla'], - group: 'Avanzado', - }, - image: { - title: 'Imagen', - subtext: 'Imagen redimensionable con leyenda', - aliases: ['imagen', 'subir imagen', 'cargar', 'img', 'foto', 'media', 'url'], - group: 'Medios', - }, - video: { - title: 'Vídeo', - subtext: 'Vídeo redimensionable con leyenda', - aliases: ['video', 'subir vídeo', 'cargar', 'mp4', 'película', 'media', 'url'], - group: 'Medios', - }, - audio: { - title: 'Audio', - subtext: 'Audio incrustado con leyenda', - aliases: ['audio', 'subir audio', 'cargar', 'mp3', 'sonido', 'media', 'url'], - group: 'Medios', - }, - file: { - title: 'Archivo', - subtext: 'Archivo incrustado', - aliases: ['archivo', 'cargar', 'incrustar', 'media', 'url'], - group: 'Medios', - }, - emoji: { - title: 'Emoji', - subtext: 'Busca e inserta un emoji', - aliases: ['emoji', 'emoticono', 'emoción', 'cara'], - group: 'Otros', - }, - }, - placeholders: { - default: "Escribe o teclea '/' para comandos", - heading: 'Encabezado', - bulletListItem: 'Lista', - numberedListItem: 'Lista', - checkListItem: 'Lista', - }, - file_blocks: { - image: { - add_button_text: 'Agregar imagen', - }, - video: { - add_button_text: 'Agregar vídeo', - }, - audio: { - add_button_text: 'Agregar audio', - }, - file: { - add_button_text: 'Agregar archivo', - }, - }, - side_menu: { - add_block_label: 'Agregar bloque', - drag_handle_label: 'Abrir menú de bloque', - }, - drag_handle: { - delete_menuitem: 'Eliminar', - colors_menuitem: 'Colores', - }, - table_handle: { - delete_column_menuitem: 'Eliminar columna', - delete_row_menuitem: 'Eliminar fila', - add_left_menuitem: 'Agregar columna a la izquierda', - add_right_menuitem: 'Agregar columna a la derecha', - add_above_menuitem: 'Agregar fila arriba', - add_below_menuitem: 'Agregar fila abajo', - }, - suggestion_menu: { - no_items_title: 'No se encontraron elementos', - loading: 'Cargando…', - }, - color_picker: { - text_title: 'Texto', - background_title: 'Fondo', - colors: { - default: 'Por defecto', - gray: 'Gris', - brown: 'Marrón', - red: 'Rojo', - orange: 'Naranja', - yellow: 'Amarillo', - green: 'Verde', - blue: 'Azul', - purple: 'Morado', - pink: 'Rosa', - }, - }, - formatting_toolbar: { - bold: { - tooltip: 'Negrita', - secondary_tooltip: 'Mod+B', - }, - italic: { - tooltip: 'Cursiva', - secondary_tooltip: 'Mod+I', - }, - underline: { - tooltip: 'Subrayado', - secondary_tooltip: 'Mod+U', - }, - strike: { - tooltip: 'Tachado', - secondary_tooltip: 'Mod+Shift+S', - }, - code: { - tooltip: 'Código', - secondary_tooltip: '', - }, - colors: { - tooltip: 'Colores', - }, - link: { - tooltip: 'Crear enlace', - secondary_tooltip: 'Mod+K', - }, - file_caption: { - tooltip: 'Editar leyenda', - input_placeholder: 'Editar leyenda', - }, - file_replace: { - tooltip: { - image: 'Reemplazar imagen', - video: 'Reemplazar vídeo', - audio: 'Reemplazar audio', - file: 'Reemplazar archivo', - } as Record, - }, - file_rename: { - tooltip: { - image: 'Renombrar imagen', - video: 'Renombrar vídeo', - audio: 'Renombrar audio', - file: 'Renombrar archivo', - } as Record, - input_placeholder: { - image: 'Renombrar imagen', - video: 'Renombrar vídeo', - audio: 'Renombrar audio', - file: 'Renombrar archivo', - } as Record, - }, - file_download: { - tooltip: { - image: 'Descargar imagen', - video: 'Descargar vídeo', - audio: 'Descargar audio', - file: 'Descargar archivo', - } as Record, - }, - file_delete: { - tooltip: { - image: 'Eliminar imagen', - video: 'Eliminar vídeo', - audio: 'Eliminar audio', - file: 'Eliminar archivo', - } as Record, - }, - file_preview_toggle: { - tooltip: 'Alternar vista previa', - }, - nest: { - tooltip: 'Anidar bloque', - secondary_tooltip: 'Tab', - }, - unnest: { - tooltip: 'Desanidar bloque', - secondary_tooltip: 'Shift+Tab', - }, - align_left: { - tooltip: 'Alinear texto a la izquierda', - }, - align_center: { - tooltip: 'Alinear texto al centro', - }, - align_right: { - tooltip: 'Alinear texto a la derecha', - }, - align_justify: { - tooltip: 'Justificar texto', - }, - }, - file_panel: { - upload: { - title: 'Subir', - file_placeholder: { - image: 'Subir imagen', - video: 'Subir vídeo', - audio: 'Subir audio', - file: 'Subir archivo', - } as Record, - upload_error: 'Error: Fallo en la subida', - }, - embed: { - title: 'Incrustar', - embed_button: { - image: 'Incrustar imagen', - video: 'Incrustar vídeo', - audio: 'Incrustar audio', - file: 'Incrustar archivo', - } as Record, - url_placeholder: 'Introduce la URL', - }, - }, - link_toolbar: { - delete: { - tooltip: 'Eliminar enlace', - }, - edit: { - text: 'Editar enlace', - tooltip: 'Editar', - }, - open: { - tooltip: 'Abrir en nueva pestaña', - }, - form: { - title_placeholder: 'Editar título', - url_placeholder: 'Editar URL', - }, - }, - generic: { - ctrl_shortcut: 'Ctrl', - }, - }; - \ No newline at end of file + slash_menu: { + heading: { + title: "Encabezado 1", + subtext: "Encabezado de primer nivel", + aliases: ["h", "encabezado1", "h1"], + group: "Encabezados", + }, + heading_2: { + title: "Encabezado 2", + subtext: "Encabezado de sección principal", + aliases: ["h2", "encabezado2", "subencabezado"], + group: "Encabezados", + }, + heading_3: { + title: "Encabezado 3", + subtext: "Encabezado de subsección y grupo", + aliases: ["h3", "encabezado3", "subencabezado"], + group: "Encabezados", + }, + numbered_list: { + title: "Lista Numerada", + subtext: "Lista con elementos ordenados", + aliases: ["ol", "li", "lista", "lista numerada"], + group: "Bloques básicos", + }, + bullet_list: { + title: "Lista con Viñetas", + subtext: "Lista con elementos no ordenados", + aliases: ["ul", "li", "lista", "lista con viñetas"], + group: "Bloques básicos", + }, + check_list: { + title: "Lista de Verificación", + subtext: "Lista con casillas de verificación", + aliases: [ + "ul", + "li", + "lista", + "lista de verificación", + "lista de chequeo", + "checkbox", + ], + group: "Bloques básicos", + }, + paragraph: { + title: "Párrafo", + subtext: "El cuerpo de tu documento", + aliases: ["p", "párrafo"], + group: "Bloques básicos", + }, + code_block: { + title: "Bloque de Código", + subtext: "Bloque de código con resaltado de sintaxis", + aliases: ["code", "pre"], + group: "Bloques básicos", + }, + table: { + title: "Tabla", + subtext: "Tabla con celdas editables", + aliases: ["tabla"], + group: "Avanzado", + }, + image: { + title: "Imagen", + subtext: "Imagen redimensionable con leyenda", + aliases: [ + "imagen", + "subir imagen", + "cargar", + "img", + "foto", + "media", + "url", + ], + group: "Medios", + }, + video: { + title: "Vídeo", + subtext: "Vídeo redimensionable con leyenda", + aliases: [ + "video", + "subir vídeo", + "cargar", + "mp4", + "película", + "media", + "url", + ], + group: "Medios", + }, + audio: { + title: "Audio", + subtext: "Audio incrustado con leyenda", + aliases: [ + "audio", + "subir audio", + "cargar", + "mp3", + "sonido", + "media", + "url", + ], + group: "Medios", + }, + file: { + title: "Archivo", + subtext: "Archivo incrustado", + aliases: ["archivo", "cargar", "incrustar", "media", "url"], + group: "Medios", + }, + emoji: { + title: "Emoji", + subtext: "Busca e inserta un emoji", + aliases: ["emoji", "emoticono", "emoción", "cara"], + group: "Otros", + }, + }, + placeholders: { + default: "Escribe o teclea '/' para comandos", + heading: "Encabezado", + bulletListItem: "Lista", + numberedListItem: "Lista", + checkListItem: "Lista", + }, + file_blocks: { + image: { + add_button_text: "Agregar imagen", + }, + video: { + add_button_text: "Agregar vídeo", + }, + audio: { + add_button_text: "Agregar audio", + }, + file: { + add_button_text: "Agregar archivo", + }, + }, + side_menu: { + add_block_label: "Agregar bloque", + drag_handle_label: "Abrir menú de bloque", + }, + drag_handle: { + delete_menuitem: "Eliminar", + colors_menuitem: "Colores", + }, + table_handle: { + delete_column_menuitem: "Eliminar columna", + delete_row_menuitem: "Eliminar fila", + add_left_menuitem: "Agregar columna a la izquierda", + add_right_menuitem: "Agregar columna a la derecha", + add_above_menuitem: "Agregar fila arriba", + add_below_menuitem: "Agregar fila abajo", + }, + suggestion_menu: { + no_items_title: "No se encontraron elementos", + loading: "Cargando…", + }, + color_picker: { + text_title: "Texto", + background_title: "Fondo", + colors: { + default: "Por defecto", + gray: "Gris", + brown: "Marrón", + red: "Rojo", + orange: "Naranja", + yellow: "Amarillo", + green: "Verde", + blue: "Azul", + purple: "Morado", + pink: "Rosa", + }, + }, + formatting_toolbar: { + bold: { + tooltip: "Negrita", + secondary_tooltip: "Mod+B", + }, + italic: { + tooltip: "Cursiva", + secondary_tooltip: "Mod+I", + }, + underline: { + tooltip: "Subrayado", + secondary_tooltip: "Mod+U", + }, + strike: { + tooltip: "Tachado", + secondary_tooltip: "Mod+Shift+S", + }, + code: { + tooltip: "Código", + secondary_tooltip: "", + }, + colors: { + tooltip: "Colores", + }, + link: { + tooltip: "Crear enlace", + secondary_tooltip: "Mod+K", + }, + file_caption: { + tooltip: "Editar leyenda", + input_placeholder: "Editar leyenda", + }, + file_replace: { + tooltip: { + image: "Reemplazar imagen", + video: "Reemplazar vídeo", + audio: "Reemplazar audio", + file: "Reemplazar archivo", + } as Record, + }, + file_rename: { + tooltip: { + image: "Renombrar imagen", + video: "Renombrar vídeo", + audio: "Renombrar audio", + file: "Renombrar archivo", + } as Record, + input_placeholder: { + image: "Renombrar imagen", + video: "Renombrar vídeo", + audio: "Renombrar audio", + file: "Renombrar archivo", + } as Record, + }, + file_download: { + tooltip: { + image: "Descargar imagen", + video: "Descargar vídeo", + audio: "Descargar audio", + file: "Descargar archivo", + } as Record, + }, + file_delete: { + tooltip: { + image: "Eliminar imagen", + video: "Eliminar vídeo", + audio: "Eliminar audio", + file: "Eliminar archivo", + } as Record, + }, + file_preview_toggle: { + tooltip: "Alternar vista previa", + }, + nest: { + tooltip: "Anidar bloque", + secondary_tooltip: "Tab", + }, + unnest: { + tooltip: "Desanidar bloque", + secondary_tooltip: "Shift+Tab", + }, + align_left: { + tooltip: "Alinear texto a la izquierda", + }, + align_center: { + tooltip: "Alinear texto al centro", + }, + align_right: { + tooltip: "Alinear texto a la derecha", + }, + align_justify: { + tooltip: "Justificar texto", + }, + }, + file_panel: { + upload: { + title: "Subir", + file_placeholder: { + image: "Subir imagen", + video: "Subir vídeo", + audio: "Subir audio", + file: "Subir archivo", + } as Record, + upload_error: "Error: Fallo en la subida", + }, + embed: { + title: "Incrustar", + embed_button: { + image: "Incrustar imagen", + video: "Incrustar vídeo", + audio: "Incrustar audio", + file: "Incrustar archivo", + } as Record, + url_placeholder: "Introduce la URL", + }, + }, + link_toolbar: { + delete: { + tooltip: "Eliminar enlace", + }, + edit: { + text: "Editar enlace", + tooltip: "Editar", + }, + open: { + tooltip: "Abrir en nueva pestaña", + }, + form: { + title_placeholder: "Editar título", + url_placeholder: "Editar URL", + }, + }, + generic: { + ctrl_shortcut: "Ctrl", + }, +}; diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index fe227e0638..152d2805dd 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -51,6 +51,12 @@ export const fr: Dictionary = { aliases: ["p", "paragraphe"], group: "Blocs de base", }, + code_block: { + title: "Bloc de code", + subtext: "Bloc de code avec coloration syntaxique", + aliases: ["code", "pre"], + group: "Blocs de base", + }, table: { title: "Tableau", subtext: "Utilisé pour les tableaux", diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index 2423de303d..07fcb3bfb7 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -44,6 +44,12 @@ export const is: Dictionary = { aliases: ["p", "malsgrein"], group: "Grunnblokkar", }, + code_block: { + title: "Kóðablokk", + subtext: "Kóðablokkur með litskiptingu", + aliases: ["kóði", "pre"], + group: "Grunnblokkar", + }, table: { title: "Tafla", subtext: "Notað fyrir töflur", diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index 11453b6eb0..2172289b0b 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -68,6 +68,12 @@ export const ja: Dictionary = { aliases: ["p", "paragraph", "標準テキスト"], group: "基本ブロック", }, + code_block: { + title: "コードブロック", + subtext: "シンタックスハイライト付きのコードブロック", + aliases: ["code", "pre", "コード", "コードブロック"], + group: "基本ブロック", + }, table: { title: "表", subtext: "表に使用", diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index d7cb7df1a7..cc0144481d 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -52,6 +52,12 @@ export const ko: Dictionary = { aliases: ["p", "paragraph", "본문"], group: "기본 블록", }, + code_block: { + title: "코드 블록", + subtext: "구문 강조가 있는 코드 블록", + aliases: ["code", "pre"], + group: "기본 블록", + }, table: { title: "표", subtext: "간단한 표를 추가합니다.", diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index a53976ee77..152e640001 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -44,6 +44,12 @@ export const nl: Dictionary = { aliases: ["p", "paragraaf"], group: "Basisblokken", }, + code_block: { + title: "Codeblok", + subtext: "Codeblok met syntax highlighting", + aliases: ["code", "pre"], + group: "Basisblokken", + }, table: { title: "Tabel", subtext: "Gebruikt voor tabellen", diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index fb67274bb0..fdf53fe591 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -44,6 +44,12 @@ export const pl: Dictionary = { aliases: ["p", "akapit"], group: "Podstawowe bloki", }, + code_block: { + title: "Blok kodu", + subtext: "Blok kodu z podświetleniem składni", + aliases: ["kod", "pre"], + group: "Podstawowe bloki", + }, table: { title: "Tabela", subtext: "Używana do tworzenia tabel", diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index a2e0dfa224..018d11e174 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -51,6 +51,12 @@ export const pt: Dictionary = { aliases: ["p", "paragrafo"], group: "Blocos Básicos", }, + code_block: { + title: "Bloco de Código", + subtext: "Usado para exibir código com destaque de sintaxe", + aliases: ["codigo", "pre"], + group: "Blocos Básicos", + }, table: { title: "Tabela", subtext: "Usado para tabelas", diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts index b5d7269220..b1e26f1247 100644 --- a/packages/core/src/i18n/locales/ru.ts +++ b/packages/core/src/i18n/locales/ru.ts @@ -69,6 +69,12 @@ export const ru: Dictionary = { aliases: ["p", "paragraph", "параграф"], group: "Базовые блоки", }, + code_block: { + title: "Блок кода", + subtext: "Блок кода с подсветкой синтаксиса", + aliases: ["code", "pre", "блок кода"], + group: "Базовые блоки", + }, table: { title: "Таблица", subtext: "Используется для таблиц", diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index 72ee9c85ef..507146013f 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -51,6 +51,12 @@ export const vi: Dictionary = { aliases: ["p", "doanvan"], group: "Khối cơ bản", }, + code_block: { + title: "Mã", + subtext: "Sử dụng để hiển thị mã với cú pháp", + aliases: ["code", "pre"], + group: "Khối cơ bản", + }, table: { title: "Bảng", subtext: "Sử dụng để tạo bảng", diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index 9efeb38955..955b9ca098 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -69,6 +69,12 @@ export const zh: Dictionary = { aliases: ["p", "paragraph", "text", "正文"], group: "基础", }, + code_block: { + title: "代码块", + subtext: "用于显示带有语法高亮的代码块", + aliases: ["code", "pre", "代码", "预格式"], + group: "基础", + }, table: { title: "表格", subtext: "使用表格", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cae4aefe2a..7a45f99e66 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,20 +4,21 @@ export * from "./api/exporters/html/internalHTMLSerializer.js"; export * from "./api/getBlockInfoFromPos.js"; export * from "./api/testUtil/index.js"; export * from "./blocks/AudioBlockContent/AudioBlockContent.js"; +export * from "./blocks/CodeBlockContent/CodeBlockContent.js"; +export * from "./blocks/defaultBlockHelpers.js"; +export * from "./blocks/defaultBlocks.js"; +export * from "./blocks/defaultBlockTypeGuards.js"; +export * from "./blocks/defaultProps.js"; export * from "./blocks/FileBlockContent/FileBlockContent.js"; export * from "./blocks/FileBlockContent/fileBlockHelpers.js"; export * from "./blocks/FileBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.js"; export * from "./blocks/ImageBlockContent/ImageBlockContent.js"; +export { parseImageElement } from "./blocks/ImageBlockContent/imageBlockHelpers.js"; export { - EMPTY_CELL_WIDTH, EMPTY_CELL_HEIGHT, + EMPTY_CELL_WIDTH, } from "./blocks/TableBlockContent/TableExtension.js"; -export { parseImageElement } from "./blocks/ImageBlockContent/imageBlockHelpers.js"; export * from "./blocks/VideoBlockContent/VideoBlockContent.js"; -export * from "./blocks/defaultBlockHelpers.js"; -export * from "./blocks/defaultBlockTypeGuards.js"; -export * from "./blocks/defaultBlocks.js"; -export * from "./blocks/defaultProps.js"; export * from "./editor/BlockNoteEditor.js"; export * from "./editor/BlockNoteExtensions.js"; export * from "./editor/BlockNoteSchema.js"; @@ -29,9 +30,9 @@ export * from "./extensions/LinkToolbar/LinkToolbarPlugin.js"; export * from "./extensions/SideMenu/SideMenuPlugin.js"; export * from "./extensions/SuggestionMenu/DefaultGridSuggestionItem.js"; export * from "./extensions/SuggestionMenu/DefaultSuggestionItem.js"; -export * from "./extensions/SuggestionMenu/SuggestionPlugin.js"; export * from "./extensions/SuggestionMenu/getDefaultEmojiPickerItems.js"; export * from "./extensions/SuggestionMenu/getDefaultSlashMenuItems.js"; +export * from "./extensions/SuggestionMenu/SuggestionPlugin.js"; export * from "./extensions/TableHandles/TableHandlesPlugin.js"; export * from "./i18n/dictionary.js"; export * from "./schema/index.js"; diff --git a/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx b/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx index 0b8b0502c7..6e7bc96beb 100644 --- a/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx +++ b/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx @@ -19,6 +19,7 @@ import { RiTable2, RiText, RiVolumeUpFill, + RiCodeBlock, } from "react-icons/ri"; import { DefaultReactSuggestionItem } from "./types.js"; @@ -36,6 +37,7 @@ const icons = { audio: RiVolumeUpFill, file: RiFile2Line, emoji: RiEmotionFill, + code_block: RiCodeBlock, }; export function getDefaultReactSlashMenuItems< diff --git a/playground/package.json b/playground/package.json index d0e70d767c..0635f5e5c9 100644 --- a/playground/package.json +++ b/playground/package.json @@ -3,7 +3,7 @@ "private": true, "version": "0.17.1", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "tsc && vite build", "preview": "vite preview", "lint": "eslint src --max-warnings 0", diff --git a/tests/package.json b/tests/package.json index 1ed54a5184..759a072c23 100644 --- a/tests/package.json +++ b/tests/package.json @@ -7,9 +7,9 @@ "lint": "eslint src --max-warnings 0", "playwright": "npx playwright test", "playwright:ui": "npx playwright test --ui", - "test:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.44.1-focal npx playwright test", + "test:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.44.1-focal npx playwright test -u", "test-ct": "playwright test -c playwright-ct.config.ts --headed", - "test-ct:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.35.1-focal npm install && playwright test -c playwright-ct.config.ts -u", + "test-ct:updateSnaps": "docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.35.1-focal npx playwright test -c playwright-ct.config.ts -u", "clean": "rimraf dist" }, "dependencies": { diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png index 5e2715b039..7f5d6c4c9b 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-chromium-linux.png differ diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png index f11b18f818..2a31a61200 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-emoji-picker-webkit-linux.png differ diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png index 1742d9b9db..d93fb7180a 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-chromium-linux.png differ diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png index 1b6d9b61c2..15672652c3 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-firefox-linux.png differ diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png index d000df6e52..33e062f08b 100644 Binary files a/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png and b/tests/src/end-to-end/ariakit/ariakit.test.ts-snapshots/ariakit-slash-menu-webkit-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-chromium-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-chromium-linux.png index c959fd661d..a891246a12 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-chromium-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-chromium-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-webkit-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-webkit-linux.png index faf8eb3ca3..b112870f11 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-webkit-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-emoji-picker-webkit-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png index c1f9d6f64b..aa0e2865d1 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-chromium-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png index aeec08eb5f..915e24a32e 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-firefox-linux.png differ diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png index 2e2fb007c7..19560ed150 100644 Binary files a/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png and b/tests/src/end-to-end/shadcn/shadcn.test.ts-snapshots/shadcn-slash-menu-webkit-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png index 6aebeb8009..50b29b5367 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-chromium-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png index 57fab97138..56531fe120 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-emoji-picker-webkit-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png index b60320e829..7ec6b5d603 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-link-toolbar-chromium-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png index c08950e9d8..47f94fe21f 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-chromium-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png index 624d5c5ba1..de6f49b8cf 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-firefox-linux.png differ diff --git a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png index 0a0350cc0c..90958fa776 100644 Binary files a/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png and b/tests/src/end-to-end/theming/theming.test.ts-snapshots/dark-slash-menu-webkit-linux.png differ