// SPDX-FileCopyrightText: Amolith // // SPDX-License-Identifier: AGPL-3.0-or-later package main import ( "html" "regexp" "strings" "github.com/microcosm-cc/bluemonday" ) var numberedListRegex = regexp.MustCompile(`^\d+\.\s`) func formatBody(body string) (string, error) { p := bluemonday.UGCPolicy() sanitized := html.UnescapeString(p.Sanitize(body)) lines := strings.Split(sanitized, "\n") var result []string var plainTextBuffer []string flushPlainText := func() { if len(plainTextBuffer) > 0 { joined := strings.Join(plainTextBuffer, " ") wrapped := wordWrap(joined, 72) result = append(result, wrapped) plainTextBuffer = nil } } for _, line := range lines { // Preserve indented lines (4+ spaces or tab) as preformatted code if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { result = append(result, line) continue } trimmed := strings.TrimSpace(line) if trimmed == "" { flushPlainText() result = append(result, "") continue } if strings.HasPrefix(trimmed, "- ") || strings.HasPrefix(trimmed, "* ") { flushPlainText() marker := trimmed[:2] content := trimmed[2:] wrapped := wrapWithHangingIndent(marker, " ", content, 72) result = append(result, wrapped) continue } if numberedListRegex.MatchString(trimmed) { flushPlainText() parts := strings.SplitN(trimmed, " ", 2) marker := parts[0] + " " content := "" if len(parts) > 1 { content = parts[1] } indent := strings.Repeat(" ", len(marker)) wrapped := wrapWithHangingIndent(marker, indent, content, 72) result = append(result, wrapped) continue } plainTextBuffer = append(plainTextBuffer, trimmed) } flushPlainText() return strings.Join(result, "\n"), nil } func wrapWithHangingIndent(firstPrefix, contPrefix, text string, width int) string { firstWidth := width - len(firstPrefix) contWidth := width - len(contPrefix) words := strings.Fields(text) if len(words) == 0 { return firstPrefix } var lines []string var currentLine strings.Builder var currentWidth int isFirstLine := true for _, word := range words { wordLen := len(word) maxWidth := firstWidth if !isFirstLine { maxWidth = contWidth } if currentLine.Len() == 0 { currentLine.WriteString(word) currentWidth = wordLen } else if currentWidth+1+wordLen <= maxWidth { currentLine.WriteString(" ") currentLine.WriteString(word) currentWidth += 1 + wordLen } else { if isFirstLine { lines = append(lines, firstPrefix+currentLine.String()) isFirstLine = false } else { lines = append(lines, contPrefix+currentLine.String()) } currentLine.Reset() currentLine.WriteString(word) currentWidth = wordLen } } if currentLine.Len() > 0 { if isFirstLine { lines = append(lines, firstPrefix+currentLine.String()) } else { lines = append(lines, contPrefix+currentLine.String()) } } return strings.Join(lines, "\n") } func wordWrap(text string, width int) string { words := strings.Fields(text) if len(words) == 0 { return "" } var result strings.Builder var currentLine strings.Builder var currentWidth int for _, word := range words { wordLen := len(word) if currentLine.Len() == 0 { currentLine.WriteString(word) currentWidth = wordLen } else if currentWidth+1+wordLen <= width { currentLine.WriteString(" ") currentLine.WriteString(word) currentWidth += 1 + wordLen } else { if result.Len() > 0 { result.WriteString("\n") } result.WriteString(currentLine.String()) currentLine.Reset() currentLine.WriteString(word) currentWidth = wordLen } } if currentLine.Len() > 0 { if result.Len() > 0 { result.WriteString("\n") } result.WriteString(currentLine.String()) } return result.String() }