首页右栏接入单页入口与公告置顶/全部页;Markdown 编辑器支持可视化时间线与 Git 导入;隐藏块改为 [hide]/[timeline] 命名闭合,并修复单页/公告 GORM 更新只改 updated_at 的问题。 Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
3.4 KiB
Go
159 lines
3.4 KiB
Go
package markdown
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// RewriteLegacyDirectives 将旧式 :::hide / :::timeline / 裸 ::: 改写为行级 BBCode。
|
|
// 幂等:已是 BBCode 的内容原样返回(若无旧标记)。代码围栏内不改写。
|
|
func RewriteLegacyDirectives(content string) string {
|
|
if content == "" {
|
|
return content
|
|
}
|
|
if !strings.Contains(content, ":::") {
|
|
return content
|
|
}
|
|
lines := splitLines(content)
|
|
var out []string
|
|
inCode := false
|
|
i := 0
|
|
for i < len(lines) {
|
|
trimmed := strings.TrimSpace(lines[i])
|
|
if strings.HasPrefix(trimmed, "```") {
|
|
inCode = !inCode
|
|
out = append(out, lines[i])
|
|
i++
|
|
continue
|
|
}
|
|
if inCode {
|
|
out = append(out, lines[i])
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if kind, pts, pwd, locked, ok := parseLegacyHideOpen(trimmed); ok {
|
|
bodyLines := make([]string, 0)
|
|
j := i + 1
|
|
innerCode := false
|
|
found := false
|
|
for j < len(lines) {
|
|
inner := strings.TrimSpace(lines[j])
|
|
if strings.HasPrefix(inner, "```") {
|
|
innerCode = !innerCode
|
|
bodyLines = append(bodyLines, lines[j])
|
|
j++
|
|
continue
|
|
}
|
|
if innerCode {
|
|
bodyLines = append(bodyLines, lines[j])
|
|
j++
|
|
continue
|
|
}
|
|
if inner == ":::" {
|
|
found = true
|
|
break
|
|
}
|
|
bodyLines = append(bodyLines, lines[j])
|
|
j++
|
|
}
|
|
out = append(out, openMarkerLine(kind, pts, pwd, locked))
|
|
out = append(out, bodyLines...)
|
|
if found {
|
|
out = append(out, "[/hide]")
|
|
i = j + 1
|
|
} else {
|
|
// 未闭合:仍写出已转换开标记与正文,避免丢内容
|
|
i = j
|
|
}
|
|
continue
|
|
}
|
|
|
|
if trimmed == ":::timeline" || strings.HasPrefix(trimmed, ":::timeline ") {
|
|
bodyLines := make([]string, 0)
|
|
j := i + 1
|
|
innerCode := false
|
|
found := false
|
|
for j < len(lines) {
|
|
inner := strings.TrimSpace(lines[j])
|
|
if strings.HasPrefix(inner, "```") {
|
|
innerCode = !innerCode
|
|
bodyLines = append(bodyLines, lines[j])
|
|
j++
|
|
continue
|
|
}
|
|
if innerCode {
|
|
bodyLines = append(bodyLines, lines[j])
|
|
j++
|
|
continue
|
|
}
|
|
if inner == ":::" {
|
|
found = true
|
|
break
|
|
}
|
|
bodyLines = append(bodyLines, lines[j])
|
|
j++
|
|
}
|
|
out = append(out, "[timeline]")
|
|
out = append(out, bodyLines...)
|
|
if found {
|
|
out = append(out, "[/timeline]")
|
|
i = j + 1
|
|
} else {
|
|
i = j
|
|
}
|
|
continue
|
|
}
|
|
|
|
out = append(out, lines[i])
|
|
i++
|
|
}
|
|
return strings.Join(out, "\n")
|
|
}
|
|
|
|
// parseLegacyHideOpen 解析旧式 :::hide <kind> [arg] [locked]
|
|
func parseLegacyHideOpen(trimmed string) (kind string, points int, password string, locked bool, ok bool) {
|
|
if !strings.HasPrefix(trimmed, ":::hide") {
|
|
return "", 0, "", false, false
|
|
}
|
|
rest := strings.TrimSpace(trimmed[len(":::hide"):])
|
|
if rest == "" {
|
|
return "", 0, "", false, false
|
|
}
|
|
parts := strings.Fields(rest)
|
|
if len(parts) == 0 {
|
|
return "", 0, "", false, false
|
|
}
|
|
kind = parts[0]
|
|
switch kind {
|
|
case HideKindLogin, HideKindReply, HideKindPoints, HideKindPassword:
|
|
default:
|
|
return "", 0, "", false, false
|
|
}
|
|
idx := 1
|
|
if kind == HideKindPoints {
|
|
if idx >= len(parts) {
|
|
return "", 0, "", false, false
|
|
}
|
|
n, err := strconv.Atoi(parts[idx])
|
|
if err != nil {
|
|
return "", 0, "", false, false
|
|
}
|
|
points = n
|
|
idx++
|
|
} else if kind == HideKindPassword {
|
|
if idx < len(parts) && parts[idx] != "locked" {
|
|
password = parts[idx]
|
|
idx++
|
|
}
|
|
}
|
|
for ; idx < len(parts); idx++ {
|
|
if parts[idx] == "locked" {
|
|
locked = true
|
|
} else {
|
|
return "", 0, "", false, false
|
|
}
|
|
}
|
|
return kind, points, password, locked, true
|
|
}
|