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
This commit is contained in:
Estevão Soares dos Santos 2015-07-11 23:02:02 +01:00
parent c33f98884b
commit dc72403acc
11 changed files with 52 additions and 40 deletions

1
.gitignore vendored
View File

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

BIN
dist/showdown.js vendored

Binary file not shown.

BIN
dist/showdown.js.map vendored

Binary file not shown.

BIN
dist/showdown.min.js vendored

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -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;
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() !== '');
listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4) {
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) {

View File

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

View File

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

View File

@ -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);
});

View File

@ -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();
}