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 [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 }