- 编辑器: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 完整记录迁移决策、架构、用法
94 lines
3.3 KiB
HTML
94 lines
3.3 KiB
HTML
{{template "layout/header.html" .}}
|
||
<body>
|
||
|
||
{{template "layout/nav.html" .}}
|
||
|
||
<div class="container post-detail">
|
||
<article class="post-article">
|
||
<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-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>
|
||
{{end}}
|
||
</div>
|
||
</header>
|
||
|
||
<div class="post-detail-body" id="postContent"></div>
|
||
|
||
{{if .Post.RejectReason}}
|
||
<div class="post-reject-reason">
|
||
<strong>退回理由:</strong>{{.Post.RejectReason}}
|
||
</div>
|
||
{{end}}
|
||
</article>
|
||
|
||
{{if .IsLoggedIn}}
|
||
<div class="post-actions">
|
||
{{if and $.Post (or (eq $.Post.UserID $.UID) $.CanAccessAdmin) (ne $.Post.Status "pending") (ne $.Post.Status "locked")}}
|
||
<a href="/posts/{{$.Post.ID}}/edit" class="btn btn-secondary">编辑</a>
|
||
{{end}}
|
||
{{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}}
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
{{template "layout/footer.html" .}}
|
||
|
||
{{/* 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() {
|
||
var el = document.getElementById('postContent');
|
||
var mdEl = document.getElementById('postMdContent');
|
||
if (!el || !mdEl) return;
|
||
|
||
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;
|
||
btn.addEventListener('click', function() {
|
||
if (!confirm('确认提交审核?审核通过后将公开发布。')) return;
|
||
var csrf = document.querySelector('meta[name="csrf-token"]');
|
||
fetch('/api/posts/' + btn.dataset.id + '/submit', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-Token': csrf ? csrf.getAttribute('content') : ''
|
||
}
|
||
})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) { window.location.reload(); }
|
||
else { alert(d.message || '提交失败'); }
|
||
})
|
||
.catch(function() { alert('请求失败'); });
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|