Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,18 +272,43 @@ More details at Confluence [Code Block Macro](https://confluence.atlassian.com/d

### Block Quotes

Block Quotes are converted to Confluence Info/Warn/Note box when the following conditions are met
Mark now supports GitHub Alerts to create highlighted information boxes using Github Flavored Markdown syntax!
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor spelling inconsistency: "Github" should be "GitHub" (with capital H) for consistency with the company's branding.

Suggested change
Mark now supports GitHub Alerts to create highlighted information boxes using Github Flavored Markdown syntax!
Mark now supports GitHub Alerts to create highlighted information boxes using GitHub Flavored Markdown syntax!

Copilot uses AI. Check for mistakes.

#### GitHub Alerts Support

You can now use GitHub-style alert syntax in your markdown, and Mark will automatically convert them to Confluence macros:

```markdown
> [!NOTE]
> This creates a blue info box - perfect for helpful information!

> [!TIP]
> This creates a green tip box - great for best practices and suggestions!

> [!IMPORTANT]
> This creates a blue info box - ideal for critical information!

> [!WARNING]
> This creates a yellow warning box - use for important warnings!

> [!CAUTION]
> This creates a red warning box - perfect for dangerous situations!
```

#### Technical Details

Block Quotes are converted to Confluence Info/Warn/Note box when the following conditions are met:

1. The BlockQuote is on the root level of the document (not nested)
1. The first line of the BlockQuote contains one of the following patterns `Info/Warn/Note` or [Github MD Alerts style](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) `[!NOTE]/[!TIP]/[!IMPORTANT]/[!WARNING]/[!CAUTION]`

| Github Alerts | Confluence |
| --- | --- |
| Tip (green lightbulb) | Tip (green checkmark in circle) |
| Note (blue I in circle) | Info (blue I in circle) |
| Important (purple exclamation mark in speech bubble) | Info (blue I in circle) |
| Warning (yellow exclamation mark in triangle) | Note (yellow exclamation mark in triangle) |
| Caution (red exclamation mark in hexagon) | Warning (red exclamation mark in hexagon) |
2. The first line of the BlockQuote contains one of the following patterns `Info/Warn/Note` or [Github MD Alerts style](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) `[!NOTE]/[!TIP]/[!IMPORTANT]/[!WARNING]/[!CAUTION]`

| Github Alerts | Confluence | Description |
Comment on lines +303 to +305
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor spelling inconsistency: "Github" should be "GitHub" (with capital H) for consistency with the company's branding. This appears in the heading "Github MD Alerts style" and the table headers.

Suggested change
2. The first line of the BlockQuote contains one of the following patterns `Info/Warn/Note` or [Github MD Alerts style](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) `[!NOTE]/[!TIP]/[!IMPORTANT]/[!WARNING]/[!CAUTION]`
| Github Alerts | Confluence | Description |
2. The first line of the BlockQuote contains one of the following patterns `Info/Warn/Note` or [GitHub MD Alerts style](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) `[!NOTE]/[!TIP]/[!IMPORTANT]/[!WARNING]/[!CAUTION]`
| GitHub Alerts | Confluence | Description |

Copilot uses AI. Check for mistakes.
| --------------- | ------------ | ------------- |
| `[!TIP]` (green lightbulb) | Tip (green checkmark in circle) | Helpful suggestions and best practices |
| `[!NOTE]` (blue I in circle) | Info (blue I in circle) | General information and notes |
| `[!IMPORTANT]` (purple exclamation mark in speech bubble) | Info (blue I in circle) | Critical information that needs attention |
| `[!WARNING]` (yellow exclamation mark in triangle) | Note (yellow exclamation mark in triangle) | Important warnings and cautions |
| `[!CAUTION]` (red exclamation mark in hexagon) | Warning (red exclamation mark in hexagon) | Dangerous situations requiring immediate attention |

In any other case the default behaviour will be resumed and html `<blockquote>` tag will be used

Expand Down
185 changes: 170 additions & 15 deletions markdown/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
cparser "github.com/kovetskiy/mark/parser"
crenderer "github.com/kovetskiy/mark/renderer"
"github.com/kovetskiy/mark/stdlib"
ctransformer "github.com/kovetskiy/mark/transformer"
"github.com/kovetskiy/mark/types"
"github.com/reconquest/pkg/log"
mkDocsParser "github.com/stefanfritsch/goldmark-admonitions"
Expand All @@ -20,18 +21,19 @@ import (
"github.com/yuin/goldmark/util"
)

// Renderer renders anchor [Node]s.
type ConfluenceExtension struct {
// ConfluenceLegacyExtension is the original goldmark extension without GitHub Alerts support
// This extension is preserved for backward compatibility and testing purposes
type ConfluenceLegacyExtension struct {
html.Config
Stdlib *stdlib.Lib
Path string
MarkConfig types.MarkConfig
Attachments []attachment.Attachment
}

// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
func NewConfluenceExtension(stdlib *stdlib.Lib, path string, cfg types.MarkConfig) *ConfluenceExtension {
return &ConfluenceExtension{
// NewConfluenceLegacyExtension creates a new instance of the legacy ConfluenceRenderer
func NewConfluenceLegacyExtension(stdlib *stdlib.Lib, path string, cfg types.MarkConfig) *ConfluenceLegacyExtension {
return &ConfluenceLegacyExtension{
Config: html.NewConfig(),
Stdlib: stdlib,
Path: path,
Expand All @@ -40,14 +42,14 @@ func NewConfluenceExtension(stdlib *stdlib.Lib, path string, cfg types.MarkConfi
}
}

func (c *ConfluenceExtension) Attach(a attachment.Attachment) {
func (c *ConfluenceLegacyExtension) Attach(a attachment.Attachment) {
c.Attachments = append(c.Attachments, a)
}

func (c *ConfluenceExtension) Extend(m goldmark.Markdown) {
func (c *ConfluenceLegacyExtension) Extend(m goldmark.Markdown) {

m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(crenderer.NewConfluenceTextRenderer(c.MarkConfig.StripNewlines), 100),
util.Prioritized(crenderer.NewConfluenceTextLegacyRenderer(c.MarkConfig.StripNewlines), 100),
util.Prioritized(crenderer.NewConfluenceBlockQuoteRenderer(), 100),
util.Prioritized(crenderer.NewConfluenceCodeBlockRenderer(c.Stdlib, c.Path), 100),
util.Prioritized(crenderer.NewConfluenceFencedCodeBlockRenderer(c.Stdlib, c, c.MarkConfig), 100),
Expand Down Expand Up @@ -89,10 +91,10 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) {
))
}

func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
log.Tracef(nil, "rendering markdown:\n%s", string(markdown))

confluenceExtension := NewConfluenceExtension(stdlib, path, cfg)
// compileMarkdownWithExtension is a shared helper to eliminate code duplication
// between different compilation approaches
func compileMarkdownWithExtension(markdown []byte, ext goldmark.Extender, logMessage string) (string, []attachment.Attachment) {
log.Tracef(nil, logMessage, string(markdown))

converter := goldmark.New(
goldmark.WithExtensions(
Expand All @@ -101,7 +103,7 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, cfg types
extension.NewTable(
extension.WithTableCellAlignMethod(extension.TableCellAlignStyle),
),
confluenceExtension,
ext,
extension.GFM,
),
goldmark.WithParserOptions(
Expand All @@ -122,8 +124,161 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, cfg types
}

html := buf.Bytes()

log.Tracef(nil, "rendered markdown to html:\n%s", string(html))

return string(html), confluenceExtension.Attachments
// We'll return attachments separately - caller handles this
return string(html), []attachment.Attachment{}
Comment on lines +129 to +130
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helper function compileMarkdownWithExtension always returns an empty attachment slice, but the callers ignore this return value and use their own attachment handling. This works correctly, but the return value in the helper is misleading. Consider either removing the attachment return value from this helper function or properly collecting attachments from the extension parameter.

Copilot uses AI. Check for mistakes.
}

// CompileMarkdown compiles markdown to Confluence Storage Format with GitHub Alerts support
// This is the main function that now uses the enhanced GitHub Alerts transformer by default
// for superior processing of [!NOTE], [!TIP], [!WARNING], [!CAUTION], [!IMPORTANT] syntax
func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
// Use the enhanced GitHub Alerts extension for better processing
ghAlertsExtension := NewConfluenceExtension(stdlib, path, cfg)
html, _ := compileMarkdownWithExtension(markdown, ghAlertsExtension, "rendering markdown with GitHub Alerts support:\n%s")
return html, ghAlertsExtension.Attachments
}
Comment on lines +133 to +141
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the default behavior of CompileMarkdown to use GitHub Alerts transformer is a breaking change. Previously, blockquotes containing [!NOTE] would render with that text literally visible. Now they will be converted to macros with "Note" as the title. While this is likely the desired behavior for most users (and matches GitHub's rendering), consider:

  1. Documenting this breaking change in release notes
  2. Providing a migration guide for users who need the old behavior
  3. Consider if a configuration flag should control this behavior by default

Copilot uses AI. Check for mistakes.

// CompileMarkdownLegacy compiles markdown using the legacy approach without GitHub Alerts transformer
// This function is preserved for backward compatibility and testing purposes
func CompileMarkdownLegacy(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
confluenceExtension := NewConfluenceLegacyExtension(stdlib, path, cfg)
html, _ := compileMarkdownWithExtension(markdown, confluenceExtension, "rendering markdown with legacy renderer:\n%s")
return html, confluenceExtension.Attachments
}

// ConfluenceExtension is a goldmark extension for GitHub Alerts with Transformer approach
// This extension provides superior GitHub Alert processing by transforming [!NOTE], [!TIP], etc.
// into proper Confluence macros while maintaining full compatibility with existing functionality.
// This is now the primary/default extension.
type ConfluenceExtension struct {
html.Config
Stdlib *stdlib.Lib
Path string
MarkConfig types.MarkConfig
Attachments []attachment.Attachment
}

// NewConfluenceExtension creates a new instance of the GitHub Alerts extension
// This is the improved standalone version that doesn't depend on feature flags
func NewConfluenceExtension(stdlib *stdlib.Lib, path string, cfg types.MarkConfig) *ConfluenceExtension {
return &ConfluenceExtension{
Config: html.NewConfig(),
Stdlib: stdlib,
Path: path,
MarkConfig: cfg,
Attachments: []attachment.Attachment{},
}
}

func (c *ConfluenceExtension) Attach(a attachment.Attachment) {
c.Attachments = append(c.Attachments, a)
}

// Extend extends the Goldmark processor with GitHub Alerts transformer and renderers
// This method registers all necessary components for GitHub Alert processing:
// 1. Core renderers for standard markdown elements
// 2. GitHub Alerts specific renderers (blockquote and text) with higher priority
// 3. GitHub Alerts AST transformer for preprocessing
func (c *ConfluenceExtension) Extend(m goldmark.Markdown) {
// Register core renderers (excluding blockquote and text which we'll replace)
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(crenderer.NewConfluenceCodeBlockRenderer(c.Stdlib, c.Path), 100),
util.Prioritized(crenderer.NewConfluenceFencedCodeBlockRenderer(c.Stdlib, c, c.MarkConfig), 100),
util.Prioritized(crenderer.NewConfluenceHTMLBlockRenderer(c.Stdlib), 100),
util.Prioritized(crenderer.NewConfluenceHeadingRenderer(c.MarkConfig.DropFirstH1), 100),
util.Prioritized(crenderer.NewConfluenceImageRenderer(c.Stdlib, c, c.Path), 100),
util.Prioritized(crenderer.NewConfluenceParagraphRenderer(), 100),
util.Prioritized(crenderer.NewConfluenceLinkRenderer(), 100),
))

// Add GitHub Alerts specific renderers with higher priority to override defaults
// These renderers handle both GitHub Alerts and legacy blockquote syntax
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(crenderer.NewConfluenceGHAlertsBlockQuoteRenderer(), 200),
util.Prioritized(crenderer.NewConfluenceTextRenderer(c.MarkConfig.StripNewlines), 200),
))

// Add the GitHub Alerts AST transformer that preprocesses [!TYPE] syntax
m.Parser().AddOptions(parser.WithASTTransformers(
util.Prioritized(ctransformer.NewGHAlertsTransformer(), 100),
))

// Add mkdocsadmonitions support if requested
if slices.Contains(c.MarkConfig.Features, "mkdocsadmonitions") {
m.Parser().AddOptions(
parser.WithBlockParsers(
util.Prioritized(mkDocsParser.NewAdmonitionParser(), 100),
),
)

m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(crenderer.NewConfluenceMkDocsAdmonitionRenderer(), 100),
))
}

// Add mention support if requested
if slices.Contains(c.MarkConfig.Features, "mention") {
m.Parser().AddOptions(
parser.WithInlineParsers(
util.Prioritized(cparser.NewMentionParser(), 99),
),
)

m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(crenderer.NewConfluenceMentionRenderer(c.Stdlib), 100),
))
}

