diff --git a/README.md b/README.md index 1c64f444..faed0524 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,15 @@ some long code block 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` +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) | In any other case the default behaviour will be resumed and html `
` tag will be used diff --git a/pkg/mark/renderer/blockquote.go b/pkg/mark/renderer/blockquote.go index 9d21573a..12100b33 100644 --- a/pkg/mark/renderer/blockquote.go +++ b/pkg/mark/renderer/blockquote.go @@ -35,11 +35,12 @@ const ( Info BlockQuoteType = iota Note Warn + Tip None ) func (t BlockQuoteType) String() string { - return []string{"info", "note", "warn", "none"}[t] + return []string{"info", "note", "warning", "tip", "none"}[t] } type BlockQuoteLevelMap map[ast.Node]int @@ -48,20 +49,45 @@ func (m BlockQuoteLevelMap) Level(node ast.Node) int { return m[node] } +type BlockQuoteClassifier struct { + patternMap map[string]*regexp.Regexp +} + +func LegacyBlockQuoteClassifier() BlockQuoteClassifier { + return BlockQuoteClassifier{ + patternMap: map[string]*regexp.Regexp{ + "info": regexp.MustCompile(`(?i)info`), + "note": regexp.MustCompile(`(?i)note`), + "warn": regexp.MustCompile(`(?i)warn`), + "tip": regexp.MustCompile(`(?i)tip`), + }, + } +} + +func GHAlertsBlockQuoteClassifier() BlockQuoteClassifier { + return BlockQuoteClassifier{ + patternMap: map[string]*regexp.Regexp{ + "info": regexp.MustCompile(`(?i)^\!(note|important)`), + "note": regexp.MustCompile(`(?i)^\!warning`), + "warn": regexp.MustCompile(`(?i)^\!caution`), + "tip": regexp.MustCompile(`(?i)^\!tip`), + }, + } +} + // ClassifyingBlockQuote compares a string against a set of patterns and returns a BlockQuoteType -func ClassifyingBlockQuote(literal string) BlockQuoteType { - infoPattern := regexp.MustCompile(`info|Info|INFO`) - notePattern := regexp.MustCompile(`note|Note|NOTE`) - warnPattern := regexp.MustCompile(`warn|Warn|WARN`) +func (classifier BlockQuoteClassifier) ClassifyingBlockQuote(literal string) BlockQuoteType { var t = None switch { - case infoPattern.MatchString(literal): + case classifier.patternMap["info"].MatchString(literal): t = Info - case notePattern.MatchString(literal): + case classifier.patternMap["note"].MatchString(literal): t = Note - case warnPattern.MatchString(literal): + case classifier.patternMap["warn"].MatchString(literal): t = Warn + case classifier.patternMap["tip"].MatchString(literal): + t = Tip } return t } @@ -69,6 +95,8 @@ func ClassifyingBlockQuote(literal string) BlockQuoteType { // ParseBlockQuoteType parses the first line of a blockquote and returns its type func ParseBlockQuoteType(node ast.Node, source []byte) BlockQuoteType { var t = None + var legacyClassifier = LegacyBlockQuoteClassifier() + var ghAlertsClassifier = GHAlertsBlockQuoteClassifier() countParagraphs := 0 _ = ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { @@ -80,7 +108,29 @@ func ParseBlockQuoteType(node ast.Node, source []byte) BlockQuoteType { if countParagraphs < 2 && entering { if node.Kind() == ast.KindText { n := node.(*ast.Text) - t = ClassifyingBlockQuote(string(n.Text(source))) + t = legacyClassifier.ClassifyingBlockQuote(string(n.Text(source))) + // If the node is a text node but classification returned none do not give up! + // Find the next two sibling nodes midNode and rightNode, + // 1. If both are also a text node + // 2. and the original node (node) text value is '[' + // 3. and the rightNode text value is ']' + // It means with high degree of confidence that the original md doc contains a Github alert type of blockquote + // Classifying the next text type node (midNode) will confirm that. + if t == None { + midNode := node.NextSibling() + rightNode := midNode.NextSibling() + + if midNode.Kind() == ast.KindText { + midTextNode := midNode.(*ast.Text) + if rightNode != nil && rightNode.Kind() == ast.KindText { + rightTextNode := rightNode.(*ast.Text) + + if string(n.Text(source)) == "[" && string(rightTextNode.Text(source)) == "]" { + t = ghAlertsClassifier.ClassifyingBlockQuote(string(midTextNode.Text(source))) + } + } + } + } countParagraphs += 1 } if node.Kind() == ast.KindHTMLBlock { @@ -88,7 +138,7 @@ func ParseBlockQuoteType(node ast.Node, source []byte) BlockQuoteType { n := node.(*ast.HTMLBlock) for i := 0; i < n.BaseBlock.Lines().Len(); i++ { line := n.BaseBlock.Lines().At(i) - t = ClassifyingBlockQuote(string(line.Value(source))) + t = legacyClassifier.ClassifyingBlockQuote(string(line.Value(source))) if t != None { break } diff --git a/pkg/mark/testdata/quotes-droph1.html b/pkg/mark/testdata/quotes-droph1.html index d8fdcdf0..8b98bc47 100644 --- a/pkg/mark/testdata/quotes-droph1.html +++ b/pkg/mark/testdata/quotes-droph1.html @@ -12,7 +12,7 @@

First Heading

Warn (Should not be picked as blockquote type)

Second Heading

-true +true

Warn

  • Warn bullet 1
  • @@ -28,7 +28,73 @@

    Third Heading

    Test

    +

    Fourth Heading - Warn should not get picked as block quote

    +true +

    TIP:

    +
      +
    1. Note number one
    2. +
    3. Note number two
    4. +
    +
    +

    a +b

    +
    +

    Warn (Should not be picked as blockquote type)

    +

    Simple Blockquote

    This paragraph is a simple blockquote

    +

    GH Alerts Heading

    +

    Note Type Alert Heading

    +true +

    [!NOTE]

    +
      +
    • Note bullet 1
    • +
    • Note bullet 2
    • +
    +
    +

    Tip Type Alert Heading

    +true +

    [!TIP]

    +
      +
    • Tip bullet 1
    • +
    • Tip bullet 2
    • +
    +
    +

    Warning Type Alert Heading

    +true +

    [!WARNING]

    +
      +
    • Warning bullet 1
    • +
    • Warning bullet 2
    • +
    +
    +

    Important/Caution Type Alert Heading

    +true +

    [!IMPORTANT]

    +
      +
    • Important bullet 1
    • +
    • Important bullet 2
    • +
    +
    +true +

    [!CAUTION]

    +
      +
    • Important bullet 1
    • +
    • Important bullet 2
    • +
    +
    +

    Should not be picked up and converted into blockquote macro

    +
    +

    [[!NOTE]

    +
    +
    +

    [!NOTE

    +
    +
    +

    [Hey !NOTE]

    +
    +
    +

    [NOTE]

    +
    diff --git a/pkg/mark/testdata/quotes-stripnewlines.html b/pkg/mark/testdata/quotes-stripnewlines.html index 7a430199..ba9c9731 100644 --- a/pkg/mark/testdata/quotes-stripnewlines.html +++ b/pkg/mark/testdata/quotes-stripnewlines.html @@ -12,7 +12,7 @@

    First Heading

    Warn (Should not be picked as blockquote type)

    Second Heading

    -true +true

    Warn

    • Warn bullet 1
    • @@ -27,7 +27,72 @@

      Third Heading

      Test

      +

      Fourth Heading - Warn should not get picked as block quote

      +true +

      TIP:

      +
        +
      1. Note number one
      2. +
      3. Note number two
      4. +
      +
      +

      a b

      +
      +

      Warn (Should not be picked as blockquote type)

      +

      Simple Blockquote

      This paragraph is a simple blockquote

      +

      GH Alerts Heading

      +

      Note Type Alert Heading

      +true +

      [!NOTE]

      +
        +
      • Note bullet 1
      • +
      • Note bullet 2
      • +
      +
      +

      Tip Type Alert Heading

      +true +

      [!TIP]

      +
        +
      • Tip bullet 1
      • +
      • Tip bullet 2
      • +
      +
      +

      Warning Type Alert Heading

      +true +

      [!WARNING]

      +
        +
      • Warning bullet 1
      • +
      • Warning bullet 2
      • +
      +
      +

      Important/Caution Type Alert Heading

      +true +

      [!IMPORTANT]

      +
        +
      • Important bullet 1
      • +
      • Important bullet 2
      • +
      +
      +true +

      [!CAUTION]

      +
        +
      • Important bullet 1
      • +
      • Important bullet 2
      • +
      +
      +

      Should not be picked up and converted into blockquote macro

      +
      +

      [[!NOTE]

      +
      +
      +

      [!NOTE

      +
      +
      +

      [Hey !NOTE]

      +
      +
      +

      [NOTE]

      +
      diff --git a/pkg/mark/testdata/quotes.html b/pkg/mark/testdata/quotes.html index c1519c7a..a2d2a002 100644 --- a/pkg/mark/testdata/quotes.html +++ b/pkg/mark/testdata/quotes.html @@ -13,7 +13,7 @@

      First Heading

      Warn (Should not be picked as blockquote type)

      Second Heading

      -true +true

      Warn

      • Warn bullet 1
      • @@ -29,7 +29,73 @@

        Third Heading

        Test

        +

        Fourth Heading - Warn should not get picked as block quote

        +true +

        TIP:

        +
          +
        1. Note number one
        2. +
        3. Note number two
        4. +
        +
        +

        a +b

        +
        +

        Warn (Should not be picked as blockquote type)

        +

        Simple Blockquote

        This paragraph is a simple blockquote

        +

        GH Alerts Heading

        +

        Note Type Alert Heading

        +true +

        [!NOTE]

        +
          +
        • Note bullet 1
        • +
        • Note bullet 2
        • +
        +
        +

        Tip Type Alert Heading

        +true +

        [!TIP]

        +
          +
        • Tip bullet 1
        • +
        • Tip bullet 2
        • +
        +
        +

        Warning Type Alert Heading

        +true +

        [!WARNING]

        +
          +
        • Warning bullet 1
        • +
        • Warning bullet 2
        • +
        +
        +

        Important/Caution Type Alert Heading

        +true +

        [!IMPORTANT]

        +
          +
        • Important bullet 1
        • +
        • Important bullet 2
        • +
        +
        +true +

        [!CAUTION]

        +
          +
        • Important bullet 1
        • +
        • Important bullet 2
        • +
        +
        +

        Should not be picked up and converted into blockquote macro

        +
        +

        [[!NOTE]

        +
        +
        +

        [!NOTE

        +
        +
        +

        [Hey !NOTE]

        +
        +
        +

        [NOTE]

        +
        diff --git a/pkg/mark/testdata/quotes.md b/pkg/mark/testdata/quotes.md index 995f67ff..d9de8e05 100644 --- a/pkg/mark/testdata/quotes.md +++ b/pkg/mark/testdata/quotes.md @@ -26,6 +26,65 @@ > > Test +## Fourth Heading - Warn should not get picked as block quote + +> **TIP:** +> +> 1. Note number one +> 1. Note number two +> +>> a +>> b +> +> **Warn (Should not be picked as blockquote type)** + ## Simple Blockquote > This paragraph is a simple blockquote + +## GH Alerts Heading + +### Note Type Alert Heading + +> [!NOTE] +> +> * Note bullet 1 +> * Note bullet 2 + +### Tip Type Alert Heading + +> [!TIP] +> +> * Tip bullet 1 +> * Tip bullet 2 + +### Warning Type Alert Heading + +> [!WARNING] +> +> * Warning bullet 1 +> * Warning bullet 2 + +### Important/Caution Type Alert Heading + +> [!IMPORTANT] +> +> * Important bullet 1 +> * Important bullet 2 + + +> [!CAUTION] +> +> * Important bullet 1 +> * Important bullet 2 + +### Should not be picked up and converted into blockquote macro + +> [[!NOTE] + + +> [!NOTE + +> [Hey !NOTE] + +> [NOTE]