新增:文件系统导航面包屑
功能: - 新增 PathBreadcrumb 组件,支持路径快速跳转 - 新增 DropdownItem 通用下拉菜单组件 优化: - 版本升级流程优化(Pinia 状态管理、进度节流、完整下载验证) - 模块延迟初始化(数据库、文件系统按需启动) - API 数据格式统一(蛇形转驼峰) - CodeMirror 语言包按需动态加载 - Markdown 渲染增强(支持锚点跳转) 重构: - 迁移到 Pinia 状态管理(stores/config.ts、stores/theme.ts、stores/update.ts) - 简化 UpdatePanel、UpdateNotification、ThemeToggle 逻辑 - 优化表结构加载逻辑 清理: - 删除测试组件 index-simple.vue - 删除旧的 useTheme.ts
This commit is contained in:
@@ -3,121 +3,32 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, onBeforeUnmount, computed } from 'vue'
|
||||
import { ref, onMounted, watch, onBeforeUnmount, computed, nextTick } from 'vue'
|
||||
import { EditorView, lineNumbers, highlightActiveLineGutter, keymap } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { json } from '@codemirror/lang-json'
|
||||
import { cpp } from '@codemirror/lang-cpp'
|
||||
import { css } from '@codemirror/lang-css'
|
||||
import { go } from '@codemirror/lang-go'
|
||||
import { html } from '@codemirror/lang-html'
|
||||
import { java } from '@codemirror/lang-java'
|
||||
import { markdown } from '@codemirror/lang-markdown'
|
||||
import { php } from '@codemirror/lang-php'
|
||||
import { python } from '@codemirror/lang-python'
|
||||
import { rust } from '@codemirror/lang-rust'
|
||||
import { sql } from '@codemirror/lang-sql'
|
||||
import { yaml } from '@codemirror/lang-yaml'
|
||||
import { StreamLanguage } from '@codemirror/language'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import { defaultKeymap, history } from '@codemirror/commands'
|
||||
import { bracketMatching } from '@codemirror/language'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { loadLanguageExtension, getLanguageFromExtension } from '@/utils/codeMirrorLoader'
|
||||
|
||||
// Legacy modes for languages without dedicated packages
|
||||
import { csharp, kotlin } from '@codemirror/legacy-modes/mode/clike'
|
||||
import { swift } from '@codemirror/legacy-modes/mode/swift'
|
||||
import { ruby } from '@codemirror/legacy-modes/mode/ruby'
|
||||
import { shell } from '@codemirror/legacy-modes/mode/shell'
|
||||
import { octave } from '@codemirror/legacy-modes/mode/octave'
|
||||
import { perl } from '@codemirror/legacy-modes/mode/perl'
|
||||
import { r } from '@codemirror/legacy-modes/mode/r'
|
||||
import { properties } from '@codemirror/legacy-modes/mode/properties'
|
||||
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'
|
||||
import { stex } from '@codemirror/legacy-modes/mode/stex'
|
||||
import { xml } from '@codemirror/legacy-modes/mode/xml'
|
||||
|
||||
// ==================== Constants ====================
|
||||
// 文件扩展名到 CodeMirror 语言包的映射
|
||||
const LANGUAGE_MAP = {
|
||||
// JavaScript/TypeScript (使用 javascript 包)
|
||||
javascript: ['js', 'jsx', 'mjs', 'cjs', 'cts', 'mts'],
|
||||
typescript: ['ts', 'tsx', 'cts', 'mts'],
|
||||
|
||||
// 数据格式
|
||||
json: ['json'],
|
||||
yaml: ['yaml', 'yml'],
|
||||
xml: ['xml', 'xhtml', 'svg'],
|
||||
|
||||
// Web
|
||||
html: ['html', 'htm'],
|
||||
css: ['css', 'scss', 'sass', 'less'],
|
||||
|
||||
// 系统编程
|
||||
cpp: ['cpp', 'c', 'cc', 'cxx', 'h', 'hpp', 'hxx'],
|
||||
rust: ['rs'],
|
||||
go: ['go'],
|
||||
|
||||
// 脚本语言
|
||||
python: ['py', 'pyw'],
|
||||
php: ['php'],
|
||||
ruby: ['rb'],
|
||||
perl: ['pl', 'pm'],
|
||||
shell: ['sh', 'bash', 'zsh', 'fish', 'cmd', 'bat', 'ps1'],
|
||||
sql: ['sql'],
|
||||
|
||||
// JVM 语言
|
||||
java: ['java'],
|
||||
kotlin: ['kt', 'kts'],
|
||||
csharp: ['cs', 'csx'],
|
||||
|
||||
// 其他语言
|
||||
swift: ['swift'],
|
||||
markdown: ['md', 'markdown'],
|
||||
r: ['r'],
|
||||
matlab: ['m'],
|
||||
latex: ['tex'],
|
||||
makefile: ['makefile', 'make', 'mk', 'gnumakefile'],
|
||||
ini: ['ini', 'cfg', 'conf', 'properties'],
|
||||
dockerfile: ['dockerfile', 'containerfile'],
|
||||
gitignore: ['gitignore', 'gitignore-global', 'gitattributes'],
|
||||
|
||||
// 纯文本(未知类型)
|
||||
text: ['txt', 'text', 'log', 'csv']
|
||||
}
|
||||
|
||||
// ==================== Props & Emits ====================
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
fileExtension: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
modelValue: { type: String, required: true },
|
||||
fileExtension: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
// ==================== State ====================
|
||||
const { isDark } = useTheme()
|
||||
const themeStore = useThemeStore()
|
||||
const editorContainer = ref(null)
|
||||
let view = null
|
||||
|
||||
// ==================== Editor Management ====================
|
||||
/**
|
||||
* 创建编辑器扩展配置
|
||||
*/
|
||||
const createExtensions = () => {
|
||||
const createExtensions = async () => {
|
||||
const extensions = [
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
history(),
|
||||
keymap.of(defaultKeymap),
|
||||
// 不使用 historyKeymap,避免 Ctrl+Z 与外部重置功能冲突
|
||||
// 用户可以通过外部的重置按钮或 Ctrl+Z(全局快捷键)恢复原始内容
|
||||
bracketMatching(),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
@@ -125,202 +36,64 @@ const createExtensions = () => {
|
||||
}
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
height: '100%',
|
||||
fontSize: '13px'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
overflow: 'auto',
|
||||
fontFamily: 'Consolas, Monaco, Courier New, monospace'
|
||||
},
|
||||
'.cm-content': {
|
||||
padding: '8px',
|
||||
minHeight: '100%'
|
||||
},
|
||||
'.cm-line': {
|
||||
padding: '0 0'
|
||||
},
|
||||
'&.cm-focused': {
|
||||
outline: 'none'
|
||||
}
|
||||
'&': { height: '100%', fontSize: '13px' },
|
||||
'.cm-scroller': { overflow: 'auto', fontFamily: 'Consolas, Monaco, Courier New, monospace' },
|
||||
'.cm-content': { padding: '8px', minHeight: '100%' },
|
||||
'.cm-line': { padding: '0 0' },
|
||||
'&.cm-focused': { outline: 'none' }
|
||||
})
|
||||
]
|
||||
|
||||
// 主题
|
||||
if (isDark.value) {
|
||||
if (themeStore.isDark) {
|
||||
extensions.push(oneDark)
|
||||
}
|
||||
|
||||
// 语言支持
|
||||
const ext = props.fileExtension.toLowerCase()
|
||||
|
||||
// JavaScript/TypeScript
|
||||
if (LANGUAGE_MAP.javascript.includes(ext) || LANGUAGE_MAP.typescript.includes(ext)) {
|
||||
extensions.push(javascript({ jsx: true }))
|
||||
const language = getLanguageFromExtension(props.fileExtension)
|
||||
if (language !== 'text') {
|
||||
const langExtension = await loadLanguageExtension(language)
|
||||
if (langExtension) {
|
||||
extensions.push(langExtension)
|
||||
}
|
||||
}
|
||||
// JSON
|
||||
else if (LANGUAGE_MAP.json.includes(ext)) {
|
||||
extensions.push(json())
|
||||
}
|
||||
// YAML
|
||||
else if (LANGUAGE_MAP.yaml.includes(ext)) {
|
||||
extensions.push(yaml())
|
||||
}
|
||||
// HTML
|
||||
else if (LANGUAGE_MAP.html.includes(ext)) {
|
||||
extensions.push(html())
|
||||
}
|
||||
// CSS (including SCSS, SASS, LESS)
|
||||
else if (LANGUAGE_MAP.css.includes(ext)) {
|
||||
extensions.push(css())
|
||||
}
|
||||
// C/C++
|
||||
else if (LANGUAGE_MAP.cpp.includes(ext)) {
|
||||
extensions.push(cpp())
|
||||
}
|
||||
// Rust
|
||||
else if (LANGUAGE_MAP.rust.includes(ext)) {
|
||||
extensions.push(rust())
|
||||
}
|
||||
// Go
|
||||
else if (LANGUAGE_MAP.go.includes(ext)) {
|
||||
extensions.push(go())
|
||||
}
|
||||
// Python
|
||||
else if (LANGUAGE_MAP.python.includes(ext)) {
|
||||
extensions.push(python())
|
||||
}
|
||||
// PHP
|
||||
else if (LANGUAGE_MAP.php.includes(ext)) {
|
||||
extensions.push(php())
|
||||
}
|
||||
// SQL
|
||||
else if (LANGUAGE_MAP.sql.includes(ext)) {
|
||||
extensions.push(sql())
|
||||
}
|
||||
// Markdown
|
||||
else if (LANGUAGE_MAP.markdown.includes(ext)) {
|
||||
extensions.push(markdown())
|
||||
}
|
||||
// Java
|
||||
else if (LANGUAGE_MAP.java.includes(ext)) {
|
||||
extensions.push(java())
|
||||
}
|
||||
// Ruby
|
||||
else if (LANGUAGE_MAP.ruby.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(ruby))
|
||||
}
|
||||
// Shell
|
||||
else if (LANGUAGE_MAP.shell.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(shell))
|
||||
}
|
||||
// Kotlin
|
||||
else if (LANGUAGE_MAP.kotlin.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(kotlin))
|
||||
}
|
||||
// C#
|
||||
else if (LANGUAGE_MAP.csharp.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(csharp))
|
||||
}
|
||||
// Swift
|
||||
else if (LANGUAGE_MAP.swift.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(swift))
|
||||
}
|
||||
// R
|
||||
else if (LANGUAGE_MAP.r.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(r))
|
||||
}
|
||||
// Perl
|
||||
else if (LANGUAGE_MAP.perl.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(perl))
|
||||
}
|
||||
// LaTeX
|
||||
else if (LANGUAGE_MAP.latex.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(stex))
|
||||
}
|
||||
// Makefile (使用纯文本,legacy-modes 没有专门的 makefile 支持)
|
||||
else if (LANGUAGE_MAP.makefile.includes(ext)) {
|
||||
// 纯文本模式,不添加语言扩展
|
||||
}
|
||||
// INI/Properties/Dockerfile
|
||||
else if (LANGUAGE_MAP.ini.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(properties))
|
||||
}
|
||||
else if (LANGUAGE_MAP.dockerfile.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(dockerFile))
|
||||
}
|
||||
// XML (包括 SVG)
|
||||
else if (LANGUAGE_MAP.xml.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(xml))
|
||||
}
|
||||
// Matlab/Octave
|
||||
else if (LANGUAGE_MAP.matlab.includes(ext)) {
|
||||
extensions.push(StreamLanguage.define(octave))
|
||||
}
|
||||
// 其他类型(包括 gitignore, dockerfile, txt 等)使用纯文本模式
|
||||
// 不添加任何语言扩展,保持纯文本
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建编辑器实例
|
||||
*/
|
||||
const createEditor = (docContent = '') => {
|
||||
const createEditor = async (docContent = '') => {
|
||||
if (!editorContainer.value) return
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: docContent,
|
||||
extensions: createExtensions()
|
||||
})
|
||||
|
||||
view = new EditorView({
|
||||
state,
|
||||
parent: editorContainer.value
|
||||
})
|
||||
const extensions = await createExtensions()
|
||||
const state = EditorState.create({ doc: docContent, extensions })
|
||||
view = new EditorView({ state, parent: editorContainer.value })
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建编辑器(保留内容)
|
||||
*/
|
||||
const recreateEditor = () => {
|
||||
const recreateEditor = async () => {
|
||||
if (!view) return
|
||||
const currentDoc = view.state.doc.toString()
|
||||
view.destroy()
|
||||
createEditor(currentDoc)
|
||||
await createEditor(currentDoc)
|
||||
}
|
||||
|
||||
// ==================== Lifecycle ====================
|
||||
onMounted(() => {
|
||||
createEditor(props.modelValue || '')
|
||||
onMounted(async () => {
|
||||
await createEditor(props.modelValue || '')
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (view) {
|
||||
view.destroy()
|
||||
}
|
||||
view?.destroy()
|
||||
})
|
||||
|
||||
// ==================== Watchers ====================
|
||||
// 监听外部内容变化
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (view && newValue !== view.state.doc.toString()) {
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.state.doc.length,
|
||||
insert: newValue || ''
|
||||
}
|
||||
changes: { from: 0, to: view.state.doc.length, insert: newValue || '' }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 监听主题或文件扩展名变化,重建编辑器
|
||||
// 使用 nextTick 确保 DOM 更新完成后再重建,避免视觉抖动
|
||||
import { nextTick } from 'vue'
|
||||
const isDark = computed(() => themeStore.isDark)
|
||||
watch([isDark, () => props.fileExtension], async () => {
|
||||
await nextTick()
|
||||
recreateEditor()
|
||||
await recreateEditor()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user