Răsfoiți Sursa

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);
```
Estevao Soares dos Santos 8 ani în urmă
părinte
comite
d51be6e0b4

+ 21 - 18
dist/showdown.js

@@ -1,4 +1,4 @@
-;/*! showdown 09-11-2016 */
+;/*! showdown 11-11-2016 */
 (function(){
 /**
  * Created by Tivie on 13-07-2015.
@@ -1951,11 +1951,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 = '';
 
@@ -1971,6 +1972,7 @@ showdown.subParser('lists', function (text, options, globals) {
           return otp;
         });
       }
+
       // m1 - Leading line or
       // Has a double return (multi paragraph) or
       // Has sublist
@@ -2013,8 +2015,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) {
@@ -2025,7 +2029,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));
@@ -2044,21 +2048,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

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
dist/showdown.js.map


Fișier diff suprimat deoarece este prea mare
+ 1 - 1
dist/showdown.min.js


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
dist/showdown.min.js.map


+ 20 - 17
src/subParsers/lists.js

@@ -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 = '';
 
@@ -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
@@ -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) {
@@ -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));
@@ -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

+ 4 - 4
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

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

@@ -2,9 +2,9 @@ Test pre in a list
 
 - & <
 - `& <` 
-   - & <
-   - `& <`
-      - & <
-      - `& <`
-         - & <
-         - `& <`
+    - & <
+    - `& <`
+        - & <
+        - `& <`
+            - & <
+            - `& <`

+ 54 - 0
test/issues/#299.nested-ordered-unordered-list-inconsistent-behavior-2.html

@@ -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>

+ 42 - 0
test/issues/#299.nested-ordered-unordered-list-inconsistent-behavior-2.md

@@ -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

+ 15 - 0
test/issues/#299.nested-ordered-unordered-list-inconsistent-behavior-3.html

@@ -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>

+ 9 - 0
test/issues/#299.nested-ordered-unordered-list-inconsistent-behavior-3.md

@@ -0,0 +1,9 @@
+ * one long paragraph of
+text
+ 1. two
+
+foo
+
+  * one long paragraph of
+text
+   1. two

+ 35 - 8
test/issues/#299.nested-ordered-unordered-list-inconsistent-behavior.html

@@ -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>

+ 26 - 1
test/issues/#299.nested-ordered-unordered-list-inconsistent-behavior.md

@@ -9,4 +9,29 @@ foo
 foo
 
 * one
- * two
+  1. two
+
+foo
+
+* one
+   1. two
+
+foo
+
+* uli one
+* uli two
+
+foo
+
+* uli one
+ * uli two
+
+foo
+
+* uli one
+  * uli two
+
+foo
+
+* uli one
+   * uli two

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff