embed readme

This commit is contained in:
2026-05-29 14:09:05 +08:00
parent 1ee22b7155
commit a2d80d2660
8 changed files with 248 additions and 81 deletions

View File

@@ -24,9 +24,15 @@ func ChangelogPage(w http.ResponseWriter, r *http.Request) {
.changelog-panel { max-width: 840px; }
.changelog-content h1 { margin: 0 0 14px; font-size: 20px; line-height: 1.25; }
.changelog-content h2 { margin: 18px 0 8px; padding-top: 12px; border-top: 1px solid #333; font-size: 15px; line-height: 1.3; }
.changelog-content h3 { margin: 14px 0 6px; font-size: 13px; line-height: 1.3; }
.changelog-content ul { margin: 6px 0 12px; padding-left: 20px; }
.changelog-content li { margin: 4px 0; line-height: 1.45; }
.changelog-content li.nested { margin-left: 18px; }
.changelog-content p { margin: 8px 0; line-height: 1.5; }
.changelog-content code { font-family: Consolas, monospace; font-size: 0.95em; background: rgba(128,128,128,0.12); padding: 1px 4px; border-radius: 3px; }
.changelog-block { margin: 8px 0 18px; padding: 10px 12px; border: 1px solid #333; border-radius: 4px; background: rgba(128,128,128,0.08); }
.change-section { margin: 8px 0 4px; font-weight: 600; }
.changelog-block ul { margin: 4px 0 10px; }
</style>`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -34,6 +40,9 @@ func ChangelogPage(w http.ResponseWriter, r *http.Request) {
}
func readChangelog() string {
if embeddedReadme != "" {
return embeddedReadme
}
for _, start := range changelogSearchRoots() {
for _, candidate := range changelogCandidates(start) {
data, err := os.ReadFile(candidate)
@@ -72,6 +81,8 @@ func renderChangelogMarkdown(markdown string) string {
lines := strings.Split(strings.ReplaceAll(markdown, "\r\n", "\n"), "\n")
var out strings.Builder
inList := false
inCodeBlock := false
codeLines := []string{}
closeList := func() {
if inList {
out.WriteString("</ul>")
@@ -80,6 +91,22 @@ func renderChangelogMarkdown(markdown string) string {
}
for _, rawLine := range lines {
if strings.TrimSpace(rawLine) == "```" {
closeList()
if inCodeBlock {
out.WriteString(renderChangelogBlock(codeLines))
codeLines = []string{}
inCodeBlock = false
} else {
inCodeBlock = true
}
continue
}
if inCodeBlock {
codeLines = append(codeLines, rawLine)
continue
}
line := strings.TrimSpace(rawLine)
if line == "" {
closeList()
@@ -87,17 +114,22 @@ func renderChangelogMarkdown(markdown string) string {
}
if strings.HasPrefix(line, "### ") {
closeList()
out.WriteString("<h2>" + html.EscapeString(strings.TrimSpace(line[4:])) + "</h2>")
out.WriteString("<h2>" + renderInlineMarkdown(strings.TrimSpace(line[4:])) + "</h2>")
continue
}
if strings.HasPrefix(line, "## ") {
closeList()
out.WriteString("<h2>" + html.EscapeString(strings.TrimSpace(line[3:])) + "</h2>")
out.WriteString("<h2>" + renderInlineMarkdown(strings.TrimSpace(line[3:])) + "</h2>")
continue
}
if strings.HasPrefix(line, "# ") {
closeList()
out.WriteString("<h1>" + html.EscapeString(strings.TrimSpace(line[2:])) + "</h1>")
out.WriteString("<h1>" + renderInlineMarkdown(strings.TrimSpace(line[2:])) + "</h1>")
continue
}
if strings.HasPrefix(line, "**") && strings.HasSuffix(line, "**") && len(line) > 4 {
closeList()
out.WriteString("<h2>" + html.EscapeString(strings.TrimSuffix(strings.TrimPrefix(line, "**"), "**")) + "</h2>")
continue
}
if strings.HasPrefix(line, "- ") {
@@ -105,12 +137,93 @@ func renderChangelogMarkdown(markdown string) string {
out.WriteString("<ul>")
inList = true
}
out.WriteString("<li>" + html.EscapeString(strings.TrimSpace(line[2:])) + "</li>")
out.WriteString("<li>" + renderInlineMarkdown(strings.TrimSpace(line[2:])) + "</li>")
continue
}
closeList()
out.WriteString("<p>" + html.EscapeString(line) + "</p>")
out.WriteString("<p>" + renderInlineMarkdown(line) + "</p>")
}
if inCodeBlock {
out.WriteString(renderChangelogBlock(codeLines))
}
closeList()
return out.String()
}
func renderChangelogBlock(lines []string) string {
var out strings.Builder
inList := false
closeList := func() {
if inList {
out.WriteString("</ul>")
inList = false
}
}
out.WriteString(`<div class="changelog-block">`)
for _, rawLine := range lines {
line := strings.TrimSpace(rawLine)
if line == "" {
closeList()
continue
}
if strings.HasPrefix(line, "- ") {
if !inList {
out.WriteString("<ul>")
inList = true
}
className := ""
if leadingSpaceCount(rawLine) >= 8 {
className = ` class="nested"`
}
out.WriteString("<li" + className + ">" + renderInlineMarkdown(strings.TrimSpace(line[2:])) + "</li>")
continue
}
closeList()
out.WriteString(`<div class="change-section">` + renderInlineMarkdown(line) + `</div>`)
}
closeList()
out.WriteString("</div>")
return out.String()
}
func renderInlineMarkdown(value string) string {
escaped := html.EscapeString(value)
escaped = replaceDelimited(escaped, "**", "strong")
escaped = replaceDelimited(escaped, "`", "code")
return escaped
}
func replaceDelimited(value, marker, tag string) string {
var out strings.Builder
for {
start := strings.Index(value, marker)
if start < 0 {
out.WriteString(value)
break
}
end := strings.Index(value[start+len(marker):], marker)
if end < 0 {
out.WriteString(value)
break
}
end += start + len(marker)
out.WriteString(value[:start])
out.WriteString("<" + tag + ">")
out.WriteString(value[start+len(marker) : end])
out.WriteString("</" + tag + ">")
value = value[end+len(marker):]
}
return out.String()
}
func leadingSpaceCount(value string) int {
count := 0
for _, ch := range value {
if ch != ' ' {
break
}
count++
}
return count
}