Skip to content

Commit 2d23d04

Browse files
committed
Switch Markdown rendering from Text::MultiMarkdown to CommonMark
Custom rendering function to add `id` attribute to headings. Fixes #2312
1 parent 13f4505 commit 2d23d04

File tree

7 files changed

+121
-36
lines changed

7 files changed

+121
-36
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
with:
3434
node-version: "18"
3535
- run: npm install -g yarn && yarn install
36+
- run: apt-get update && apt-get -y install libcmark-dev
3637
- name: Install Carton
3738
uses: perl-actions/install-with-cpm@stable
3839
with:

cpanfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ requires 'Catalyst::TraitFor::Request::REST::ForBrowsers';
1414
requires 'Catalyst::View::JSON';
1515
requires 'Catalyst::View::Xslate';
1616
requires 'CatalystX::Fastly::Role::Response', '0.06';
17+
requires 'CommonMark';
1718
requires 'Config::General';
1819
requires 'Config::ZOMG', '1.000000';
1920
requires 'Cpanel::JSON::XS';
@@ -69,7 +70,6 @@ requires 'Plack::Test';
6970
requires 'Ref::Util', '>= 0.008';
7071
requires 'Router::Simple';
7172
requires 'Term::Size::Any';
72-
requires 'Text::MultiMarkdown';
7373
requires 'Text::Pluralize';
7474
requires 'Text::Xslate::Bridge';
7575
requires 'Text::Xslate::Bridge::Star';

cpanfile.snapshot

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,15 @@ DISTRIBUTIONS
809809
perl 5.006
810810
strict 0
811811
warnings 0
812+
CommonMark-0.290000
813+
pathname: N/NW/NWELLNHOF/CommonMark-0.290000.tar.gz
814+
provides:
815+
CommonMark 0.290000
816+
CommonMark::Node 0.290000
817+
requirements:
818+
Devel::CheckLib 0
819+
ExtUtils::MakeMaker 0
820+
perl 5.008
812821
Config-Any-0.33
813822
pathname: H/HA/HAARG/Config-Any-0.33.tar.gz
814823
provides:
@@ -1481,6 +1490,16 @@ DISTRIBUTIONS
14811490
Test::More 0.98
14821491
parent 0
14831492
perl 5.008001
1493+
Devel-CheckLib-1.16
1494+
pathname: M/MA/MATTN/Devel-CheckLib-1.16.tar.gz
1495+
provides:
1496+
Devel::CheckLib 1.16
1497+
requirements:
1498+
Exporter 0
1499+
ExtUtils::MakeMaker 0
1500+
File::Spec 0
1501+
File::Temp 0.16
1502+
perl 5.004050
14841503
Devel-Confess-0.009004
14851504
pathname: H/HA/HAARG/Devel-Confess-0.009004.tar.gz
14861505
provides:
@@ -5457,35 +5476,6 @@ DISTRIBUTIONS
54575476
Exporter 0
54585477
ExtUtils::MakeMaker 0
54595478
perl 5.006
5460-
Text-Markdown-1.000031
5461-
pathname: B/BO/BOBTFISH/Text-Markdown-1.000031.tar.gz
5462-
provides:
5463-
Text::Markdown 1.000031
5464-
requirements:
5465-
Digest::MD5 0
5466-
Encode 0
5467-
ExtUtils::MakeMaker 6.42
5468-
FindBin 0
5469-
List::MoreUtils 0
5470-
Test::Differences 0
5471-
Test::Exception 0
5472-
Test::More 0.42
5473-
Text::Balanced 0
5474-
Text-MultiMarkdown-1.000035
5475-
pathname: B/BO/BOBTFISH/Text-MultiMarkdown-1.000035.tar.gz
5476-
provides:
5477-
Text::MultiMarkdown 1.000035
5478-
requirements:
5479-
Digest::MD5 0
5480-
Encode 0
5481-
ExtUtils::MakeMaker 6.59
5482-
FindBin 0
5483-
HTML::Entities 0
5484-
List::MoreUtils 0
5485-
Test::Exception 0
5486-
Test::More 0.42
5487-
Text::Markdown v1.0.26
5488-
perl 5.008
54895479
Text-Pluralize-1.1
54905480
pathname: K/KV/KVAIL/Text-Pluralize-1.1.tar.gz
54915481
provides:

lib/MetaCPAN/Web/Controller/Feed.pm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use HTML::Escape qw( escape_html );
99
use MetaCPAN::Web::Types qw( ArrayRef Enum HashRef Str Undef Uri );
1010
use Params::ValidationCompiler qw( validation_for );
1111
use Path::Tiny qw( path );
12-
use Text::MultiMarkdown qw( markdown );
12+
use MetaCPAN::Web::RenderUtil qw( render_markdown );
1313
use XML::FeedPP ();
1414

