From 2d45c56ba53fc582b55ed844a2f9fa54737d85a6 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 26 Aug 2013 15:21:23 -0400 Subject: [PATCH] Replace Markdown Linebreak Regexp with node parser. --- .../discourse/components/markdown.js | 10 ----- .../discourse/dialects/bbcode_dialect.js | 2 +- .../javascripts/discourse/dialects/dialect.js | 18 +++++--- .../discourse/dialects/github_code_dialect.js | 42 ++++++++++++++++++- .../discourse/dialects/newline_dialect.js | 42 +++++++++++++++++++ spec/components/pretty_text_spec.rb | 6 +-- test/javascripts/components/bbcode_test.js | 2 +- test/javascripts/components/markdown_test.js | 21 ++++++---- 8 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 app/assets/javascripts/discourse/dialects/newline_dialect.js diff --git a/app/assets/javascripts/discourse/components/markdown.js b/app/assets/javascripts/discourse/components/markdown.js index 57fb2e0dd43..1f1c1929ba6 100644 --- a/app/assets/javascripts/discourse/components/markdown.js +++ b/app/assets/javascripts/discourse/components/markdown.js @@ -97,17 +97,7 @@ Discourse.Markdown = { return { makeHtml: function(text) { - // Linebreaks - var linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks; - if (!linebreaks) { - text = text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) { - if (t.match(/\n{2}/gim)) return t; - return t.replace("\n", " \n"); - }); - } - text = Discourse.Dialect.cook(text, opts); - if (!text) return ""; if (opts.sanitize) { diff --git a/app/assets/javascripts/discourse/dialects/bbcode_dialect.js b/app/assets/javascripts/discourse/dialects/bbcode_dialect.js index 9ac4475af1a..8f2c9fb3071 100644 --- a/app/assets/javascripts/discourse/dialects/bbcode_dialect.js +++ b/app/assets/javascripts/discourse/dialects/bbcode_dialect.js @@ -174,7 +174,7 @@ Discourse.Dialect.on("register", function(event) { result.push(['p', ['aside', params, ['div', {'class': 'title'}, ['div', {'class': 'quote-controls'}], - avatarImg ? avatarImg + "\n" : "", + avatarImg ? avatarImg : "", I18n.t('user.said',{username: username}) ], contents diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js index 9a5b6ce976e..6d2d50e535c 100644 --- a/app/assets/javascripts/discourse/dialects/dialect.js +++ b/app/assets/javascripts/discourse/dialects/dialect.js @@ -32,8 +32,7 @@ ```javascript Discourse.Dialect.on("parseNode", function(event) { - var node = event.node, - path = event.path; + var node = event.node; if (node[0] === 'code') { node[node.length-1] = "EVIL TROUT HACKED YOUR CODE"; @@ -68,16 +67,22 @@ var parser = window.BetterMarkdown, @method parseTree @param {Array} tree the JsonML tree to parse @param {Array} path the path of ancestors to the current node in the tree. Can be used for matching. + @param {Object} insideCounts counts what tags we're inside @returns {Array} the parsed tree **/ - parseTree = function parseTree(tree, path) { + parseTree = function parseTree(tree, path, insideCounts) { if (tree instanceof Array) { - Discourse.Dialect.trigger('parseNode', {node: tree, path: path}); + Discourse.Dialect.trigger('parseNode', {node: tree, path: path, dialect: dialect, insideCounts: insideCounts || {}}); path = path || []; + insideCounts = insideCounts || {}; + path.push(tree); tree.slice(1).forEach(function (n) { - parseTree(n, path); + var tagName = n[0]; + insideCounts[tagName] = (insideCounts[tagName] || 0) + 1; + parseTree(n, path, insideCounts); + insideCounts[tagName] = insideCounts[tagName] - 1; }); path.pop(); } @@ -103,7 +108,8 @@ Discourse.Dialect = { cook: function(text, opts) { if (!initialized) { initializeDialects(); } dialect.options = opts; - return parser.renderJsonML(parseTree(parser.toHTMLTree(text, 'Discourse'))); + var tree = parser.toHTMLTree(text, 'Discourse'); + return parser.renderJsonML(parseTree(tree)); } }; diff --git a/app/assets/javascripts/discourse/dialects/github_code_dialect.js b/app/assets/javascripts/discourse/dialects/github_code_dialect.js index f1a717ef9d2..c83d21d68ed 100644 --- a/app/assets/javascripts/discourse/dialects/github_code_dialect.js +++ b/app/assets/javascripts/discourse/dialects/github_code_dialect.js @@ -84,10 +84,48 @@ Discourse.Dialect.on("register", function(event) { @namespace Discourse.Dialect **/ Discourse.Dialect.on("parseNode", function(event) { - var node = event.node, - path = event.path; + var node = event.node; if (node[0] === 'code') { node[node.length-1] = Handlebars.Utils.escapeExpression(node[node.length-1]); } }); + + +Discourse.Dialect.on("parseNode", function(event) { + + var node = event.node, + opts = event.dialect.options, + insideCounts = event.insideCounts, + linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks; + + if (!linebreaks) { + // We don't add line breaks inside a pre + if (insideCounts.pre > 0) { return; } + + if (node.length > 1) { + for (var j=1; j 0) { + spliceInstructions.push(split[i]); + if (i !== split.length-1) { spliceInstructions.push(['br']); } + } + } + node.splice.apply(node, spliceInstructions); + } + } + } + } + } + } +}); diff --git a/app/assets/javascripts/discourse/dialects/newline_dialect.js b/app/assets/javascripts/discourse/dialects/newline_dialect.js new file mode 100644 index 00000000000..5859a3ed5df --- /dev/null +++ b/app/assets/javascripts/discourse/dialects/newline_dialect.js @@ -0,0 +1,42 @@ +/** + Support for the newline behavior in markdown that most expect. + + @event parseNode + @namespace Discourse.Dialect +**/ +Discourse.Dialect.on("parseNode", function(event) { + var node = event.node, + opts = event.dialect.options, + insideCounts = event.insideCounts, + linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks; + + if (!linebreaks) { + // We don't add line breaks inside a pre + if (insideCounts.pre > 0) { return; } + + if (node.length > 1) { + for (var j=1; j 0) { + spliceInstructions.push(split[i]); + if (i !== split.length-1) { spliceInstructions.push(['br']); } + } + } + node.splice.apply(node, spliceInstructions); + } + } + } + } + } + } +}); \ No newline at end of file diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index b41ba19bc77..82ee603d18e 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -14,15 +14,15 @@ describe PrettyText do end it "produces a quote even with new lines in it" do - PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]").should match_html "

" + PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]").should match_html "

" end it "should produce a quote" do - PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]").should match_html "

" + PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]").should match_html "

