chore: change

This commit is contained in:
zhuzhengjian
2025-11-21 10:12:04 +08:00
parent a46a07cb54
commit e5661226e5

View File

@@ -1,7 +1,8 @@
```
<template>
<node-view-wrapper class="code-block-wrapper" @dblclick="onContainerClick">
<node-view-wrapper class="code-block-wrapper">
<div class="code-block-header">
<select v-model="selectedLanguage" class="language-select" contenteditable="false">
<select v-model="selectedLanguage" class="language-select" contenteditable="false" :disabled="!isEditorEditable">
<option value="null">auto</option>
<option disabled>—</option>
<option v-for="(language, index) in languages" :key="index" :value="language">
@@ -15,10 +16,10 @@
</div>
</div>
<div class="code-block-content" v-if="!isEditable">
<div class="code-block-content" v-if="!isEditorEditable">
<div v-html="highlightedCode" class="shiki-container"></div>
</div>
<div class="monaco-container" ref="monacoContainer" v-show="isEditable"></div>
<div class="monaco-container" ref="monacoContainer" v-show="isEditorEditable"></div>
</node-view-wrapper>
</template>
@@ -58,7 +59,7 @@ const selectedLanguage = computed({
const monacoContainer = ref<HTMLElement | null>(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.
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.
// 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.
// 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 try to just update the node content when we exit edit mode.
// 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
})
</script>
<style scoped>
@@ -253,6 +227,11 @@ defineExpose({
outline: none;
}
.language-select:disabled {
opacity: 0.7;
cursor: default;
}
.actions {
display: flex;
gap: 0.5rem;