feat: Tiptap替换为Vditor + MD存储重构 + Shortcode卡片扩展
- 编辑器:Tiptap 替换为 Vditor 3.11.2(wysiwyg 模式,本地托管 5.8MB,去 CDN) - 存储:Body 字段从 HTML 改为 Markdown,去除 BodyHTML,新增 Excerpt 列表摘要 - 安全:去除 bluemonday 依赖(MD 纯文本无 XSS 风险),go.mod 已清理 - Shortcode:新增 [zone:type:params] 扩展语法,支持活动/游戏/投票/资源卡片 - 图片上传:POST /api/posts/upload-image 直接返回 Vditor 原生响应格式 - 草稿:localStorage 键名改为 draft_post_body_md,与旧 HTML 草稿隔离 - 详情页:Vditor.method.min.js 客户端 MD → HTML 渲染,去 highlight.js CDN - 样式:去 Tiptap 样式 ~190 行,精简工具栏 CSS,新增卡片样式 - 文档:vditor-migration-plan.md 完整记录迁移决策、架构、用法
This commit is contained in:
@ -1,5 +1,4 @@
|
||||
{{template "layout/header.html" .}}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/github-dark.min.css">
|
||||
<body>
|
||||
|
||||
{{template "layout/nav.html" .}}
|
||||
@ -9,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}}{{else}}UID{{.Post.UserID}}{{end}}</span>
|
||||
<span class="post-author">{{if .Post.AuthorName}}{{.Post.AuthorName}}({{.Post.UserID}}){{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>
|
||||
@ -17,13 +16,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="post-detail-body">
|
||||
{{.PostBodyHTML}}
|
||||
{{/* 如果 BodyHTML 为空,直接显示原文 */}}
|
||||
</div>
|
||||
{{if not .PostBodyHTML}}
|
||||
<div class="post-detail-body-raw">{{.Post.Body}}</div>
|
||||
{{end}}
|
||||
<div class="post-detail-body" id="postContent"></div>
|
||||
|
||||
{{if .Post.RejectReason}}
|
||||
<div class="post-reject-reason">
|
||||
@ -46,38 +39,34 @@
|
||||
|
||||
{{template "layout/footer.html" .}}
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js"></script>
|
||||
{{/* MD 内容安全传递给 JS */}}
|
||||
<textarea id="postMdContent" style="display:none">{{.Post.Body}}</textarea>
|
||||
|
||||
<link rel="stylesheet" href="/static/vditor/dist/index.css">
|
||||
<script src="/static/vditor/dist/method.min.js"></script>
|
||||
<script src="/static/js/common.js"></script>
|
||||
<script src="/static/js/shortcode.js"></script>
|
||||
<script>
|
||||
// ---- 代码块语言标签注入 + 语法高亮 ----
|
||||
(function() {
|
||||
document.querySelectorAll('.post-detail-body pre').forEach(function(pre) {
|
||||
// 从 pre 的 data-language 或 code 的 class 中获取语言
|
||||
var lang = pre.getAttribute('data-language');
|
||||
if (!lang) {
|
||||
var code = pre.querySelector('code');
|
||||
if (code) {
|
||||
var match = code.className.match(/language-(\w+)/);
|
||||
if (match) lang = match[1];
|
||||
}
|
||||
}
|
||||
lang = lang || 'text';
|
||||
var el = document.getElementById('postContent');
|
||||
var mdEl = document.getElementById('postMdContent');
|
||||
if (!el || !mdEl) return;
|
||||
|
||||
var label = document.createElement('div');
|
||||
label.className = 'code-lang-label';
|
||||
label.textContent = lang;
|
||||
pre.insertBefore(label, pre.firstChild);
|
||||
pre.style.paddingTop = '32px';
|
||||
|
||||
// 语法高亮
|
||||
var code = pre.querySelector('code');
|
||||
if (code && lang !== 'text') {
|
||||
code.classList.add('language-' + lang);
|
||||
hljs.highlightElement(code);
|
||||
}
|
||||
var md = mdEl.value;
|
||||
Vditor.preview(el, md, {
|
||||
cdn: '/static/vditor',
|
||||
theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' },
|
||||
hljs: { style: 'github-dark', enable: true },
|
||||
});
|
||||
})();
|
||||
|
||||
// Vditor.preview() 渲染 MD,shortcode 占位 div 原样保留
|
||||
// 渲染完成后,将占位 div 替换为对应卡片 UI
|
||||
renderShortcodes(el);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// 提交审核按钮
|
||||
(function() {
|
||||
var btn = document.getElementById('submitAuditBtn');
|
||||
if (!btn) return;
|
||||
|
||||
Reference in New Issue
Block a user