" end it "trims spaces on quote params" do - PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]").should match_html "

" + PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]").should match_html "

" end end diff --git a/test/javascripts/components/bbcode_test.js b/test/javascripts/components/bbcode_test.js index d198ffd92e2..08a93df0246 100644 --- a/test/javascripts/components/bbcode_test.js +++ b/test/javascripts/components/bbcode_test.js @@ -88,7 +88,7 @@ test("quote formatting", function() { format("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]\nhello", "

\n\n

\nhello", + "

abc

\n\n

hello", "handles new lines properly"); }); diff --git a/test/javascripts/components/markdown_test.js b/test/javascripts/components/markdown_test.js index e0840812ffc..eb9457e569b 100644 --- a/test/javascripts/components/markdown_test.js +++ b/test/javascripts/components/markdown_test.js @@ -20,8 +20,7 @@ test("basic cooking", function() { cooked("***hello***", "

hello

", "it can do bold and italics at once."); }); -test("Line Breaks", function() { - +test("Traditional Line Breaks", function() { var input = "1\n2\n3"; cooked(input, "

1
2
3

", "automatically handles trivial newlines"); @@ -34,7 +33,12 @@ test("Line Breaks", function() { Discourse.SiteSettings.traditional_markdown_linebreaks = true; cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting"); +}); +test("Line Breaks", function() { + cooked("[] first choice\n[] second choice", + "

[] first choice
[] second choice

", + "it handles new lines correctly with [] options"); }); test("Links", function() { @@ -79,11 +83,10 @@ test("Links", function() { "

Here's a tweet:
https://twitter.com/evil_trout/status/345954894420787200

", "It doesn't strip the new line."); - cooked("1. View @eviltrout's profile here: http://meta.discourse.org/users/eviltrout/activity\nnext line.", + cooked("1. View @eviltrout's profile here: http://meta.discourse.org/users/eviltrout/activity
next line.", "
  1. View @eviltrout's profile here: http://meta.discourse.org/users/eviltrout/activity
    next line.
", "allows autolinking within a list without inserting a paragraph."); - cooked("[3]: http://eviltrout.com", "", "It doesn't autolink markdown link references"); cooked("http://discourse.org and http://discourse.org/another_url and http://www.imdb.com/name/nm2225369", @@ -98,13 +101,13 @@ test("Quotes", function() { cookedOptions("[quote=\"eviltrout, post: 1\"]\na quote\n\nsecond line\n[/quote]", { topicId: 2 }, - "