diff --git a/ui/packages/editor/src/extensions/code-block/code-block.ts b/ui/packages/editor/src/extensions/code-block/code-block.ts index 400fb1d4ea..03af3cc72c 100644 --- a/ui/packages/editor/src/extensions/code-block/code-block.ts +++ b/ui/packages/editor/src/extensions/code-block/code-block.ts @@ -6,7 +6,13 @@ import { findParentNode, VueNodeViewRenderer, } from "@/tiptap/vue-3"; -import { EditorState, TextSelection, type Transaction } from "@/tiptap/pm"; +import { + EditorState, + Plugin, + PluginKey, + TextSelection, + type Transaction, +} from "@/tiptap/pm"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import type { CodeBlockLowlightOptions } from "@tiptap/extension-code-block-lowlight"; import CodeBlockViewRenderer from "./CodeBlockViewRenderer.vue"; @@ -237,4 +243,70 @@ export default CodeBlockLowlight.extend< }, }; }, + + addProseMirrorPlugins() { + return [ + // Solve the paste problem. Because the upstream has not been + // able to deal with this problem for a long time, it is + // handled manually locally. + // see: https://github.com/ueberdosis/tiptap/pull/3606 + new Plugin({ + key: new PluginKey("codeBlockVSCodeHandlerFixPaste"), + props: { + handlePaste: (view, event) => { + if (!event.clipboardData) { + return false; + } + // don’t create a new code block within code blocks + if (this.editor.isActive(this.type.name)) { + 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 || !language) { + return false; + } + + const { tr, schema } = view.state; + + // add text to code block + // strip carriage return chars from text pasted as code + // see: https://github.com/ProseMirror/prosemirror-view/commit/a50a6bcceb4ce52ac8fcc6162488d8875613aacd + const contentTextNode = schema.text(text.replace(/\r\n?/g, "\n")); + + // create an empty code block + tr.replaceSelectionWith( + this.type.create({ language }, contentTextNode) + ); + + const { selection } = tr; + // Whether the current position is code block, if not, move forward to code block. + let codeBlockPos = Math.max(0, selection.from - 1); + while ( + codeBlockPos > 0 && + tr.doc.resolve(codeBlockPos).parent.type.name !== this.type.name + ) { + codeBlockPos--; + } + // put cursor inside the newly created code block + tr.setSelection(TextSelection.near(tr.doc.resolve(codeBlockPos))); + + // store meta information + // this is useful for other plugins that depends on the paste event + // like the paste rule plugin + tr.setMeta("paste", true); + + view.dispatch(tr); + + return true; + }, + }, + }), + ...(this.parent?.() || []), + ]; + }, });