Skip to content

Commit

Permalink
fix(lists): enforce 4 space indentation in sublists
Browse files Browse the repository at this point in the history
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);
```
  • Loading branch information
tivie committed Nov 11, 2016
1 parent 9cfe8b1 commit d51be6e
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 58 deletions.
39 changes: 21 additions & 18 deletions dist/showdown.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/showdown.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/showdown.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/showdown.min.js.map

Large diffs are not rendered by default.

37 changes: 20 additions & 17 deletions src/subParsers/lists.js
Expand Up @@ -41,11 +41,12 @@ showdown.subParser('lists', function (text, options, globals) {
// attacklab: add sentinel to emulate \z
listStr += '~0';

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

listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
checked = (checked && checked.trim() !== '');

var item = showdown.subParser('outdent')(m4, options, globals),
bulletStyle = '';

Expand All @@ -61,6 +62,7 @@ showdown.subParser('lists', function (text, options, globals) {
return otp;
});
}

// m1 - Leading line or
// Has a double return (multi paragraph) or
// Has sublist
Expand Down Expand Up @@ -103,8 +105,10 @@ showdown.subParser('lists', function (text, options, globals) {
function parseConsecutiveLists(list, listType, trimTrailing) {
// check if we caught 2 or more consecutive lists by mistake
// we use the counterRgx, meaning if listType is UL we look for OL and vice versa
var counterRxg = (listType === 'ul') ? /^\d+\.[ \t]/gm : /^[*+-][ \t]/gm,
result = '';
var olRgx = /^ {0,3}\d+\.[ \t]/gm,
ulRgx = /^ {0,3}[*+-][ \t]/gm,
counterRxg = (listType === 'ul') ? olRgx : ulRgx,
result = '';

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

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

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

// Re-usable pattern to match any entire ul or ol list:
var wholeList = /^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

if (globals.gListLevel) {
text = text.replace(wholeList, function (wholeMatch, list, m2) {
var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
return parseConsecutiveLists(list, listType, true);
});
text = text.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,
function (wholeMatch, list, m2) {
var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
return parseConsecutiveLists(list, listType, true);
}
);
} else {
wholeList = /(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
text = text.replace(wholeList, function (wholeMatch, m1, list, m3) {

var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
return parseConsecutiveLists(list, listType, false);
});
text = text.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,
function (wholeMatch, m1, list, m3) {
var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
return parseConsecutiveLists(list, listType, false);
}
);
}

// strip sentinel
Expand Down
8 changes: 4 additions & 4 deletions test/cases/paragraphed-list-with-sublists.md
@@ -1,13 +1,13 @@
- foo

- bazinga
- bazinga

- yeah
- yeah

- bar

1. damn
1. damn

2. so many paragraphs
2. so many paragraphs

- baz
12 changes: 6 additions & 6 deletions test/issues/#196.entity-in-code-block-in-nested-list.md
Expand Up @@ -2,9 +2,9 @@ Test pre in a list

- & <
- `& <`
- & <
- `& <`
- & <
- `& <`
- & <
- `& <`
- & <
- `& <`
- & <
- `& <`
- & <
- `& <`
@@ -0,0 +1,54 @@
<ul>
<li>one</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>one</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>one</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>one

<ol>
<li>two</li></ol></li>
</ul>
<p>foo</p>
<ul>
<li>one</li>
<li>two</li>
</ul>
<p>foo</p>
<ul>
<li>one</li>
<li>two</li>
</ul>
<p>foo</p>
<ul>
<li>one</li>
<li>two</li>
</ul>
<p>foo</p>
<ul>
<li>one</li>
<li>two</li>
</ul>
<p>foo</p>
<ul>
<li>one

<ul>
<li>two</li></ul></li>
</ul>
@@ -0,0 +1,42 @@
* one
1. two

foo

* one
1. two

foo

* one
1. two

foo

* one
1. two

foo

* one
* two

foo

* one
* two

foo

* one
* two

foo

* one
* two

foo

* one
* two
@@ -0,0 +1,15 @@
<ul>
<li>one long paragraph of
text</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>one long paragraph of
text</li>
</ul>
<ol>
<li>two</li>
</ol>
@@ -0,0 +1,9 @@
* one long paragraph of
text
1. two

foo

* one long paragraph of
text
1. two
Expand Up @@ -6,15 +6,42 @@
</ol>
<p>foo</p>
<ul>
<li>one

<ol>
<li>two</li></ol></li>
<li>one</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>one</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>one</li>
</ul>
<ol>
<li>two</li>
</ol>
<p>foo</p>
<ul>
<li>uli one</li>
<li>uli two</li>
</ul>
<p>foo</p>
<ul>
<li>uli one</li>
<li>uli two</li>
</ul>
<p>foo</p>
<ul>
<li>uli one</li>
<li>uli two</li>
</ul>
<p>foo</p>
<ul>
<li>one

<ul>
<li>two</li></ul></li>
<li>uli one</li>
<li>uli two</li>
</ul>

0 comments on commit d51be6e

Please sign in to comment.