feat: Markdown 工具栏编辑器与服务端预览
共用 md_editor 于发帖/改帖/评论,扩展 ComposeBodyToHTML,并提供 /compose/preview。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -187,6 +187,29 @@ func (d Deps) ComposeUpload(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"url": url})
|
||||
}
|
||||
|
||||
// ComposePreview 正文预览(与发帖同一套 ComposeBodyToHTML + 消毒)
|
||||
func (d Deps) ComposePreview(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.IsSigned() {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||||
return
|
||||
}
|
||||
if !ctx.CheckCSRF() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效请求"})
|
||||
return
|
||||
}
|
||||
content := c.PostForm("content")
|
||||
if content == "" {
|
||||
var body struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
_ = c.ShouldBindJSON(&body)
|
||||
content = body.Content
|
||||
}
|
||||
htmlBody := services.SanitizePostHTML(services.ComposeBodyToHTML(content))
|
||||
c.JSON(http.StatusOK, gin.H{"html": htmlBody})
|
||||
}
|
||||
|
||||
type composeForm struct {
|
||||
BoardID uint
|
||||
Title string
|
||||
|
||||
@@ -58,6 +58,7 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
g.GET("/compose", authMW.RequireAuth(), deps.ComposeGet)
|
||||
g.POST("/compose", authMW.RequireAuth(), deps.ComposePost)
|
||||
g.POST("/compose/upload", authMW.RequireAuth(), deps.ComposeUpload)
|
||||
g.POST("/compose/preview", authMW.RequireAuth(), deps.ComposePreview)
|
||||
g.GET("/admin/login", func(c *gin.Context) { c.Redirect(http.StatusFound, "/login?redirect=/admin/dashboard") })
|
||||
|
||||
admin := g.Group("/admin", authMW.RequireAuth(), authMW.RequireAdmin())
|
||||
|
||||
@@ -2,7 +2,6 @@ package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -510,7 +509,7 @@ func (d Deps) PostComment(c *gin.Context) {
|
||||
ctx.Redirect(fmt.Sprintf("/post/%d#comments", id))
|
||||
return
|
||||
}
|
||||
safe := "<p>" + html.EscapeString(content) + "</p>"
|
||||
safe := services.ComposeBodyToHTML(content)
|
||||
cm, err := d.Comment.Create(services.CommentCreateInput{
|
||||
PostID: id, UserID: ctx.UserID(), Content: safe,
|
||||
ReplyTo: replyTo, IsPrivate: isPrivate,
|
||||
@@ -815,7 +814,7 @@ func (d Deps) CommentEditGet(c *gin.Context) {
|
||||
PostID: postID,
|
||||
CommentID: cm.ID,
|
||||
Floor: cm.Floor,
|
||||
Content: commentHTMLToPlain(cm.Content),
|
||||
Content: services.HTMLToComposePlain(cm.Content),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -841,7 +840,7 @@ func (d Deps) CommentEditPost(c *gin.Context) {
|
||||
d.renderCommentEdit(ctx, "评论不能为空", postID, cm, plain)
|
||||
return
|
||||
}
|
||||
safe := "<p>" + html.EscapeString(plain) + "</p>"
|
||||
safe := services.ComposeBodyToHTML(plain)
|
||||
_, enteredPending, err := d.Comment.Update(ctx.UserID(), cm.ID, ctx.IsAdmin(), ctx.SkipsModeration(), safe)
|
||||
if err != nil {
|
||||
d.renderCommentEdit(ctx, err.Error(), postID, cm, plain)
|
||||
@@ -923,14 +922,6 @@ func (d Deps) renderCommentEdit(ctx *webctx.Context, errMsg string, postID uint,
|
||||
})
|
||||
}
|
||||
|
||||
// commentHTMLToPlain 发评存的是转义后的 <p>…</p>,编辑时还原为纯文本
|
||||
func commentHTMLToPlain(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
s = strings.TrimPrefix(s, "<p>")
|
||||
s = strings.TrimSuffix(s, "</p>")
|
||||
return html.UnescapeString(s)
|
||||
}
|
||||
|
||||
type postRevisionRow struct {
|
||||
ID uint
|
||||
EditorName string
|
||||
|
||||
Reference in New Issue
Block a user