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:
2026-05-30 15:56:22 +08:00
parent c9808ab9b2
commit 6ec12010f3
121 changed files with 8267 additions and 613 deletions

View File

@ -6,6 +6,10 @@
<div class="editor-layout">
<form id="postForm" class="editor-form">
<input type="hidden" id="postId" value="{{if .Post}}{{.Post.ID}}{{end}}">
{{/* 编辑模式下传入已有的 MD 内容 */}}
{{if .Post}}
<textarea id="editBody" style="display:none">{{.Post.Body}}</textarea>
{{end}}
{{/* 标题区域 */}}
<div class="editor-header">
@ -15,62 +19,10 @@
maxlength="200" required autofocus>
</div>
{{/* 工具栏 */}}
<div class="editor-toolbar" id="toolbar">
<button type="button" data-action="bold" title="加粗 (Ctrl+B)"><strong>B</strong></button>
<button type="button" data-action="italic" title="斜体 (Ctrl+I)"><em>I</em></button>
<button type="button" data-action="strike" title="删除线"><s>S</s></button>
<button type="button" data-action="code" title="行内代码">&lt;/&gt;</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="h1" title="标题 1">H1</button>
<button type="button" data-action="h2" title="标题 2">H2</button>
<button type="button" data-action="h3" title="标题 3">H3</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="bulletList" title="无序列表"></button>
<button type="button" data-action="orderedList" title="有序列表">1.</button>
<button type="button" data-action="blockquote" title="引用">"</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="codeBlock" title="代码块">{ }</button>
<select id="codeLangSelect" class="code-lang-select" style="display:none" title="选择语言">
<option value="text">text</option>
<option value="javascript">JavaScript</option>
<option value="typescript">TypeScript</option>
<option value="python">Python</option>
<option value="go">Go</option>
<option value="rust">Rust</option>
<option value="java">Java</option>
<option value="cpp">C++</option>
<option value="c">C</option>
<option value="csharp">C#</option>
<option value="ruby">Ruby</option>
<option value="php">PHP</option>
<option value="swift">Swift</option>
<option value="kotlin">Kotlin</option>
<option value="sql">SQL</option>
<option value="bash">Bash</option>
<option value="json">JSON</option>
<option value="yaml">YAML</option>
<option value="xml">XML</option>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="shell">Shell</option>
<option value="dockerfile">Dockerfile</option>
<option value="nginx">Nginx</option>
<option value="diff">Diff</option>
</select>
<button type="button" data-action="horizontalRule" title="分隔线"></button>
<span class="toolbar-divider"></span>
<button type="button" data-action="undo" title="撤销"></button>
<button type="button" data-action="redo" title="重做"></button>
</div>
{{/* 编辑器主体 */}}
{{/* Vditor 编辑器容器(工具栏+编辑区由 Vditor 自动生成) */}}
<div class="editor-main">
<div class="editor-pane" id="editorPane">
<div id="editor-container"
data-content="{{if .Post}}{{.Post.BodyHTML}}{{end}}"></div>
<div class="editor-pane">
<div id="vditor"></div>
</div>
</div>
@ -79,7 +31,6 @@
<span class="status-item" id="wordCount">0 字</span>
<span class="status-item status-draft" id="draftStatus" style="display:none">草稿已保存</span>
<span class="status-spacer"></span>
<span class="status-item" id="btnFullscreen" style="cursor:pointer" title="全屏编辑 (F11)">全屏</span>
<button type="submit" class="btn btn-primary btn-submit">{{if .Post}}保存修改{{else}}发布帖子{{end}}</button>
</div>
</form>
@ -87,18 +38,9 @@
{{template "layout/footer.html" .}}
<script type="importmap">
{
"imports": {
"@tiptap/core": "https://esm.sh/@tiptap/core@3.13.0",
"@tiptap/starter-kit": "https://esm.sh/@tiptap/starter-kit@3.13.0",
"@tiptap/extension-placeholder": "https://esm.sh/@tiptap/extension-placeholder@3.13.0",
"@tiptap/extension-image": "https://esm.sh/@tiptap/extension-image@3.13.0",
"@tiptap/extension-code-block-lowlight": "https://esm.sh/@tiptap/extension-code-block-lowlight@3.13.0",
"lowlight": "https://esm.sh/lowlight@3.3.0",
"highlight.js/lib/languages/": "https://esm.sh/highlight.js@11.11.0/es/languages/"
}
}
</script>
<link rel="stylesheet" href="/static/vditor/dist/index.css">
<script src="/static/vditor/dist/index.min.js"></script>
<script src="/static/js/common.js"></script>
<script type="module" src="/static/js/editor.js"></script>
</body>
</html>