fix: 修复编辑器代码块样式不一致 + 提交 HTML 保留格式 + 详情页布局优化

- 编辑器 pre code 移除 wangEditor 内置 border,与预览区样式统一
- 代码块语言标签 padding 加大并用 !important 对抗 wangEditor 全局重置
- 编辑器 ::before 伪元素仅作降级方案,避免与 JS 注入的 .code-lang-label 冲突
- 编辑器 pre padding-top 统一为 36px,font-size/line-height 对齐预览区
- 提交改用 editor.getHtml(),后端 sanitizeHTML 替代 renderMarkdown,保留富文本格式
- 详情页 post-detail-header 覆盖 common.css sticky,固定在文章卡片内
- 详情页增加白色卡片背景、圆角、阴影和内边距
- 详情页添加代码块语言标签 JS 注入
- 详情页用户名移除括号 UID 显示,移除管理按钮
This commit is contained in:
2026-05-28 00:53:02 +08:00
parent ec8fa3eb56
commit 2917562bf0
4 changed files with 52 additions and 46 deletions

View File

@ -53,8 +53,14 @@ func (s *PostService) auditEnabled() bool {
return s.ss.IsAuditEnabled()
}
// renderMarkdown 将 Markdown 渲染为安全 HTMLGoldmark + bluemonday 消毒
func (s *PostService) renderMarkdown(body string) (string, error) {
// sanitizeHTML 对编辑器输出的 HTML 做安全消毒(bluemonday
// 编辑器提交的是富文本 HTML不再经过 Markdown 渲染
func (s *PostService) sanitizeHTML(body string) string {
return ucgPolicy.Sanitize(body)
}
// RenderMarkdown 对外暴露,用于预览(从纯文本 Markdown 渲染为 HTML
func (s *PostService) RenderMarkdown(body string) (string, error) {
var buf bytes.Buffer
if err := s.md.Convert([]byte(body), &buf); err != nil {
return "", err
@ -64,10 +70,7 @@ func (s *PostService) renderMarkdown(body string) (string, error) {
// Create 创建帖子
func (s *PostService) Create(userID uint, title, body string) (*model.Post, error) {
bodyHTML, err := s.renderMarkdown(body)
if err != nil {
bodyHTML = body // 渲染失败降级使用纯文本
}
bodyHTML := s.sanitizeHTML(body)
status := model.PostStatusApproved
if s.auditEnabled() {
@ -125,12 +128,7 @@ func (s *PostService) Update(postID uint, title, body string) error {
post.Title = title
post.Body = body
bodyHTML, err := s.renderMarkdown(body)
if err != nil {
bodyHTML = body
}
post.BodyHTML = bodyHTML
post.BodyHTML = s.sanitizeHTML(body)
// rejected 状态编辑后自动重置为 draft
if post.Status == model.PostStatusRejected {
@ -214,8 +212,3 @@ func (s *PostService) Unlock(postID uint) error {
func (s *PostService) Restore(postID uint) error {
return s.repo.Restore(postID)
}
// RenderMarkdown 公开的 Markdown 渲染(供预览 API 使用,不写 DB
func (s *PostService) RenderMarkdown(body string) (string, error) {
return s.renderMarkdown(body)
}

View File

@ -8,7 +8,7 @@
<header class="post-detail-header">
<h1 class="post-detail-title">{{.Post.Title}}</h1>
<div class="post-detail-meta">
<span class="post-author">{{if .Post.AuthorName}}{{.Post.AuthorName}}({{.Post.UserID}}){{else}}UID{{.Post.UserID}}{{end}}</span>
<span class="post-author">{{if .Post.AuthorName}}{{.Post.AuthorName}}{{else}}UID{{.Post.UserID}}{{end}}</span>
<span class="post-time">{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</span>
{{if ne .Post.Status "approved"}}
<span class="post-status post-status-{{.Post.Status}}">{{index $.StatusNames .Post.Status}}</span>
@ -39,9 +39,6 @@
{{if and $.Post (eq $.Post.UserID $.UID) (or (eq $.Post.Status "draft") (eq $.Post.Status "rejected"))}}
<button class="btn btn-primary" id="submitAuditBtn" data-id="{{$.Post.ID}}">提交审核</button>
{{end}}
{{if $.CanAccessAdmin}}
<a href="/admin/posts" class="btn btn-secondary">管理</a>
{{end}}
</div>
{{end}}
</div>
@ -51,6 +48,18 @@
<script src="/static/js/common.js"></script>
<script>
(function() {
// 代码块语言标签注入
document.querySelectorAll('.post-detail-body pre').forEach(function(pre) {
var code = pre.querySelector('code[class*="language-"]');
if (!code) return;
var match = code.className.match(/language-(\w+)/);
if (!match) return;
var label = document.createElement('span');
label.className = 'code-lang-label';
label.textContent = match[1];
pre.insertBefore(label, pre.firstChild);
});
var btn = document.getElementById('submitAuditBtn');
if (!btn) return;
btn.addEventListener('click', function() {

View File

@ -61,11 +61,21 @@
/* ========== 帖子详情 ========== */
.post-detail {
max-width: 800px;
margin: 0 auto;
padding: 32px 16px;
margin: 24px auto 40px;
}
.post-article {
background: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
padding: 40px 48px;
}
.post-detail-header {
position: static !important;
z-index: auto !important;
box-shadow: none !important;
background: transparent !important;
margin-bottom: 32px;
padding-bottom: 20px;
border-bottom: 1px solid #e5e7eb;
@ -115,28 +125,18 @@
color: #2980b9;
}
.post-detail-body pre {
position: relative;
background: #f3f4f6;
padding: 16px;
padding-top: 36px;
border-radius: 8px;
overflow-x: auto;
font-size: 14px;
line-height: 1.5;
}
/* 代码块语言标签(编辑器内 + 预览页通用) */
/* 代码块基础样式(详情页预览 + 编辑器内通用) */
.post-detail-body pre,
#editor-container [data-slate-editor] pre {
position: relative;
background: #f3f4f6;
padding: 16px;
padding: 12px 16px;
padding-top: 36px;
border-radius: 8px;
overflow-x: auto;
font-size: 14px;
line-height: 1.5;
font-family: 'Menlo', 'Consolas', 'Monaco', 'Liberation Mono', monospace;
}
/* 通过 JS 动态注入语言标签,因为 CSS attr() 无法从 class 中提取 */
@ -145,7 +145,7 @@
top: 0;
left: 0;
right: 0;
padding: 4px 16px;
padding: 8px 16px !important;
background: #e5e7eb;
color: #6b7280;
font-size: 11px;
@ -157,6 +157,7 @@
.post-detail-body pre code,
#editor-container [data-slate-editor] pre code {
background: transparent !important;
border: none !important;
padding: 0 !important;
font-size: inherit !important;
}
@ -433,28 +434,29 @@
font-style: normal !important;
}
/* 编辑器内代码块样式 */
/* 编辑器内代码块样式 — 以预览区样式为准 */
#editor-container [data-slate-editor] pre {
position: relative;
background: #f3f4f6 !important;
padding: 12px 16px !important;
padding-top: 32px !important;
padding-top: 36px !important;
border-radius: 8px !important;
font-family: 'Menlo', 'Consolas', 'Monaco', 'Liberation Mono', monospace !important;
font-size: 13px !important;
line-height: 1.6 !important;
font-size: 14px !important;
line-height: 1.5 !important;
overflow-x: auto !important;
margin: 12px 0 !important;
}
/* 编辑器内代码块语言标签 */
#editor-container [data-slate-editor] pre::before {
/* 语言标签由 JS 动态注入 .code-lang-label不再使用 ::before 伪元素 */
/* 若 wangEditor 的 pre 上有 data-language 属性,则作为降级方案 */
#editor-container [data-slate-editor] pre[data-language]::before {
content: attr(data-language);
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 3px 16px;
padding: 8px 16px;
background: #e5e7eb;
color: #6b7280;
font-size: 11px;
@ -465,6 +467,7 @@
#editor-container [data-slate-editor] pre code {
background: transparent !important;
border: none !important;
padding: 0 !important;
font-size: inherit !important;
}

View File

@ -372,10 +372,11 @@ form.addEventListener('submit', e => {
e.preventDefault();
const title = titleEl.value.trim();
const body = getEditorText();
const body = getEditorHtml();
const bodyText = getEditorText();
if (!title) { alert('请输入标题'); return; }
if (!body) { alert('请输入正文'); return; }
if (!bodyText) { alert('请输入正文'); return; }
let url = '/api/posts';
let method = 'POST';