Forráskód Böngészése

feat(tasklists): add support for GFM tasklists

Github Flavored Markdown supports tasklist by `[x]` or `[ ]` after list item marker.
This commit adds this feature to showdown through an option called "tasklists".

Related to #164
Estevão Soares dos Santos 10 éve
szülő
commit
dc72403acc

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@
 .DS_Store
 node_modules
 npm-debug.log
+localtest.html

+ 29 - 39
dist/showdown.js

@@ -19,7 +19,8 @@ var showdown = {},
       strikethrough:             false,
       tables:                    false,
       tablesHeaderId:            false,
-      ghCodeBlocks:              true  // true due to historical reasons
+      ghCodeBlocks:              true,  // true due to historical reasons
+      tasklists:                 false
     },
     globalOptions = JSON.parse(JSON.stringify(defaultOptions)); //clone default options out of laziness =P
 
@@ -1667,25 +1668,28 @@ showdown.subParser('lists', function (text, options, globals) {
     // attacklab: add sentinel to emulate \z
     listStr += '~0';
 
-    /*
-     list_str = list_str.replace(/
-     (\n)?							// leading line = $1
-     (^[ \t]*)						// leading whitespace = $2
-     ([*+-]|\d+[.]) [ \t]+			// list marker = $3
-     ([^\r]+?						// list item text   = $4
-     (\n{1,2}))
-     (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
-     /gm, function(){...});
-     */
-    var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm;
-
-    listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4) {
+    var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm;
+
+    listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
+      checked = (checked && checked.trim() !== '');
+
       var item = showdown.subParser('outdent')(m4, options, globals);
-      //m1 - LeadingLine
 
+      //m1 - LeadingLine
       if (m1 || (item.search(/\n{2,}/) > -1)) {
         item = showdown.subParser('blockGamut')(item, options, globals);
       } else {
+        if (taskbtn && options.tasklists) {
+          item = item.replace(taskbtn, function () {
+            var otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';
+            if (checked) {
+              otp += ' checked';
+            }
+            otp += '>';
+            return otp;
+          });
+        }
+
         // Recursion for sub-lists:
         item = showdown.subParser('lists')(item, options, globals);
         item = item.replace(/\n$/, ''); // chomp(item)
@@ -1694,8 +1698,14 @@ showdown.subParser('lists', function (text, options, globals) {
 
       // this is a "hack" to differentiate between ordered and unordered lists
       // related to issue #142
-      var tp = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
-      return spl + tp + '<li>' + item + '</li>\n';
+      var tp = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol',
+        bulletStyle = '';
+
+      if (taskbtn) {
+        bulletStyle = ' style="list-style-type: none;"';
+      }
+
+      return spl + tp + '<li' + bulletStyle + '>' + item + '</li>\n';
     });
 
     // attacklab: strip sentinel
@@ -1712,6 +1722,8 @@ showdown.subParser('lists', function (text, options, globals) {
    * @returns {string|*}
    */
   function splitConsecutiveLists (results, listType) {
+    // parsing html with regex...
+    // This will surely fail if some extension decides to change paragraph markup directly
     var cthulhu = /(<p[^>]+?>|<p>|<\/p>)/img,
         holder = [[]],
         res = '',
@@ -1750,28 +1762,6 @@ showdown.subParser('lists', function (text, options, globals) {
   text += '~0';
 
   // Re-usable pattern to match any entire ul or ol list:
-
-  /*
-   var whole_list = /
-   (									// $1 = whole list
-   (								// $2
-   [ ]{0,3}					// attacklab: g_tab_width - 1
-   ([*+-]|\d+[.])				// $3 = first list item marker
-   [ \t]+
-   )
-   [^\r]+?
-   (								// $4
-   ~0							// sentinel for workaround; should be $
-   |
-   \n{2,}
-   (?=\S)
-   (?!							// Negative lookahead for another list item marker
-   [ \t]*
-   (?:[*+-]|\d+[.])[ \t]+
-   )
-   )
-   )/g
-   */
   var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
 
   if (globals.gListLevel) {

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/showdown.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/showdown.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/showdown.min.js.map


+ 2 - 1
src/showdown.js

@@ -17,7 +17,8 @@ var showdown = {},
       strikethrough:             false,
       tables:                    false,
       tablesHeaderId:            false,
-      ghCodeBlocks:              true  // true due to historical reasons
+      ghCodeBlocks:              true,  // true due to historical reasons
+      tasklists:                 false
     },
     globalOptions = JSON.parse(JSON.stringify(defaultOptions)); //clone default options out of laziness =P
 

+ 27 - 38
src/subParsers/lists.js

@@ -42,25 +42,28 @@ showdown.subParser('lists', function (text, options, globals) {
     // attacklab: add sentinel to emulate \z
     listStr += '~0';
 
-    /*
-     list_str = list_str.replace(/
-     (\n)?							// leading line = $1
-     (^[ \t]*)						// leading whitespace = $2
-     ([*+-]|\d+[.]) [ \t]+			// list marker = $3
-     ([^\r]+?						// list item text   = $4
-     (\n{1,2}))
-     (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
-     /gm, function(){...});
-     */
-    var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm;
-
-    listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4) {
+    var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm;
+
+    listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
+      checked = (checked && checked.trim() !== '');
+
       var item = showdown.subParser('outdent')(m4, options, globals);
-      //m1 - LeadingLine
 
+      //m1 - LeadingLine
       if (m1 || (item.search(/\n{2,}/) > -1)) {
         item = showdown.subParser('blockGamut')(item, options, globals);
       } else {
+        if (taskbtn && options.tasklists) {
+          item = item.replace(taskbtn, function () {
+            var otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';
+            if (checked) {
+              otp += ' checked';
+            }
+            otp += '>';
+            return otp;
+          });
+        }
+
         // Recursion for sub-lists:
         item = showdown.subParser('lists')(item, options, globals);
         item = item.replace(/\n$/, ''); // chomp(item)
@@ -69,8 +72,14 @@ showdown.subParser('lists', function (text, options, globals) {
 
       // this is a "hack" to differentiate between ordered and unordered lists
       // related to issue #142
-      var tp = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
-      return spl + tp + '<li>' + item + '</li>\n';
+      var tp = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol',
+        bulletStyle = '';
+
+      if (taskbtn) {
+        bulletStyle = ' style="list-style-type: none;"';
+      }
+
+      return spl + tp + '<li' + bulletStyle + '>' + item + '</li>\n';
     });
 
     // attacklab: strip sentinel
@@ -87,6 +96,8 @@ showdown.subParser('lists', function (text, options, globals) {
    * @returns {string|*}
    */
   function splitConsecutiveLists (results, listType) {
+    // parsing html with regex...
+    // This will surely fail if some extension decides to change paragraph markup directly
     var cthulhu = /(<p[^>]+?>|<p>|<\/p>)/img,
         holder = [[]],
         res = '',
@@ -125,28 +136,6 @@ showdown.subParser('lists', function (text, options, globals) {
   text += '~0';
 
   // Re-usable pattern to match any entire ul or ol list:
-
-  /*
-   var whole_list = /
-   (									// $1 = whole list
-   (								// $2
-   [ ]{0,3}					// attacklab: g_tab_width - 1
-   ([*+-]|\d+[.])				// $3 = first list item marker
-   [ \t]+
-   )
-   [^\r]+?
-   (								// $4
-   ~0							// sentinel for workaround; should be $
-   |
-   \n{2,}
-   (?=\S)
-   (?!							// Negative lookahead for another list item marker
-   [ \t]*
-   (?:[*+-]|\d+[.])[ \t]+
-   )
-   )
-   )/g
-   */
   var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
 
   if (globals.gListLevel) {

+ 10 - 0
test/features/#164.4.tasklists.html

@@ -0,0 +1,10 @@
+<h1 id="mythings">my things</h1>
+
+<ul>
+    <li>foo</li>
+    <li style="list-style-type: none;"><input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"> bar</li>
+    <li style="list-style-type: none;"><input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"> baz</li>
+    <li style="list-style-type: none;"><input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;" checked> bazinga</li>
+</ul>
+
+<p>otherthings</p>

+ 8 - 0
test/features/#164.4.tasklists.md

@@ -0,0 +1,8 @@
+# my things
+
+ -  foo
+ - [] bar
+ - [ ] baz
+ - [x] bazinga
+
+otherthings

+ 2 - 1
test/node/showdown.js

@@ -28,7 +28,8 @@ describe('showdown.options', function () {
         strikethrough:             false,
         tables:                    false,
         tablesHeaderId:            false,
-        ghCodeBlocks:              true
+        ghCodeBlocks:              true,
+        tasklists:                 false
       };
       expect(showdown.getDefaultOptions()).to.be.eql(opts);
     });

+ 3 - 1
test/node/testsuite.features.js

@@ -21,8 +21,10 @@ describe('makeHtml() features testsuite', function () {
       converter = new showdown.Converter({literalMidWordUnderscores: true});
     } else if (testsuite[i].name === '#164.3.strikethrough') {
       converter = new showdown.Converter({strikethrough: true});
-    }  else if (testsuite[i].name === 'disable_gh_codeblocks') {
+    } else if (testsuite[i].name === 'disable_gh_codeblocks') {
       converter = new showdown.Converter({ghCodeBlocks: false});
+    } else if (testsuite[i].name === '#164.4.tasklists') {
+      converter = new showdown.Converter({tasklists: true});
     } else {
       converter = new showdown.Converter();
     }

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott