refactor: 用 CodeMirror 6 重构 MD 编辑器,废弃自研 WYSIWYG
- 引入 CodeMirror 6 (MIT) 替代 contenteditable WYSIWYG 编辑器,消除 execCommand 废弃 API 风险 - 全新全宽三栏布局:标题栏、工具栏、编辑区+可切换预览、状态栏 - 工具栏新增删除线/H1/H4/任务列表/表格/预览切换/全屏按钮 - 支持粘贴图片自动上传 (Ctrl+V)、拖拽上传、上传进度反馈 - 代码块语言下拉选择器替代 prompt() 弹窗,支持搜索和键盘导航 - 自动保存草稿 (30s 防抖,localStorage + 后端静默保存) - 分屏实时预览 (Ctrl+Shift+P),编辑器与预览区双向滚动同步 - 字数/行数/阅读时间统计、全屏编辑 (F11) - 快捷键:Ctrl+B/I/K/`/Shift+I/S/Shift+P + Tab/Shift+Tab - 删除旧的 htmlToMarkdown、syncMarkdownToWysiwyg、mode-tab 等废弃代码 - 后端 API 零改动,仅移除控制器中不再需要的 ExtraJS 字段
This commit is contained in:
@ -162,263 +162,259 @@
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ========== 编辑器 ========== */
|
||||
.editor-container {
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.editor-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.post-form .form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.post-form label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ========== 模式标签 ========== */
|
||||
.editor-modes {
|
||||
/* ========== 编辑器布局(全宽) ========== */
|
||||
.editor-layout {
|
||||
height: calc(100vh - 56px);
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 0;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mode-tab {
|
||||
padding: 8px 18px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-bottom: none;
|
||||
border-radius: 8px 8px 0 0;
|
||||
background: #f9fafb;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.editor-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mode-tab.active {
|
||||
/* ---- 标题区域 ---- */
|
||||
.editor-header {
|
||||
padding: 16px 24px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
color: #6366f1;
|
||||
border-color: #d1d5db;
|
||||
z-index: 2;
|
||||
margin-bottom: -1px;
|
||||
padding-bottom: 9px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ========== 工具条 ========== */
|
||||
.editor-title-input {
|
||||
width: 100%;
|
||||
padding: 6px 0;
|
||||
border: none;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #111;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.editor-title-input::placeholder {
|
||||
color: #c4c4c4;
|
||||
}
|
||||
|
||||
/* ---- 工具栏 ---- */
|
||||
.editor-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 6px 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-top: none;
|
||||
border-radius: 0 0 8px 8px;
|
||||
min-height: 40px;
|
||||
flex-wrap: wrap;
|
||||
padding: 5px 16px;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
min-height: 38px;
|
||||
flex-shrink: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 编辑区上边距 */
|
||||
.editor-toolbar + .editor-body,
|
||||
.editor-modes + .editor-body {
|
||||
margin-top: 0;
|
||||
.tb-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.tb-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transition: background 0.15s;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tb-btn:hover {
|
||||
background: #f3f4f6;
|
||||
background: #e5e7eb;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.tb-btn:active {
|
||||
background: #d1d5db;
|
||||
}
|
||||
|
||||
.tb-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.tb-sep {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #e5e7eb;
|
||||
margin: 0 4px;
|
||||
margin: 0 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ========== 编辑区 ========== */
|
||||
.editor-body {
|
||||
position: relative;
|
||||
clear: both;
|
||||
.tb-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tb-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.tb-toggle.active {
|
||||
background: #e0e7ff;
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
/* ---- 代码块语言选择器 ---- */
|
||||
.lang-picker {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 220px;
|
||||
background: #fff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
.lang-picker-search input {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-family: 'Menlo', 'Consolas', 'Monaco', monospace;
|
||||
line-height: 1.7;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
/* WYSIWYG 编辑器 */
|
||||
.wysiwyg-editor {
|
||||
min-height: 400px;
|
||||
padding: 14px 18px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: #1f2937;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.wysiwyg-editor:focus {
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 2px rgba(99,102,241,0.15);
|
||||
.lang-picker-list {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.wysiwyg-editor h1 { font-size: 2em; margin: 0.5em 0 0.3em; }
|
||||
.wysiwyg-editor h2 { font-size: 1.6em; margin: 0.5em 0 0.3em; }
|
||||
.wysiwyg-editor h3 { font-size: 1.3em; margin: 0.4em 0 0.25em; }
|
||||
.wysiwyg-editor p { margin: 0 0 0.5em; }
|
||||
.wysiwyg-editor pre {
|
||||
.lang-picker-item {
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lang-picker-item:hover {
|
||||
background: #f3f4f6;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo','Consolas',monospace;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.wysiwyg-editor code {
|
||||
background: #f3f4f6;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Menlo','Consolas',monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.wysiwyg-editor blockquote {
|
||||
border-left: 4px solid #6366f1;
|
||||
padding: 2px 14px;
|
||||
color: #6b7280;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.wysiwyg-editor ul, .wysiwyg-editor ol {
|
||||
padding-left: 24px;
|
||||
margin: 0.4em 0;
|
||||
}
|
||||
.wysiwyg-editor li {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.wysiwyg-editor img {
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.wysiwyg-editor a {
|
||||
|
||||
.lang-picker-item.selected {
|
||||
background: #e0e7ff;
|
||||
color: #6366f1;
|
||||
}
|
||||
.wysiwyg-editor hr {
|
||||
border: none;
|
||||
border-top: 2px solid #e5e7eb;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.wysiwyg-editor table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.wysiwyg-editor th, .wysiwyg-editor td {
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
|
||||
/* ---- 编辑器主体(编辑区 + 预览区) ---- */
|
||||
.editor-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ========== 分屏预览 ========== */
|
||||
.editor-body.split-mode .form-textarea {
|
||||
width: 50%;
|
||||
float: left;
|
||||
border-radius: 8px 0 0 8px;
|
||||
border-right: none;
|
||||
.editor-pane {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.split-preview {
|
||||
.editor-pane .cm-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.editor-pane .cm-editor .cm-scroller {
|
||||
font-family: 'Menlo', 'Consolas', 'Monaco', 'Liberation Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.editor-pane .cm-editor .cm-content {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.editor-pane .cm-editor .cm-gutters {
|
||||
border-right: 1px solid #e5e7eb;
|
||||
background: #fafafa;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* 预览区 */
|
||||
.preview-pane {
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.preview-pane.visible {
|
||||
width: 50%;
|
||||
float: right;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 20px 28px;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content {
|
||||
padding: 14px 18px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0 8px 8px 0;
|
||||
min-height: 400px;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
/* ---- 状态栏 ---- */
|
||||
.editor-statusbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 6px 20px;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
flex-shrink: 0;
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content h1,
|
||||
.split-preview .split-preview-content h2,
|
||||
.split-preview .split-preview-content h3 {
|
||||
margin-top: 18px;
|
||||
margin-bottom: 8px;
|
||||
.status-item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content pre {
|
||||
background: #e5e7eb;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
.status-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-draft {
|
||||
color: #059669;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
padding: 5px 18px;
|
||||
font-size: 13px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content blockquote {
|
||||
border-left: 4px solid #6366f1;
|
||||
padding-left: 14px;
|
||||
color: #6b7280;
|
||||
/* ---- 全屏模式 ---- */
|
||||
.editor-layout.fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ========== 404 ========== */
|
||||
|
||||
Reference in New Issue
Block a user