package renderer import ( "fmt" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/util" ) type ConfluenceGHAlertsBlockQuoteRenderer struct { html.Config LevelMap BlockQuoteLevelMap BlockQuoteNode ast.Node } // NewConfluenceGHAlertsBlockQuoteRenderer creates a new instance of the renderer for GitHub Alerts func NewConfluenceGHAlertsBlockQuoteRenderer(opts ...html.Option) renderer.NodeRenderer { return &ConfluenceGHAlertsBlockQuoteRenderer{ Config: html.NewConfig(), LevelMap: nil, BlockQuoteNode: nil, } } // RegisterFuncs implements NodeRenderer.RegisterFuncs func (r *ConfluenceGHAlertsBlockQuoteRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(ast.KindBlockquote, r.renderBlockQuote) } // Define GitHub Alert to Confluence macro mapping func (r *ConfluenceGHAlertsBlockQuoteRenderer) getConfluenceMacroName(alertType string) string { switch alertType { case "note": return "info" case "tip": return "tip" case "important": return "info" case "warning": return "note" case "caution": return "warning" default: return "info" } } func (r *ConfluenceGHAlertsBlockQuoteRenderer) renderBlockQuote(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if r.LevelMap == nil { r.LevelMap = GenerateBlockQuoteLevel(node) } // Check if this blockquote has been transformed by the GHAlerts transformer if alertTypeBytes, hasAttribute := node.Attribute([]byte("gh-alert-type")); hasAttribute && alertTypeBytes != nil { if alertTypeStr, ok := alertTypeBytes.([]byte); ok { return r.renderGHAlert(writer, source, node, entering, string(alertTypeStr)) } } // Fall back to legacy blockquote rendering for non-GitHub Alert blockquotes return r.renderLegacyBlockQuote(writer, source, node, entering) } func (r *ConfluenceGHAlertsBlockQuoteRenderer) renderGHAlert(writer util.BufWriter, source []byte, node ast.Node, entering bool, alertType string) (ast.WalkStatus, error) { quoteLevel := r.LevelMap.Level(node) if quoteLevel == 0 && entering { r.BlockQuoteNode = node macroName := r.getConfluenceMacroName(alertType) prefix := fmt.Sprintf("true\n", macroName) if _, err := writer.Write([]byte(prefix)); err != nil { return ast.WalkStop, err } return ast.WalkContinue, nil } if quoteLevel == 0 && !entering && node == r.BlockQuoteNode { suffix := "\n" if _, err := writer.Write([]byte(suffix)); err != nil { return ast.WalkStop, err } return ast.WalkContinue, nil } // For nested blockquotes or continuing the content, use default rendering if quoteLevel > 0 { if entering { if _, err := writer.WriteString("
\n"); err != nil { return ast.WalkStop, err } } else { if _, err := writer.WriteString("
\n"); err != nil { return ast.WalkStop, err } } } else if quoteLevel == 0 && alertType == "" { // This handles the fallback case for non-alert blockquotes if called accidentally if entering { if _, err := writer.WriteString("
\n"); err != nil { return ast.WalkStop, err } } else { if _, err := writer.WriteString("
\n"); err != nil { return ast.WalkStop, err } } } return ast.WalkContinue, nil } func (r *ConfluenceGHAlertsBlockQuoteRenderer) renderLegacyBlockQuote(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { // Legacy blockquote handling (same as original ParseBlockQuoteType logic) quoteType := ParseBlockQuoteType(node, source) quoteLevel := r.LevelMap.Level(node) if quoteLevel == 0 && entering && quoteType != None { r.BlockQuoteNode = node prefix := fmt.Sprintf("true\n", quoteType) if _, err := writer.Write([]byte(prefix)); err != nil { return ast.WalkStop, err } return ast.WalkContinue, nil } if quoteLevel == 0 && !entering && node == r.BlockQuoteNode { suffix := "\n" if _, err := writer.Write([]byte(suffix)); err != nil { return ast.WalkStop, err } return ast.WalkContinue, nil } // For nested blockquotes or regular blockquotes (at root level with no macro type) if quoteLevel > 0 || (quoteLevel == 0 && quoteType == None) { if entering { if _, err := writer.WriteString("
\n"); err != nil { return ast.WalkStop, err } } else { if _, err := writer.WriteString("
\n"); err != nil { return ast.WalkStop, err } } } return ast.WalkContinue, nil }