Skip to content

Commit d51be6e

Browse files
committed
fix(lists): enforce 4 space indentation in sublists
Acording to the spec, multi paragraph (or block) list item requires subblocks to be indented 4 spaces (or 1 tab). Although, this is mentioned in the documentation, Showdown didn't enforce this rule in sublists because other implementations, such as GFM also didn't. However, in some edge cases, this led to inconsistent behavior, as shown in issue #299. This commit makes 4 space indentation in sublists mandatory. BREAKING CHANGE: syntax for sublists is more restrictive. Before, sublists SHOULD be indented by 4 spaces, but indenting 2 spaces would work. Now, sublists MUST be indented 4 spaces or they won't work. With this input: ```md * one * two * three ``` Before (ouput): ```html <ul> <li>one <ul> <li>two <ul><li>three</li></ul> <li> </ul> </li> <ul> ``` After (output): ```html <ul> <li>one</li> <li>two <ul><li>three</li></ul> </li> </ul> ``` To migrate either fix source md files or activate the option `disableForced4SpacesIndentedSublists` (coming in v1.5.0): ```md showdown.setOption('disableForced4SpacesIndentedSublists', true); ```
1 parent 9cfe8b1 commit d51be6e

13 files changed

Lines changed: 236 additions & 58 deletions

dist/showdown.js

Lines changed: 21 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/showdown.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/showdown.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/showdown.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/subParsers/lists.js

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ showdown.subParser('lists', function (text, options, globals) {
4141
// attacklab: add sentinel to emulate \z
4242
listStr += '~0';
4343

44-
var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
44+
var rgx = /(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,
4545
isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr));
4646

4747
listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
4848
checked = (checked && checked.trim() !== '');
49+
4950
var item = showdown.subParser('outdent')(m4, options, globals),
5051
bulletStyle = '';
5152

@@ -61,6 +62,7 @@ showdown.subParser('lists', function (text, options, globals) {
6162
return otp;
6263
});
6364
}
65+
6466
// m1 - Leading line or
6567
// Has a double return (multi paragraph) or
6668
// Has sublist
@@ -103,8 +105,10 @@ showdown.subParser('lists', function (text, options, globals) {
103105
function parseConsecutiveLists(list, listType, trimTrailing) {
104106
// check if we caught 2 or more consecutive lists by mistake
105107
// we use the counterRgx, meaning if listType is UL we look for OL and vice versa
106-
var counterRxg = (listType === 'ul') ? /^\d+\.[ \t]/gm : /^[*+-][ \t]/gm,
107-
result = '';
108+
var olRgx = /^ {0,3}\d+\.[ \t]/gm,
109+
ulRgx = /^ {0,3}[*+-][ \t]/gm,
110+
counterRxg = (listType === 'ul') ? olRgx : ulRgx,
111+
result = '';
108112

109113
if (list.search(counterRxg) !== -1) {
110114
(function parseCL(txt) {
@@ -115,7 +119,7 @@ showdown.subParser('lists', function (text, options, globals) {
115119

116120
// invert counterType and listType
117121
listType = (listType === 'ul') ? 'ol' : 'ul';
118-
counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
122+
counterRxg = (listType === 'ul') ? olRgx : ulRgx;
119123

120124
//recurse
121125
parseCL(txt.slice(pos));
@@ -134,21 +138,20 @@ showdown.subParser('lists', function (text, options, globals) {
134138
// http://bugs.webkit.org/show_bug.cgi?id=11231
135139
text += '~0';
136140

137-
// Re-usable pattern to match any entire ul or ol list:
138-
var wholeList = /^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
139-
140141
if (globals.gListLevel) {
141-
text = text.replace(wholeList, function (wholeMatch, list, m2) {
142-
var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
143-
return parseConsecutiveLists(list, listType, true);
144-
});
142+
text = text.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,
143+
function (wholeMatch, list, m2) {
144+
var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
145+
return parseConsecutiveLists(list, listType, true);
146+
}
147+
);
145148
} else {
146-
wholeList = /(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
147-
text = text.replace(wholeList, function (wholeMatch, m1, list, m3) {
148-
149-
var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
150-
return parseConsecutiveLists(list, listType, false);
151-
});
149+
text = text.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,
150+
function (wholeMatch, m1, list, m3) {
151+
var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
152+
return parseConsecutiveLists(list, listType, false);
153+
}
154+
);
152155
}
153156

154157
// strip sentinel
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
- foo
22

3-
- bazinga
3+
- bazinga
44

5-
- yeah
5+
- yeah
66

77
- bar
88

9-
1. damn
9+
1. damn
1010

11-
2. so many paragraphs
11+
2. so many paragraphs
1212

1313
- baz

test/issues/#196.entity-in-code-block-in-nested-list.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ Test pre in a list
22

33
- & <
44
- `& <`
5-
- & <
6-
- `& <`
7-
- & <
8-
- `& <`
9-
- & <
10-
- `& <`
5+
- & <
6+
- `& <`
7+
- & <
8+
- `& <`
9+
- & <
10+
- `& <`
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<ul>
2+
<li>one</li>
3+
</ul>
4+
<ol>
5+
<li>two</li>
6+
</ol>
7+
<p>foo</p>
8+
<ul>
9+
<li>one</li>
10+
</ul>
11+
<ol>
12+
<li>two</li>
13+
</ol>
14+
<p>foo</p>
15+
<ul>
16+
<li>one</li>
17+
</ul>
18+
<ol>
19+
<li>two</li>
20+
</ol>
21+
<p>foo</p>
22+
<ul>
23+
<li>one
24+
25+
<ol>
26+
<li>two</li></ol></li>
27+
</ul>
28+
<p>foo</p>
29+
<ul>
30+
<li>one</li>
31+
<li>two</li>
32+
</ul>
33+
<p>foo</p>
34+
<ul>
35+
<li>one</li>
36+
<li>two</li>
37+
</ul>
38+
<p>foo</p>
39+
<ul>
40+
<li>one</li>
41+
<li>two</li>
42+
</ul>
43+
<p>foo</p>
44+
<ul>
45+
<li>one</li>
46+
<li>two</li>
47+
</ul>
48+
<p>foo</p>
49+
<ul>
50+
<li>one
51+
52+
<ul>
53+
<li>two</li></ul></li>
54+
</ul>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
* one
2+
1. two
3+
4+
foo
5+
6+
* one
7+
1. two
8+
9+
foo
10+
11+
* one
12+
1. two
13+
14+
foo
15+
16+
* one
17+
1. two
18+
19+
foo
20+
21+
* one
22+
* two
23+
24+
foo
25+
26+
* one
27+
* two
28+
29+
foo
30+
31+
* one
32+
* two
33+
34+
foo
35+
36+
* one
37+
* two
38+
39+
foo
40+
41+
* one
42+
* two
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<ul>
2+
<li>one long paragraph of
3+
text</li>
4+
</ul>
5+
<ol>
6+
<li>two</li>
7+
</ol>
8+
<p>foo</p>
9+
<ul>
10+
<li>one long paragraph of
11+
text</li>
12+
</ul>
13+
<ol>
14+
<li>two</li>
15+
</ol>

0 commit comments

Comments
 (0)