feat(desktop): 完成编辑快捷键与警告框菜单

This commit is contained in:
2026-09-07 22:33:10 +08:00
parent c3061fdf12
commit 51105f6e53
8 changed files with 151 additions and 17 deletions
@@ -196,6 +196,16 @@ describe('VisualMarkdownEditor formatting toolbars', () => {
useEditorStore().saveStatus = 'conflict'
expect(await executeEditorCommand('editor.bold')).toEqual({ ok: false, reason: 'unavailable' })
})
it('creates editable front matter when the metadata shortcut has no existing block', async () => {
const store = useEditorStore(); store.currentFilePath = '/notes/示例.md'
const wrapper = mount(VisualMarkdownEditor, { props: { initialContent: '正文' }, attachTo: document.body })
mounted.push(wrapper)
await waitForEditor(wrapper)
expect(await executeEditorCommand('editor.metadata.edit')).toEqual({ ok: true })
expect(wrapper.find('.note-metadata').exists()).toBe(true)
expect(store.content).toContain('title: "示例"')
expect(store.content).toContain('tags: []')
})
it('keeps code examples as ordinary quotes and renders newly typed markers', async () => {
const wrapper = mount(VisualMarkdownEditor, { props: { initialContent: '> `[!NOTE]`\n\n> text' }, attachTo: document.body })
mounted.push(wrapper)
@@ -3,7 +3,7 @@ import ActionDialog from '@/components/common/ActionDialog.vue'
import { useActionDialog } from '@/composables/useActionDialog'
const { actionDialog, resolveAction, askPrompt } = useActionDialog()
import DiagramInteractions from '@/components/common/DiagramInteractions.vue'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { Link, Fold, Expand } from '@element-plus/icons-vue'
import { Crepe } from '@milkdown/crepe'
import { codeBlockConfig } from '@milkdown/kit/component/code-block'
@@ -28,9 +28,11 @@ import {
toggleStrongCommand,
turnIntoTextCommand,
wrapInBulletListCommand,
wrapInBlockquoteCommand,
wrapInHeadingCommand,
wrapInOrderedListCommand,
} from '@milkdown/kit/preset/commonmark'
import { toggleStrikethroughCommand } from '@milkdown/kit/preset/gfm'
import { commandsCtx, editorViewCtx, parserCtx, remarkStringifyOptionsCtx } from '@milkdown/kit/core'
import { Slice } from '@milkdown/kit/prose/model'
import { registerEditorCommands, type CommandHandler, type EditorCommandId } from '@/services/editorCommandService'
@@ -113,6 +115,27 @@ function installCommands() {
handlers[`editor.${command}`] = () => { runCommand(command); return { ok: true } }
}
handlers['editor.paragraph'] = () => { crepe!.editor.action(callCommand(turnIntoTextCommand.key)); return { ok: true } }
handlers['editor.strikethrough'] = () => { crepe!.editor.action(callCommand(toggleStrikethroughCommand.key)); return { ok: true } }
handlers['editor.blockquote'] = () => { crepe!.editor.action(callCommand(wrapInBlockquoteCommand.key)); return { ok: true } }
handlers['editor.task-list'] = () => { insertMarkdown('- [ ] '); return { ok: true } }
handlers['editor.horizontal-rule'] = () => { insertMarkdown('\n---\n'); return { ok: true } }
handlers['editor.hard-break'] = () => { insertMarkdown(' \n'); return { ok: true } }
handlers['editor.table'] = () => { insertMarkdown('| 列 1 | 列 2 |\n| --- | --- |\n| | |'); return { ok: true } }
handlers['editor.link'] = async () => { await applyLink(); return { ok: true } }
handlers['editor.metadata.edit'] = async () => {
if (!metadata.value) {
const body = crepe!.editor.action(getMarkdown())
const filename = (editorStore.currentFilePath?.split('/').at(-1) ?? '未命名').replace(/\.md$/i, '')
const source = `---\ntitle: ${JSON.stringify(filename)}\ntags: []\n---\n${body}`
metadata.value = splitNoteMetadata(source)
editorStore.updateContent(source)
editorStore.scheduleAutoSave(settingsStore.autoSaveInterval)
await nextTick()
}
const input = editorRoot.value?.closest<HTMLElement>('.milkdown-host')?.querySelector<HTMLElement>('.note-metadata input')
if (!input) return { ok: false, reason: 'unavailable' }
input.scrollIntoView({ block: 'center' }); input.focus(); return { ok: true }
}
handlers['editor.heading'] = params => {
if (!Number.isInteger(params) || Number(params) < 1 || Number(params) > 6) return { ok: false, reason: 'invalid-params' }
crepe!.editor.action(callCommand(wrapInHeadingCommand.key, Number(params)))