feat: 替换 textarea 为 CodeMirror 6 编辑器

新增功能:
- CodeMirror 6 专业编辑器替代原生 textarea
- 语法高亮(Markdown 专用)
- 行号(内置,精确同步)
- 代码折叠(foldGutter)
- 括号匹配
- 多光标选区(rectangularSelection)
- 内置搜索高亮(highlightSelectionMatches)
- undo/redo 原生支持(history)
- Tab 缩进(indentWithTab)
- 暗色主题(oneDark)

新增文件:
- useCodeMirror.ts: CodeMirror 初始化 Hook
- EditorToolbar.tsx: 格式化工具栏(B/I/S/标题/列表/引用/代码/链接/图片)
- Editor.tsx: 重写为 CodeMirror 组件

格式化工具栏按钮:
- 粗体/斜体/删除线/行内代码
- H1/H2/H3 标题
- 无序列表/有序列表/引用/代码块
- 链接/图片

快捷键:
- Ctrl+B 粗体
- Ctrl+I 斜体
This commit is contained in:
thzxx
2026-05-27 23:04:05 +08:00
parent b3987e9abd
commit 1cc4206b32
6 changed files with 459 additions and 305 deletions
+110 -30
View File
@@ -109,51 +109,131 @@ html, body {
border-right: 1px solid var(--border);
}
#editor-wrapper {
/* Editor Container */
.editor-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
#line-numbers {
width: 50px;
/* Editor Toolbar */
.editor-toolbar {
display: flex;
align-items: center;
gap: 2px;
padding: 4px 8px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-light);
flex-shrink: 0;
overflow-x: auto;
}
.editor-toolbar::-webkit-scrollbar { height: 0; }
.toolbar-btn-sm {
display: flex;
align-items: center;
justify-content: center;
min-width: 28px;
height: 26px;
padding: 0 6px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 12px;
font-family: var(--font-ui);
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.toolbar-btn-sm:hover {
background: var(--bg-tertiary);
color: var(--text);
}
.toolbar-divider-sm {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 4px;
}
/* CodeMirror Wrapper */
.codemirror-wrapper {
flex: 1;
overflow: hidden;
}
.codemirror-wrapper .cm-editor {
height: 100%;
font-family: var(--font-mono);
font-size: 13px;
}
.codemirror-wrapper .cm-editor .cm-scroller {
overflow: auto;
}
.codemirror-wrapper .cm-editor .cm-content {
padding: 12px 0;
}
.codemirror-wrapper .cm-editor .cm-line {
padding: 0 16px;
}
.codemirror-wrapper .cm-editor .cm-gutters {
background: var(--bg-secondary);
border-right: 1px solid var(--border-light);
padding: 12px 0;
text-align: right;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
color: var(--text-tertiary);
overflow: hidden;
user-select: none;
flex-shrink: 0;
font-size: 12px;
min-width: 40px;
}
#line-numbers .line-num {
padding-right: 12px;
height: 20.8px;
.codemirror-wrapper .cm-editor .cm-activeLineGutter {
background: var(--bg-tertiary);
color: var(--text-secondary);
}
#editor {
flex: 1;
width: 100%;
padding: 12px 16px;
border: none;
.codemirror-wrapper .cm-editor .cm-activeLine {
background: var(--bg-tertiary);
}
.codemirror-wrapper .cm-editor.cm-focused {
outline: none;
resize: none;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
color: var(--text);
background: var(--bg);
tab-size: 4;
overflow-y: auto;
user-select: text;
}
#editor::placeholder { color: var(--text-tertiary); }
/* 搜索高亮 */
.cm-searchMatch {
background: rgba(255, 220, 0, 0.35);
border-radius: 2px;
}
.cm-searchMatch.cm-searchMatch-selected {
background: rgba(255, 150, 0, 0.55);
}
/* Fold gutter */
.cm-foldGutter .cm-gutterElement {
cursor: pointer;
opacity: 0.5;
}
.cm-foldGutter .cm-gutterElement:hover {
opacity: 1;
}
/* Selection */
.cm-selectionBackground {
background: rgba(26, 115, 232, 0.2) !important;
}
.cm-editor .cm-cursor {
border-left-color: var(--text);
}
/* Resizer */
#resizer {