This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/templates/MetaLab-2026/html/posts/new.html
Victor_Jay 174d715e50 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 模式渲染已有内容
2026-05-27 18:35:13 +08:00

88 lines
5.2 KiB
HTML

{{template "layout/header.html" .}}
<body>
{{template "layout/nav.html" .}}
<div class="container editor-container">
<h1 class="editor-title">{{if .Post}}编辑帖子{{else}}撰写帖子{{end}}</h1>
<form id="postForm" class="post-form">
<input type="hidden" id="postId" value="{{if .Post}}{{.Post.ID}}{{end}}">
<div class="form-group">
<label for="postTitle">标题</label>
<input type="text" id="postTitle" class="form-input" value="{{if .Post}}{{.Post.Title}}{{end}}" maxlength="200" required>
</div>
{{/* 模式切换标签 */}}
<div class="editor-modes">
<button type="button" class="mode-tab active" data-mode="wysiwyg">所见即所得</button>
<button type="button" class="mode-tab" data-mode="source">源码</button>
<button type="button" class="mode-tab" data-mode="split">分屏预览</button>
</div>
{{/* 工具条 */}}
<div class="editor-toolbar" id="toolbar">
<button type="button" class="tb-btn" data-action="bold" title="加粗 (Ctrl+B)">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"/></svg>
</button>
<button type="button" class="tb-btn" data-action="italic" title="斜体 (Ctrl+I)">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"/></svg>
</button>
<span class="tb-sep"></span>
<button type="button" class="tb-btn" data-action="h2" title="标题">H2</button>
<button type="button" class="tb-btn" data-action="h3" title="子标题">H3</button>
<span class="tb-sep"></span>
<button type="button" class="tb-btn" data-action="link" title="链接">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>
</button>
<button type="button" class="tb-btn" data-action="image" title="图片">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
</button>
<span class="tb-sep"></span>
<button type="button" class="tb-btn" data-action="quote" title="引用">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/></svg>
</button>
<button type="button" class="tb-btn" data-action="code" title="行内代码">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.4 16.6 4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0 4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
</button>
<button type="button" class="tb-btn" data-action="pre" title="代码块">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 3h18v18H3V3zm2 2v14h14V5H5zm4 2h6v2H9V7zm0 4h6v2H9v-2z"/></svg>
</button>
<span class="tb-sep"></span>
<button type="button" class="tb-btn" data-action="ul" title="无序列表">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/></svg>
</button>
<button type="button" class="tb-btn" data-action="ol" title="有序列表">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"/></svg>
</button>
<button type="button" class="tb-btn" data-action="hr" title="分割线"></button>
</div>
{{/* 源码编辑器 */}}
<div class="editor-body">
<textarea id="postBody" class="form-textarea" rows="20" required
placeholder="使用 Markdown 编写正文...">{{if .Post}}{{.Post.Body}}{{end}}</textarea>
{{/* WYSIWYG 编辑器 */}}
<div id="wysiwygEditor" class="wysiwyg-editor" contenteditable="true" style="display:none;"></div>
{{/* 分屏预览 */}}
<div id="splitPreview" class="split-preview" style="display:none;">
<div class="split-preview-content post-detail-body"></div>
</div>
</div>
<div class="editor-actions">
<button type="submit" class="btn btn-primary">{{if .Post}}保存修改{{else}}发布帖子{{end}}</button>
</div>
</form>
</div>
{{template "layout/footer.html" .}}
{{if .ExtraJS}}<script src="{{.ExtraJS}}"></script>{{end}}
<script src="/static/js/common.js"></script>
</body>
</html>