Переглянути джерело

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 роки тому
батько
коміт
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

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/showdown.js.map


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/showdown.min.js


Різницю між файлами не показано, бо вона завелика
+ 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

Деякі файли не було показано, через те що забагато файлів було змінено