1515
sub recent_rss : Path('/recent.rss') Args(0) {
@@ -91,7 +91,7 @@ sub news : Private {
9191
9292
#$str =~ s{\[([^]]+)\]\(([^)]+)\)}{<a href="$2">$1</a>}g;
9393
$e{abstract} = $str;
94-
$e{abstract} = markdown($str);
94+
$e{abstract} = render_markdown($str);
9595
9696
push @entries, \%e;
9797
}

lib/MetaCPAN/Web/RenderUtil.pm

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ use HTML::Escape qw( escape_html );
88
use HTML::Restrict ();
99
use URI ();
1010
use Digest::MD5 ();
11+
use CommonMark qw( :node :event );
1112

1213
our @EXPORT_OK = qw(
1314
filter_html
1415
gravatar_image
16+
render_markdown
1517
);
1618

1719
sub filter_html {
@@ -146,4 +148,66 @@ sub gravatar_image {
146148
$grav_id, $size // 80;
147149
}
148150

151+
my @is_leaf;
152+
$is_leaf[$_] = 1
153+
for (
154+
NODE_HTML, NODE_HRULE, NODE_CODE_BLOCK, NODE_TEXT,
155+
NODE_SOFTBREAK, NODE_LINEBREAK, NODE_CODE, NODE_INLINE_HTML,
156+
);
157+
158+
sub render_markdown {
159+
my ($markdown) = @_;
160+
161+
my $doc = CommonMark->parse_document($markdown);
162+
163+
my ( $html, $header_content, %seen_header );
164+
165+
my $iter = $doc->iterator;
166+
while ( my ( $ev_type, $node ) = $iter->next ) {
167+
my $node_type = $node->get_type;
168+
169+
if ( $node_type == NODE_DOCUMENT ) {
170+
next;
171+
}
172+
173+
if ( $node_type == NODE_HEADER ) {
174+
if ( $ev_type == EVENT_ENTER ) {
175+
$header_content = '';
176+
}
177+
if ( $ev_type == EVENT_EXIT ) {
178+
$header_content =~ s{(?:-(\d+))?$}{'-' . (($1 // 1) + 1)}e
179+
while $seen_header{$header_content}++;
180+
181+
my $header_html = $node->render_html;
182+
$header_html
183+
=~ s/^<h[0-9]+\b\K/' id="'.escape_html($header_content).'"'/e;
184+
$html .= $header_html;
185+
186+
undef $header_content;
187+
}
188+
}
189+
elsif ($ev_type == EVENT_ENTER
190+
&& $node->parent->get_type == NODE_DOCUMENT )
191+
{
192+
$html .= $node->render_html;
193+
}
194+
195+
if ( defined $header_content ) {
196+
if ( $is_leaf[$node_type] ) {
197+
my $content = lc( $node->get_literal );
198+
$content =~ s/\A\s+//;
199+
$content =~ s/\s+\z//;
200+
$content =~ s/\s+/-/g;
201+
202+
if ( length $content ) {
203+
$header_content .= '-' if length $header_content;
204+
$header_content .= $content;
205+
}
206+
}
207+
}
208+
}
209+
210+
return $html;
211+
}
212+
149213
1;

lib/MetaCPAN/Web/View/Xslate/Bridge.pm

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ use parent qw(Text::Xslate::Bridge);
66
use Text::Xslate::Util qw( mark_raw );
77
use Number::Format ();
88
use Ref::Util qw( is_coderef is_regexpref );
9-
use Text::MultiMarkdown ();
109
use List::Util ();
1110
use DateTime ();
1211
use With::Roles ();
1312
use Text::Pluralize ();
1413
use MetaCPAN::Web::RenderUtil qw( gravatar_image ); ## no perlimports
14+
use MetaCPAN::Web::RenderUtil qw( render_markdown );
1515
use overload ();
1616

1717
my $num_formatter = Number::Format->new;
@@ -26,11 +26,9 @@ sub format_bytes {
2626
$num_formatter->format_bytes($number);
2727
}
2828

29-
my $md = Text::MultiMarkdown->new( heading_ids => 1 );
30-
3129
sub markdown {
3230
my ($text) = @_;
33-
mark_raw( $md->markdown($text) );
31+
mark_raw( render_markdown($text) );
3432
}
3533

3634
sub filter_html {

t/markdown.t

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use strict;
2+
use warnings;
3+
use Test::More;
4+
5+
use MetaCPAN::Web::RenderUtil qw( render_markdown );
6+
7+
my $html = render_markdown(<<'EOM');
8+
# Heading
9+
10+
Some body text
11+
12+
## Heading
13+
14+
More stuff
15+
16+
## Heading
17+
18+
more stuff
19+
20+
## Heading **with** _markup_
21+
22+
Content
23+
24+
EOM
25+
26+
like $html, qr{<h1 id="heading">Heading</h1>}, 'first heading';
27+
like $html, qr{<h2 id="heading-2">Heading</h2>}, 'second heading';
28+
like $html, qr{<h2 id="heading-3">Heading</h2>}, 'third heading';
29+
30+
like $html, qr{<h2 id="heading-with-markup">Heading <}, 'heading with markup';
31+
32+
done_testing();

0 commit comments

Comments
 (0)