// Add confluence tag parser for <ac:*/> tags
m.Parser().AddOptions(parser.WithInlineParsers(
util.Prioritized(cparser.NewConfluenceTagParser(), 199),
))
}

// CompileMarkdownWithTransformer compiles markdown using the transformer approach for GitHub Alerts
// This function provides enhanced GitHub Alert processing while maintaining full compatibility
// with existing markdown functionality. It transforms [!NOTE], [!TIP], etc. into proper titles.
// This is an alias for CompileMarkdown for backward compatibility.
func CompileMarkdownWithTransformer(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
return CompileMarkdown(markdown, stdlib, path, cfg)
}

// Approach 2: Decorator Pattern Implementation
// CompileMarkdownDecorator wraps the compilation process with configurable GitHub Alerts support

// MarkdownCompiler interface defines the contract for markdown compilation
type MarkdownCompiler interface {
Compile(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment)
}

// LegacyMarkdownCompiler implements the original compilation without GitHub Alerts transformer
type LegacyMarkdownCompiler struct{}

func (c *LegacyMarkdownCompiler) Compile(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
return CompileMarkdownLegacy(markdown, stdlib, path, cfg)
}

// GHAlertsMarkdownCompiler implements compilation with GitHub Alerts transformer
type GHAlertsMarkdownCompiler struct{}

