Skip to content
Merged
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
67 changes: 67 additions & 0 deletions spec/fixtures/alert.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## Alert

Alerts are a Markdown extension based on the blockquote syntax that
you can use to emphasize critical information. On GitHub, they are
displayed with distinctive colors and icons to indicate the significance
of the content.

Use alerts only when they are crucial for user success and limit them
to one or two per article to prevent overloading the reader. Additionally,
you should avoid placing alerts consecutively. Alerts cannot be nested
within other elements.

To add an alert, use a special blockquote line specifying the alert type
and an optional title, followed by the alert information in a standard
blockquote.

There are five types of alert:

* NOTE
* TIP
* IMPORTANT
* WARNING
* CAUTION

```````````````````````````````` example alert
> [!NOTE]
> Useful information that users should know, even when skimming content.
.
<div class="alert alert-note"><p class="alert-title">NOTE</p>
<p>Useful information that users should know, even when skimming content.</p>
</div>
````````````````````````````````

An optional title can be added after the closing bracket.

```````````````````````````````` example alert
> [!NOTE] What is a note?
> Useful information that users should know, even when skimming content.
.
<div class="alert alert-note"><p class="alert-title">What is a note?</p>
<p>Useful information that users should know, even when skimming content.</p>
</div>
````````````````````````````````

Empty spaces after the brackets are ignored.

```````````````````````````````` example alert
> [!NOTE]
> Useful information that users should know, even when skimming content.
.
<div class="alert alert-note"><p class="alert-title">NOTE</p>
<p>Useful information that users should know, even when skimming content.</p>
</div>
````````````````````````````````

Alert-like block quotes which don't use one of the five listed
alert types are just block quotes.

```````````````````````````````` example alert
> [!FOO]
> Not a real alert.
.
<blockquote>
<p>[!FOO]<br />
Not a real alert.</p>
</blockquote>
````````````````````````````````
3 changes: 3 additions & 0 deletions spec/markd_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ describe_spec("fixtures/gfm-extensions.txt", gfm: true)

describe_spec("fixtures/gfm-regression.txt", gfm: true)

# Alert spec examples
describe_spec("fixtures/alert.txt", gfm: true)

describe Markd do
# Thanks Ryan Westlund <[email protected]> feedback via email.
it "should escape unsafe html" do
Expand Down
2 changes: 2 additions & 0 deletions src/markd/node.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Markd
List
Item
BlockQuote
Alert
ThematicBreak
Code
CodeBlock
Expand Down Expand Up @@ -45,6 +46,7 @@ module Markd
Type::List,
Type::Item,
Type::BlockQuote,
Type::Alert,
Type::CustomInLine,
Type::CustomBlock,
Type::Table,
Expand Down
1 change: 1 addition & 0 deletions src/markd/parsers/block.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Markd::Parser
RULES = {
Node::Type::Document => Rule::Document.new,
Node::Type::BlockQuote => Rule::BlockQuote.new,
Node::Type::Alert => Rule::BlockQuote.new, # Alerts and BlockQuotes are the same
Node::Type::Heading => Rule::Heading.new,
Node::Type::CodeBlock => Rule::CodeBlock.new,
Node::Type::HTMLBlock => Rule::HTMLBlock.new,
Expand Down
3 changes: 3 additions & 0 deletions src/markd/renderer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module Markd
abstract def list(node : Node, entering : Bool) : Nil
abstract def item(node : Node, entering : Bool) : Nil
abstract def block_quote(node : Node, entering : Bool) : Nil
abstract def alert(node : Node, entering : Bool) : Nil
abstract def thematic_break(node : Node, entering : Bool) : Nil
abstract def code_block(node : Node, entering : Bool, formatter : T?) : Nil forall T
abstract def code(node : Node, entering : Bool) : Nil
Expand Down Expand Up @@ -86,6 +87,8 @@ module Markd
item(node, entering)
when Node::Type::BlockQuote
block_quote(node, entering)
when Node::Type::Alert
alert(node, entering)
when Node::Type::ThematicBreak
thematic_break(node, entering)
when Node::Type::CodeBlock
Expand Down
13 changes: 13 additions & 0 deletions src/markd/renderers/html_renderer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ module Markd
newline
end

def alert(node : Node, entering : Bool) : Nil
newline
if entering
tag("div", {"class" => "alert alert-#{node.data["alert"].to_s.downcase}"})
tag("p", {"class" => "alert-title"}) do
output(node.data["title"].as(String))
end
else
tag("div", end_tag: true)
end
newline
end

def table(node : Node, entering : Bool) : Nil
has_body = node.data["has_body"]
newline
Expand Down
2 changes: 2 additions & 0 deletions src/markd/rule.cr
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ module Markd
TABLE_HEADING_SEPARATOR = /^(\|?\s*:{0,1}-:{0,1}+\s*)+(\||\s*)$/
TABLE_CELL_SEPARATOR = /(?<!\\)\|/

ADMONITION_START = /^> \[!((?:NOTE|TIP|IMPORTANT|CAUTION|WARNING)+)](\s*.*)?$/

# Match Value
#
# - None: no match
Expand Down
10 changes: 9 additions & 1 deletion src/markd/rules/block_quote.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ module Markd::Rule
if match?(parser)
seek(parser)
parser.close_unmatched_blocks
parser.add_child(Node::Type::BlockQuote, parser.next_nonspace)
if parser.gfm && (match = parser.line.match(Rule::ADMONITION_START))
node = parser.add_child(Node::Type::Alert, parser.next_nonspace)
# This is an alert
node.data["alert"] = match[1]
node.data["title"] = (match[2]? && !match[2].strip.empty?) ? match[2].strip : match[1]
parser.advance_offset(parser.line.size, false)
else
parser.add_child(Node::Type::BlockQuote, parser.next_nonspace)
end

MatchValue::Container
else
Expand Down