Files
llm-in-text/src/components/MarkdownEditor.vue
ydy0615 49f264b53b feat(project): scaffold Vue 3 + Vite markdown editor
Add initial project structure including:
- .gitignore and VSCode settings
- Vite configuration and package.json with Vue 3 dependencies
- Basic HTML entry point and README update
- Core source files: App.vue, main.js, style.css
- Markdown editor component with plugin system and related types
- Sample HelloWorld component, router, and Pinia store
- Assets and SVG icons

This commit establishes the foundation for the Vue 3 application.
2026-01-12 12:23:44 +08:00

121 lines
2.9 KiB
Vue

<template>
<!-- Textarea for markdown input -->
<textarea
v-model="markdown"
@keydown="handleKeydown"
placeholder="Enter markdown..."
rows="10"
style="width: 100%;"
></textarea>
<!-- Plugin host (no UI) -->
<PluginHost />
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { plugins } from '../plugins/index'
import PluginHost from './PluginHost.vue'
import markdownIt from 'markdown-it'
import Prism from 'prismjs'
// 按需加载常用语言
import 'prismjs/components/prism-javascript'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-css'
/* ---------- 插件挂载点 ---------- */
const pluginContext = {}
onMounted(() => {
plugins.forEach(p => {
if (p.onSetup) p.onSetup(pluginContext)
})
})
/* ---------- markdown 解析 ---------- */
const md = markdownIt({
highlight: (code, lang) => {
if (lang && Prism.languages[lang]) {
return `<pre class="language-${lang}"><code>${Prism.highlight(
code,
Prism.languages[lang],
lang
)}</code></pre>`
}
return `<pre class="language-${lang}"><code>${md.utils.escapeHtml(
code
)}</code></pre>`
}
})
const markdown = ref('')
/* ---------- 解析后钩子 & emit ---------- */
watch(
markdown,
(newVal) => {
let html = md.render(newVal)
// onAfterParse 插件
const afterPayload = { markdown: newVal, html }
plugins.forEach(p => {
if (p.onAfterParse) {
const res = p.onAfterParse(afterPayload)
if (res && res.html) afterPayload.html = res.html
}
})
// onBeforeRender 插件
const beforePayload = { html: afterPayload.html }
plugins.forEach(p => {
if (p.onBeforeRender) {
const res = p.onBeforeRender(beforePayload)
if (res && res.html) beforePayload.html = res.html
}
})
emit('update:html', beforePayload.html)
},
{ immediate: true }
)
/* ---------- 键盘快捷键 ---------- */
function insertAtCursor(text) {
const el = document.activeElement
if (el && el.selectionStart !== undefined) {
const start = el.selectionStart
const end = el.selectionEnd
const before = markdown.value.slice(0, start)
const after = markdown.value.slice(end)
markdown.value = `${before}${text}${after}`
// 将光标放在插入文本后
const pos = start + text.length
nextTick(() => {
el.setSelectionRange(pos, pos)
})
}
}
function handleKeydown(e) {
if (e.ctrlKey && !e.shiftKey) {
if (e.key === 'b' || e.key === 'B') {
e.preventDefault()
insertAtCursor('**粗体**')
} else if (e.key === 'i' || e.key === 'I') {
e.preventDefault()
insertAtCursor('_斜体_')
} else if (e.key === '`') {
e.preventDefault()
insertAtCursor('```\n代码块\n```')
}
}
}
</script>
<style scoped>
textarea {
font-family: inherit;
font-size: 1rem;
padding: 0.5rem;
box-sizing: border-box;
}
</style>