Backend implementation: - Add goldmark dependency for markdown processing - Create MarkdownProcessor with minimal config (bold, italic, links only) - Update content injector with HTML injection capabilities - Add injectHTMLContent() for safe DOM manipulation - Server now converts **bold**, *italic*, [links](url) to HTML during enhancement Frontend alignment: - Restrict marked.js to match server capabilities - Disable unsupported features (headings, lists, code blocks, tables) - Update turndown rules to prevent unsupported markdown generation - Frontend editor preview now matches server output exactly Server as source of truth: - Build-time markdown→HTML conversion during enhancement - Zero runtime overhead for end users - Consistent formatting between editor preview and final output - Raw markdown stored in database, HTML served to visitors Tested features: - **bold** → <strong>bold</strong> ✅ - *italic* → <em>italic</em> ✅ - [text](url) → <a href="url">text</a> ✅
68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
package content
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
|
|
"github.com/yuin/goldmark"
|
|
"github.com/yuin/goldmark/parser"
|
|
"github.com/yuin/goldmark/renderer/html"
|
|
"github.com/yuin/goldmark/util"
|
|
)
|
|
|
|
// MarkdownProcessor handles minimal markdown processing
|
|
// Supports only: **bold**, *italic*, and [link](url)
|
|
type MarkdownProcessor struct {
|
|
parser goldmark.Markdown
|
|
}
|
|
|
|
// NewMarkdownProcessor creates a new markdown processor with minimal configuration
|
|
func NewMarkdownProcessor() *MarkdownProcessor {
|
|
// Configure goldmark to only support basic inline formatting
|
|
md := goldmark.New(
|
|
goldmark.WithParserOptions(
|
|
parser.WithInlineParsers(
|
|
// Bold (**text**) and italic (*text*) - same parser handles both
|
|
util.Prioritized(parser.NewEmphasisParser(), 500),
|
|
|
|
// Links [text](url)
|
|
util.Prioritized(parser.NewLinkParser(), 600),
|
|
),
|
|
// Disable all block parsers except paragraph (no headings, lists, etc.)
|
|
parser.WithBlockParsers(
|
|
util.Prioritized(parser.NewParagraphParser(), 200),
|
|
),
|
|
),
|
|
goldmark.WithRendererOptions(
|
|
html.WithXHTML(), // <br /> instead of <br>
|
|
html.WithHardWraps(), // Line breaks become <br />
|
|
html.WithUnsafe(), // Allow existing HTML to pass through
|
|
),
|
|
)
|
|
|
|
return &MarkdownProcessor{parser: md}
|
|
}
|
|
|
|
// ToHTML converts markdown string to HTML
|
|
func (mp *MarkdownProcessor) ToHTML(markdown string) (string, error) {
|
|
if markdown == "" {
|
|
return "", nil
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := mp.parser.Convert([]byte(markdown), &buf); err != nil {
|
|
log.Printf("Markdown conversion failed: %v", err)
|
|
return "", err
|
|
}
|
|
|
|
html := buf.String()
|
|
|
|
// Clean up goldmark's paragraph wrapping - we want inline content
|
|
// Remove <p> and </p> tags if the content is wrapped in a single paragraph
|
|
if len(html) > 7 && html[:3] == "<p>" && html[len(html)-4:] == "</p>" {
|
|
html = html[3 : len(html)-4]
|
|
}
|
|
|
|
return html, nil
|
|
}
|