feat: bootstrap foundation application

This commit is contained in:
2026-04-29 00:26:58 -04:00
parent 8e6646499f
commit 4a72e1e030
53 changed files with 4443 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
package markdown
import (
"bytes"
"fmt"
"html/template"
"regexp"
"slices"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
)
var (
tagPattern = regexp.MustCompile(`(^|[\s(])#([a-zA-Z0-9_-]+)\b`)
)
type Result struct {
HTML template.HTML
Title string
Tags []string
}
type Renderer struct {
markdown goldmark.Markdown
}
func NewRenderer() *Renderer {
return &Renderer{
markdown: goldmark.New(
goldmark.WithExtensions(
extension.GFM,
extension.Footnote,
extension.DefinitionList,
extension.Table,
extension.Strikethrough,
extension.TaskList,
extension.Typographer,
),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(),
),
goldmark.WithRendererOptions(
html.WithHardWraps(),
html.WithXHTML(),
),
),
}
}
func (r *Renderer) Render(content []byte) (Result, error) {
title := extractTitle(string(content))
tags := extractTags(string(content))
prepared := preprocess(string(content))
var output bytes.Buffer
if err := r.markdown.Convert([]byte(prepared), &output); err != nil {
return Result{}, fmt.Errorf("render markdown: %w", err)
}
return Result{
HTML: template.HTML(output.String()),
Title: title,
Tags: tags,
}, nil
}
func preprocess(input string) string {
lines := strings.Split(input, "\n")
for index, line := range lines {
line = rewriteWikiLinks(line)
line = normalizeAdmonition(line)
lines[index] = line
}
return strings.Join(lines, "\n")
}
func rewriteWikiLinks(line string) string {
var builder strings.Builder
remaining := line
for {
start := strings.Index(remaining, "[[")
if start == -1 {
builder.WriteString(remaining)
return builder.String()
}
end := strings.Index(remaining[start+2:], "]]")
if end == -1 {
builder.WriteString(remaining)
return builder.String()
}
builder.WriteString(remaining[:start])
label := strings.TrimSpace(remaining[start+2 : start+2+end])
if label == "" {
builder.WriteString(remaining[start : start+2+end+2])
} else {
builder.WriteString(fmt.Sprintf("[%s](/docs/%s)", label, slugify(label)))
}
remaining = remaining[start+2+end+2:]
}
}
func normalizeAdmonition(line string) string {
switch {
case strings.HasPrefix(line, "> [!NOTE]"):
return strings.Replace(line, "> [!NOTE]", "> **Note:**", 1)
case strings.HasPrefix(line, "> [!TIP]"):
return strings.Replace(line, "> [!TIP]", "> **Tip:**", 1)
case strings.HasPrefix(line, "> [!WARNING]"):
return strings.Replace(line, "> [!WARNING]", "> **Warning:**", 1)
default:
return line
}
}
func extractTitle(content string) string {
for _, line := range strings.Split(content, "\n") {
if strings.HasPrefix(line, "# ") {
return strings.TrimSpace(strings.TrimPrefix(line, "# "))
}
}
return "Untitled"
}
func extractTags(content string) []string {
matches := tagPattern.FindAllStringSubmatch(content, -1)
tags := make([]string, 0, len(matches))
seen := make(map[string]struct{}, len(matches))
for _, match := range matches {
if len(match) < 3 {
continue
}
tag := match[2]
if _, exists := seen[tag]; exists {
continue
}
seen[tag] = struct{}{}
tags = append(tags, tag)
}
slices.Sort(tags)
return tags
}
func slugify(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
var builder strings.Builder
lastDash := false
for _, r := range value {
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
builder.WriteRune(r)
lastDash = false
default:
if !lastDash {
builder.WriteByte('-')
lastDash = true
}
}
}
return strings.Trim(builder.String(), "-")
}