From bf39349f191e30b719df63fae7fb6412e31f421b Mon Sep 17 00:00:00 2001 From: James Hollingworth Date: Wed, 6 May 2015 08:23:06 +0100 Subject: [PATCH] Github Task Lists Add support for Github Task Lists under the gfm flag. Changes to API * list(*string* body, *boolean* ordered, *boolean* taskList) * listitem(*string* text, [*boolean* checked]). `checked` is defined when you have a list item which starts with `[ ] ` or `[x] `.If defined its a boolean depending on whether the `x` is present. When checked is defined we add a input type type `checkbox` to the list item and add the class `task-list-item-checkbox`. `taskList` is true if a list has any list items where `checked` is defined. When true we add the class `task-list` to the list. Resolves chjj/marked#107 --- README.md | 6 +++-- lib/marked.js | 48 ++++++++++++++++++++++++++++------- test/tests/gfm_task_list.html | 11 ++++++++ test/tests/gfm_task_list.text | 6 +++++ 4 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 test/tests/gfm_task_list.html create mode 100644 test/tests/gfm_task_list.text diff --git a/README.md b/README.md index 09f8ec3397..122f8400a6 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,8 @@ This code will output the following HTML: - html(*string* html) - heading(*string* text, *number* level) - hr() -- list(*string* body, *boolean* ordered) +- list(*string* body, *boolean* ordered, *boolean* taskList) + - `taskList` true when `gfm` is `true` and there is a list item with a check box - listitem(*string* text) - paragraph(*string* text) - table(*string* header, *string* body) @@ -211,7 +212,8 @@ This code will output the following HTML: - codespan(*string* code) - br() - del(*string* text) -- link(*string* href, *string* title, *string* text) +- link(*string* href, *string* title, *string* text, [*boolean* checked]). + - `checked` only defined when `gfm` is `true` and there is a check box at the start of the list item (e.g. `* [ ] foo`). - image(*string* href, *string* title, *string* text) ### gfm diff --git a/lib/marked.js b/lib/marked.js index 9f1584bb3b..2df9446508 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -27,6 +27,7 @@ var block = { text: /^[^\n]+/ }; +block.checkbox = /^\[([ x])\] +/; block.bullet = /(?:[*+-]|\d+\.)/; block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; block.item = replace(block.item, 'gm') @@ -157,7 +158,8 @@ Lexer.prototype.token = function(src, top, bq) { , item , space , i - , l; + , l + , checked; while (src) { // newline @@ -304,6 +306,17 @@ Lexer.prototype.token = function(src, top, bq) { space = item.length; item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + if (this.options.gfm) { + checked = block.checkbox.exec(item); + + if (checked) { + checked = checked[1] === 'x'; + item = item.replace(block.checkbox, ''); + } else { + checked = undefined; + } + } + // Outdent whatever the // list item contains. Hacky. if (~item.indexOf('\n ')) { @@ -333,6 +346,7 @@ Lexer.prototype.token = function(src, top, bq) { } this.tokens.push({ + checked: checked, type: loose ? 'loose_item_start' : 'list_item_start' @@ -809,13 +823,23 @@ Renderer.prototype.hr = function() { return this.options.xhtml ? '
\n' : '
\n'; }; -Renderer.prototype.list = function(body, ordered) { +Renderer.prototype.list = function(body, ordered, taskList) { var type = ordered ? 'ol' : 'ul'; - return '<' + type + '>\n' + body + '\n'; + var classes = taskList ? ' class="task-list"' : ''; + return '<' + type + classes + '>\n' + body + '\n'; }; -Renderer.prototype.listitem = function(text) { - return '
  • ' + text + '
  • \n'; +Renderer.prototype.listitem = function(text, checked) { + if (checked === undefined) { + return '
  • ' + text + '
  • \n'; + } + + return '
  • ' + + ' ' + + text + + '
  • \n'; }; Renderer.prototype.paragraph = function(text) { @@ -1037,16 +1061,22 @@ Parser.prototype.tok = function() { } case 'list_start': { var body = '' + , taskList = false , ordered = this.token.ordered; while (this.next().type !== 'list_end') { + if (this.token.checked !== undefined) { + taskList = true; + } + body += this.tok(); } - return this.renderer.list(body, ordered); + return this.renderer.list(body, ordered, taskList); } case 'list_item_start': { - var body = ''; + var body = '' + , checked = this.token.checked; while (this.next().type !== 'list_item_end') { body += this.token.type === 'text' @@ -1054,7 +1084,7 @@ Parser.prototype.tok = function() { : this.tok(); } - return this.renderer.listitem(body); + return this.renderer.listitem(body, checked); } case 'loose_item_start': { var body = ''; @@ -1063,7 +1093,7 @@ Parser.prototype.tok = function() { body += this.tok(); } - return this.renderer.listitem(body); + return this.renderer.listitem(body, checked); } case 'html': { var html = !this.token.pre && !this.options.pedantic diff --git a/test/tests/gfm_task_list.html b/test/tests/gfm_task_list.html new file mode 100644 index 0000000000..b5fd61c907 --- /dev/null +++ b/test/tests/gfm_task_list.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/test/tests/gfm_task_list.text b/test/tests/gfm_task_list.text new file mode 100644 index 0000000000..a98ada7802 --- /dev/null +++ b/test/tests/gfm_task_list.text @@ -0,0 +1,6 @@ +* [ ] foo +* bar +* [x] baz +* [] bam + * [ ] bim + * [ ] lim