From e5661226e5cff58d01c34584ed6e57ee3ec5ec04 Mon Sep 17 00:00:00 2001 From: zhuzhengjian Date: Fri, 21 Nov 2025 10:12:04 +0800 Subject: [PATCH] chore: change --- src/components/CodeBlockComponent.vue | 163 +++++++++++--------------- 1 file changed, 71 insertions(+), 92 deletions(-) diff --git a/src/components/CodeBlockComponent.vue b/src/components/CodeBlockComponent.vue index b299b5a..87c3702 100644 --- a/src/components/CodeBlockComponent.vue +++ b/src/components/CodeBlockComponent.vue @@ -1,7 +1,8 @@ +``` @@ -58,7 +59,7 @@ const selectedLanguage = computed({ const monacoContainer = ref(null) let editor: monaco.editor.IStandaloneCodeEditor | null = null -const isEditable = ref(false) +const isEditorEditable = ref(props.editor.isEditable) const highlightedCode = ref('') // Shiki highlighter instance @@ -93,26 +94,20 @@ const updateHighlightedCode = async () => { } const initMonaco = async () => { - if (!monacoContainer.value) return + if (!monacoContainer.value || editor) return - // Create the highlighter, it can be reused - const highlighter = await createHighlighter({ - themes: [ - 'vitesse-dark', - 'vitesse-light', - ], - langs: [ - 'javascript', - 'typescript', - 'vue' - ], - }) - // Register the languageIds first. Only registered languages will be highlighted. - monaco.languages.register({ id: 'vue' }) - monaco.languages.register({ id: 'typescript' }) - monaco.languages.register({ id: 'javascript' }) - // Register the themes from Shiki, and provide syntax highlighting for Monaco. - shikiToMonaco(highlighter, monaco) + // Register languages and theme only once + if (!monaco.languages.getLanguages().some(l => l.id === 'vue')) { + const highlighter = await createHighlighter({ + themes: ['vitesse-dark', 'vitesse-light'], + langs: ['javascript', 'typescript', 'vue'], + }) + + monaco.languages.register({ id: 'vue' }) + monaco.languages.register({ id: 'typescript' }) + monaco.languages.register({ id: 'javascript' }) + shikiToMonaco(highlighter, monaco) + } editor = monaco.editor.create(monacoContainer.value, { value: props.node.content.firstChild?.text || '', @@ -125,81 +120,63 @@ const initMonaco = async () => { fontFamily: 'JetBrains Mono, monospace', lineNumbers: 'off', padding: { top: 16, bottom: 16 }, + readOnly: false }) editor.onDidChangeModelContent(() => { + if (!isEditorEditable.value) return + const value = editor?.getValue() if (value !== undefined) { - // Update Tiptap node content - // We need to be careful not to trigger a re-render loop - // This is a simplified approach; for robust sync, might need more logic - // But since we switch modes, it might be okay. - // Actually, Tiptap expects us to update the node. - // But since we are in a NodeView, we can't easily update the content directly without replacing the node? - // Wait, for code blocks, usually we update the text content. - // Let's try to update the node content using a transaction if possible, or just emit an update. - // For now, let's just keep local state and update on blur or mode switch? - // Better: Update on change. - - // NOTE: Updating the node content from within the node view can be tricky. - // A common pattern is to use `props.updateAttributes` for attributes, - // but for content, we might need to use `props.editor.commands.command(...)`. - // However, since we are "taking over" the rendering, we can just update the node when we are done? - // Or we can try to keep them in sync. - - // Let's try to just update the node content when we exit edit mode. + const pos = props.getPos() + if (typeof pos === 'number') { + // We use a transaction to update the content without triggering a re-render of the node view if possible + // But here we are just updating the text. + // We need to be careful about infinite loops if we update the node and it updates us. + // But since we are the source of truth in edit mode, it should be fine. + + // However, to avoid cursor jumping or re-rendering issues, we might want to debounce or check for changes. + // Actually, Tiptap's `insertText` might replace the node? No, it updates the text node inside. + + // Let's check if content is different + const currentContent = props.node.content.firstChild?.text || '' + if (value !== currentContent) { + props.editor.view.dispatch( + props.editor.view.state.tr.insertText(value, pos + 1, pos + 1 + props.node.content.size) + ) + } + } } }) - - // Sync content changes from Tiptap to Monaco (if changed externally) - // watch(() => props.node.content, ...) } -// Toggle edit mode -// We can toggle edit mode on click -const toggleEditMode = () => { - isEditable.value = !isEditable.value - if (isEditable.value) { - setTimeout(() => { - if (!editor) { - initMonaco() - } else { - editor.setValue(props.node.content.firstChild?.text || '') - editor.layout() - } - editor?.focus() - }, 0) - } else { - // Save content back to Tiptap - if (editor) { - const content = editor.getValue() - const pos = props.getPos() - - if (typeof pos === 'number') { - props.editor.view.dispatch( - props.editor.view.state.tr.insertText(content, pos + 1, pos + 1 + props.node.content.size) - ) - } - } - updateHighlightedCode() - } -} - -const onContainerClick = () => { - if (!isEditable.value) { - toggleEditMode() - } -} - -// Handle click outside to exit edit mode -// This is a bit complex inside a NodeView. -// A simpler way is to use the `blur` event of Monaco editor. -// But Monaco's blur might trigger when clicking the language select. -// Let's try to use a "Done" button or just blur. - onMounted(() => { initShiki() + // Watch for editor editable state changes + // We can listen to the transaction event + props.editor.on('transaction', () => { + isEditorEditable.value = props.editor.isEditable + }) + + watch(isEditorEditable, (editable) => { + if (editable) { + // Initialize Monaco if not already + if (!editor) { + initMonaco() + } else { + // Update content just in case it changed while in read-only + const currentContent = props.node.content.firstChild?.text || '' + if (editor.getValue() !== currentContent) { + editor.setValue(currentContent) + } + } + } else { + // Update Shiki + updateHighlightedCode() + } + }, { immediate: true }) + // Watch for language changes to update syntax highlighting and Monaco model language watch(selectedLanguage, (newLang) => { if (editor) { @@ -213,17 +190,14 @@ onBeforeUnmount(() => { if (editor) { editor.dispose() } + // Clean up event listener? Tiptap handles it usually, but good practice to remove if we could. + // props.editor.off('transaction', ...) }) const copyCode = () => { const code = props.node.content.firstChild?.text || '' navigator.clipboard.writeText(code) } - -// Expose methods if needed -defineExpose({ - toggleEditMode -})