showdown/src/subParsers/lists.js

160 lines
5.7 KiB
JavaScript
Raw Normal View History

2015-01-16 05:21:33 +08:00
/**
* Form HTML ordered (numbered) and unordered (bulleted) lists.
*/
showdown.subParser('lists', function (text, options, globals) {
2015-01-19 19:37:21 +08:00
'use strict';
text = globals.converter._dispatch('lists.before', text, options, globals);
2015-01-19 19:37:21 +08:00
/**
* Process the contents of a single ordered or unordered list, splitting it
* into individual list items.
2015-01-19 22:57:43 +08:00
* @param {string} listStr
* @param {boolean} trimTrailing
* @returns {string}
2015-01-19 19:37:21 +08:00
*/
function processListItems (listStr, trimTrailing) {
2015-01-19 19:37:21 +08:00
// The $g_list_level global keeps track of when we're inside a list.
// Each time we enter a list, we increment it; when we leave a list,
// we decrement. If it's zero, we're not in a list anymore.
//
// We do this because when we're not inside a list, we want to treat
// something like this:
//
// I recommend upgrading to version
// 8. Oops, now this line is treated
// as a sub-list.
//
// As a single paragraph, despite the fact that the second line starts
// with a digit-period-space sequence.
//
// Whereas when we're inside a list (or sub-list), that line will be
// treated as the start of a sub-list. What a kludge, huh? This is
// an aspect of Markdown's syntax that's hard to parse perfectly
// without resorting to mind-reading. Perhaps the solution is to
// change the syntax rules such that sub-lists must start with a
// starting cardinal number; e.g. "1." or "a.".
globals.gListLevel++;
// trim trailing blank lines:
listStr = listStr.replace(/\n{2,}$/, '\n');
// attacklab: add sentinel to emulate \z
listStr += '~0';
2015-01-16 05:21:33 +08:00
var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\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 = '';
// Support for github tasklists
if (taskbtn && options.tasklists) {
bulletStyle = ' class="task-list-item" style="list-style-type: none;"';
item = item.replace(/^[ \t]*\[(x|X| )?]/m, 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;
});
}
// m1 - Leading line or
// Has a double return (multi paragraph) or
// Has sublist
if (m1 || (item.search(/\n{2,}/) > -1)) {
item = showdown.subParser('githubCodeBlocks')(item, options, globals);
item = showdown.subParser('blockGamut')(item, options, globals);
} else {
// Recursion for sub-lists:
item = showdown.subParser('lists')(item, options, globals);
item = item.replace(/\n$/, ''); // chomp(item)
if (isParagraphed) {
item = showdown.subParser('paragraphs')(item, options, globals);
} else {
item = showdown.subParser('spanGamut')(item, options, globals);
}
}
item = '<li' + bulletStyle + '>' + item + '</li>\n';
return item;
});
2015-01-16 05:21:33 +08:00
// attacklab: strip sentinel
2015-01-19 19:37:21 +08:00
listStr = listStr.replace(/~0/g, '');
globals.gListLevel--;
if (trimTrailing) {
listStr = listStr.replace(/\s+$/, '');
}
2015-01-19 19:37:21 +08:00
return listStr;
}
/**
* Check and parse consecutive lists (better fix for issue #142)
* @param {string} list
* @param {string} listType
* @param {boolean} trimTrailing
* @returns {string}
*/
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 = '';
if (list.search(counterRxg) !== -1) {
(function parseCL(txt) {
var pos = txt.search(counterRxg);
if (pos !== -1) {
// slice
result += '\n<' + listType + '>\n' + processListItems(txt.slice(0, pos), !!trimTrailing) + '</' + listType + '>\n';
// invert counterType and listType
listType = (listType === 'ul') ? 'ol' : 'ul';
counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
//recurse
parseCL(txt.slice(pos));
} else {
result += '\n<' + listType + '>\n' + processListItems(txt, !!trimTrailing) + '</' + listType + '>\n';
}
})(list);
} else {
result = '\n<' + listType + '>\n' + processListItems(list, !!trimTrailing) + '</' + listType + '>\n';
}
return result;
}
2015-01-19 19:37:21 +08:00
// add sentinel to hack around khtml/safari bug:
2015-01-19 19:37:21 +08:00
// 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;
2015-01-19 19:37:21 +08:00
if (globals.gListLevel) {
text = text.replace(wholeList, function (wholeMatch, list, m2) {
var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
return parseConsecutiveLists(list, listType, true);
2015-01-19 19:37:21 +08:00
});
} 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) {
2015-01-19 19:37:21 +08:00
var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
return parseConsecutiveLists(list, listType, false);
2015-01-19 19:37:21 +08:00
});
}
// strip sentinel
2015-01-19 19:37:21 +08:00
text = text.replace(/~0/, '');
text = globals.converter._dispatch('lists.after', text, options, globals);
2015-01-19 19:37:21 +08:00
return text;
2015-01-16 05:21:33 +08:00
});