A deployable markdown editor that connects with your self hosted files and lets you edit in a beautiful interface
1package markdown
2
3import (
4 "bytes"
5 "fmt"
6 "strings"
7
8 "gopkg.in/yaml.v3"
9)
10
11// ParsedContent represents a markdown file with frontmatter
12type ParsedContent struct {
13 Frontmatter map[string]interface{}
14 Content string
15}
16
17// Parse extracts YAML frontmatter and markdown content from a file
18func Parse(input string) (*ParsedContent, error) {
19 // Check if the file starts with frontmatter delimiter
20 if !strings.HasPrefix(input, "---\n") && !strings.HasPrefix(input, "---\r\n") {
21 // No frontmatter, return entire content as markdown
22 return &ParsedContent{
23 Frontmatter: make(map[string]interface{}),
24 Content: input,
25 }, nil
26 }
27
28 // Find the end of frontmatter
29 lines := strings.Split(input, "\n")
30 endIdx := -1
31
32 for i := 1; i < len(lines); i++ {
33 trimmed := strings.TrimSpace(lines[i])
34 if trimmed == "---" {
35 endIdx = i
36 break
37 }
38 }
39
40 // If we didn't find the closing delimiter, treat as no frontmatter
41 if endIdx == -1 {
42 return &ParsedContent{
43 Frontmatter: make(map[string]interface{}),
44 Content: input,
45 }, nil
46 }
47
48 // Extract frontmatter YAML (between the delimiters)
49 frontmatterLines := lines[1:endIdx]
50 frontmatterYAML := strings.Join(frontmatterLines, "\n")
51
52 // Parse YAML
53 var frontmatter map[string]interface{}
54 if err := yaml.Unmarshal([]byte(frontmatterYAML), &frontmatter); err != nil {
55 return nil, fmt.Errorf("failed to parse frontmatter YAML: %w", err)
56 }
57
58 // Extract content (everything after the closing delimiter)
59 contentLines := lines[endIdx+1:]
60 content := strings.Join(contentLines, "\n")
61 // Trim leading newline if present
62 content = strings.TrimPrefix(content, "\n")
63
64 return &ParsedContent{
65 Frontmatter: frontmatter,
66 Content: content,
67 }, nil
68}
69
70// Serialize combines frontmatter and content back into a single string
71func Serialize(frontmatter map[string]interface{}, content string) (string, error) {
72 // If no frontmatter, return just the content
73 if len(frontmatter) == 0 {
74 return content, nil
75 }
76
77 // Marshal frontmatter to YAML
78 var buf bytes.Buffer
79 encoder := yaml.NewEncoder(&buf)
80 encoder.SetIndent(2)
81
82 if err := encoder.Encode(frontmatter); err != nil {
83 return "", fmt.Errorf("failed to marshal frontmatter: %w", err)
84 }
85
86 if err := encoder.Close(); err != nil {
87 return "", fmt.Errorf("failed to close encoder: %w", err)
88 }
89
90 // Combine with delimiters
91 yamlStr := buf.String()
92 // Remove trailing newline from YAML output
93 yamlStr = strings.TrimSuffix(yamlStr, "\n")
94
95 result := fmt.Sprintf("---\n%s\n---\n%s", yamlStr, content)
96 return result, nil
97}