func (c *GHAlertsMarkdownCompiler) Compile(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
return CompileMarkdownWithTransformer(markdown, stdlib, path, cfg)
}

// CompileMarkdownWithDecorator allows choosing between legacy and GitHub Alerts approaches
// This provides a flexible way to switch implementations based on configuration or feature flags
func CompileMarkdownWithDecorator(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig, useGHAlerts bool) (string, []attachment.Attachment) {
var compiler MarkdownCompiler

if useGHAlerts {
compiler = &GHAlertsMarkdownCompiler{}
log.Tracef(nil, "using GitHub Alerts transformer compiler")
} else {
compiler = &LegacyMarkdownCompiler{}
log.Tracef(nil, "using legacy compiler")
}

return compiler.Compile(markdown, stdlib, path, cfg)
}
Comment on lines +256 to 284
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decorator pattern implementation (MarkdownCompiler interface, LegacyMarkdownCompiler, GHAlertsMarkdownCompiler, and CompileMarkdownWithDecorator function) appears to be unused code. If this was intended for future extensibility or as an alternative API, consider documenting its purpose. Otherwise, consider removing this unused code to reduce maintenance burden.

Suggested change
// LegacyMarkdownCompiler implements the original compilation without GitHub Alerts transformer
type LegacyMarkdownCompiler struct{}
func (c *LegacyMarkdownCompiler) Compile(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
return CompileMarkdownLegacy(markdown, stdlib, path, cfg)
}
// GHAlertsMarkdownCompiler implements compilation with GitHub Alerts transformer
type GHAlertsMarkdownCompiler struct{}
func (c *GHAlertsMarkdownCompiler) Compile(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) {
return CompileMarkdownWithTransformer(markdown, stdlib, path, cfg)
}
// CompileMarkdownWithDecorator allows choosing between legacy and GitHub Alerts approaches
// This provides a flexible way to switch implementations based on configuration or feature flags
func CompileMarkdownWithDecorator(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig, useGHAlerts bool) (string, []attachment.Attachment) {
var compiler MarkdownCompiler
if useGHAlerts {
compiler = &GHAlertsMarkdownCompiler{}
log.Tracef(nil, "using GitHub Alerts transformer compiler")
} else {
compiler = &LegacyMarkdownCompiler{}
log.Tracef(nil, "using legacy compiler")
}
return compiler.Compile(markdown, stdlib, path, cfg)
}
// Note: legacy decorator-based Markdown compiler types and helpers that wrapped
// CompileMarkdownLegacy and CompileMarkdownWithTransformer were removed because
// they were unused thin wrappers. If a decorator-style API is needed again in
// the future, it can be reintroduced from version control history.

Copilot uses AI. Check for mistakes.
Loading
Loading