From 73489b652e2aea5dfb6c391168e13bc7dcdb395c Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 27 Aug 2013 12:24:17 -0400 Subject: [PATCH] FIX: Allow intra-word underscores. --- .../dialects/bold_italics_dialect.js | 41 ++++++---- .../{markdown.js => better_markdown.js} | 78 ++++--------------- lib/pretty_text.rb | 2 +- test/javascripts/components/markdown_test.js | 6 ++ 4 files changed, 48 insertions(+), 79 deletions(-) rename app/assets/javascripts/external/{markdown.js => better_markdown.js} (95%) diff --git a/app/assets/javascripts/discourse/dialects/bold_italics_dialect.js b/app/assets/javascripts/discourse/dialects/bold_italics_dialect.js index 704b6a16465..b4fadffb778 100644 --- a/app/assets/javascripts/discourse/dialects/bold_italics_dialect.js +++ b/app/assets/javascripts/discourse/dialects/bold_italics_dialect.js @@ -10,23 +10,34 @@ Discourse.Dialect.on("register", function(event) { var dialect = event.dialect, MD = event.MD; - /** - Handles simultaneous bold and italics - @method parseMentions - @param {String} text the text match - @param {Array} match the match found - @param {Array} prev the previous jsonML - @return {Array} an array containing how many chars we've replaced and the jsonML content for it. - @namespace Discourse.Dialect - **/ - dialect.inline['***'] = function boldItalics(text, match, prev) { - var regExp = /^\*{3}([^\*]+)\*{3}/, - m = regExp.exec(text); + var inlineBuilder = function(symbol, tag, surround) { + return function(text, match, prev) { + if (prev && (prev.length > 0)) { + var last = prev[prev.length - 1]; + if (typeof last === "string" && (!last.match(/\W$/))) { return; } + } - if (m) { - return [m[0].length, ['strong', ['em'].concat(this.processInline(m[1]))]]; - } + var regExp = new RegExp("^\\" + symbol + "([^\\" + symbol + "]+)" + "\\" + symbol, "igm"), + m = regExp.exec(text); + + if (m) { + + var contents = [tag].concat(this.processInline(m[1])); + if (surround) { + contents = [surround, contents]; + } + + return [m[0].length, contents]; + } + }; }; + dialect.inline['***'] = inlineBuilder('**', 'em', 'strong'); + dialect.inline['**'] = inlineBuilder('**', 'strong'); + dialect.inline['*'] = inlineBuilder('*', 'em'); + dialect.inline['_'] = inlineBuilder('_', 'em'); + + + }); diff --git a/app/assets/javascripts/external/markdown.js b/app/assets/javascripts/external/better_markdown.js similarity index 95% rename from app/assets/javascripts/external/markdown.js rename to app/assets/javascripts/external/better_markdown.js index e11d71e6c27..29e708fb857 100644 --- a/app/assets/javascripts/external/markdown.js +++ b/app/assets/javascripts/external/better_markdown.js @@ -1,3 +1,18 @@ +/* + This is a fork of markdown-js with a few changes to support discourse: + + * We have replaced the strong/em handlers because we prefer them only to work on word + boundaries. + + * We removed the maraku support as we don't use it. + + * We don't escape the contents of HTML as we prefer to use a whitelist. + + * Note the name BetterMarkdown doesn't mean it's *better* than markdown-js, it refers + to it being better than our previous markdown parser! + +*/ + // Released under MIT license // Copyright (c) 2009-2010 Dominic Baggott // Copyright (c) 2009-2010 Ash Berlin @@ -1004,69 +1019,6 @@ Markdown.dialects.Gruber.inline = { }; -// Meta Helper/generator method for em and strong handling -function strong_em( tag, md ) { - - var state_slot = tag + "_state", - other_slot = tag == "strong" ? "em_state" : "strong_state"; - - function CloseTag(len) { - this.len_after = len; - this.name = "close_" + md; - } - - return function ( text, orig_match ) { - - if ( this[state_slot][0] == md ) { - // Most recent em is of this type - //D:this.debug("closing", md); - this[state_slot].shift(); - - // "Consume" everything to go back to the recrusion in the else-block below - return[ text.length, new CloseTag(text.length-md.length) ]; - } - else { - // Store a clone of the em/strong states - var other = this[other_slot].slice(), - state = this[state_slot].slice(); - - this[state_slot].unshift(md); - - //D:this.debug_indent += " "; - - // Recurse - var res = this.processInline( text.substr( md.length ) ); - //D:this.debug_indent = this.debug_indent.substr(2); - - var last = res[res.length - 1]; - - //D:this.debug("processInline from", tag + ": ", uneval( res ) ); - - var check = this[state_slot].shift(); - if ( last instanceof CloseTag ) { - res.pop(); - // We matched! Huzzah. - var consumed = text.length - last.len_after; - return [ consumed, [ tag ].concat(res) ]; - } - else { - // Restore the state of the other kind. We might have mistakenly closed it. - this[other_slot] = other; - this[state_slot] = state; - - // We can't reuse the processed result as it could have wrong parsing contexts in it. - return [ md.length, md ]; - } - } - }; // End returned function -} - -Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**"); -Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__"); -Markdown.dialects.Gruber.inline["*"] = strong_em("em", "*"); -Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_"); - - // Build default order from insertion order. Markdown.buildBlockOrder = function(d) { var ord = []; diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 48fe7fdf23c..eecb5ca7eda 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -105,7 +105,7 @@ module PrettyText ctx.eval("var I18n = {}; I18n.t = function(a,b){ return helpers.t(a,b); }"); ctx_load(ctx, - "app/assets/javascripts/external/markdown.js", + "app/assets/javascripts/external/better_markdown.js", "app/assets/javascripts/discourse/dialects/dialect.js", "app/assets/javascripts/discourse/components/utilities.js", "app/assets/javascripts/discourse/components/markdown.js") diff --git a/test/javascripts/components/markdown_test.js b/test/javascripts/components/markdown_test.js index f4714baee30..2370f0b0a6d 100644 --- a/test/javascripts/components/markdown_test.js +++ b/test/javascripts/components/markdown_test.js @@ -17,7 +17,13 @@ var cookedOptions = function(input, opts, expected, text) { test("basic cooking", function() { cooked("hello", "

hello

", "surrounds text with paragraphs"); + cooked("**evil**", "

evil

", "it bolds text."); + cooked("*trout*", "

trout

", "it italicizes text."); + cooked("_trout_", "

trout

", "it italicizes text."); cooked("***hello***", "

hello

", "it can do bold and italics at once."); + cooked("word_with_underscores", "

word_with_underscores

", "it doesn't do intraword italics"); + cooked("hello \\*evil\\*", "

hello *evil*

", "it supports escaping of asterisks"); + cooked("hello \\_evil\\_", "

hello _evil_

", "it supports escaping of italics"); }); test("Traditional Line Breaks", function() {