fix: 修复 CSRF 缺失 + 重写编辑器为三模式(所见即所得/源码/分屏)
CSRF 修复: - /posts/new 和 /posts/:id/edit 原在独立 postPages group 中,仅含 Required 中间件,缺少 CSRF 下发 - 移动到 pages group(含 CSRF 中间件),用 per-route d.authMdw.Required() 弥补登录校验 - 同时消除 /posts/:id 与 /posts/new 潜在的路由冲突 编辑器重写: - 添加三种编辑模式切换标签:所见即所得 / 源码 / 分屏预览 - 所见即所得(默认):contenteditable 编辑区 + 工具条(execCommand) 支持加粗、斜体、H2/H3、链接、图片、引用、代码、列表、分割线 - 源码:纯 textarea,textarea 作为数据源始终保存 markdown 原文 - 分屏:左 textarea 编辑 + 右实时渲染预览(API 渲染) - 模式切换时自动同步:WYSIWYG → HTML → Markdown → textarea - 工具条在 WYSIWYG 模式用 execCommand,源码模式用文本包裹语法 实现细节: - contenteditable ← textarea 双向同步(WYSIWYG 模式) - 基本 HTML→Markdown 转换器(strong/em/h1-h6/a/pre/code/li/blockquote/img) - 分屏布局用 JS toggled .split-mode class(非 :has(),兼容 Firefox) - 编辑器初始时自动进 WYSIWYG 模式渲染已有内容
This commit is contained in:
@ -164,7 +164,7 @@
|
||||
|
||||
/* ========== 编辑器 ========== */
|
||||
.editor-container {
|
||||
max-width: 900px;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
@ -176,7 +176,7 @@
|
||||
}
|
||||
|
||||
.post-form .form-group {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.post-form label {
|
||||
@ -196,38 +196,229 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ========== 模式标签 ========== */
|
||||
.editor-modes {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.mode-tab.active {
|
||||
background: #fff;
|
||||
color: #6366f1;
|
||||
border-color: #d1d5db;
|
||||
z-index: 2;
|
||||
margin-bottom: -1px;
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
/* ========== 工具条 ========== */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 编辑区上边距 */
|
||||
.editor-toolbar + .editor-body,
|
||||
.editor-modes + .editor-body {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tb-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.tb-btn:hover {
|
||||
background: #f3f4f6;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.tb-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.tb-sep {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #e5e7eb;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* ========== 编辑区 ========== */
|
||||
.editor-body {
|
||||
position: relative;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-family: 'Menlo', 'Consolas', monospace;
|
||||
line-height: 1.6;
|
||||
font-family: 'Menlo', 'Consolas', 'Monaco', monospace;
|
||||
line-height: 1.7;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
/* 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;
|
||||
outline: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.post-preview {
|
||||
margin-top: 32px;
|
||||
padding: 24px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
.wysiwyg-editor:focus {
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 2px rgba(99,102,241,0.15);
|
||||
}
|
||||
|
||||
.post-preview h3 {
|
||||
margin-top: 0;
|
||||
color: #6b7280;
|
||||
.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 {
|
||||
background: #f3f4f6;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo','Consolas',monospace;
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
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 {
|
||||
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-body.split-mode .form-textarea {
|
||||
width: 50%;
|
||||
float: left;
|
||||
border-radius: 8px 0 0 8px;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.split-preview {
|
||||
width: 50%;
|
||||
float: right;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content h1,
|
||||
.split-preview .split-preview-content h2,
|
||||
.split-preview .split-preview-content h3 {
|
||||
margin-top: 18px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content pre {
|
||||
background: #e5e7eb;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.split-preview .split-preview-content blockquote {
|
||||
border-left: 4px solid #6366f1;
|
||||
padding-left: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* ========== 404 ========== */
|
||||
|
||||
Reference in New Issue
Block a user