define([ "jquery", "underscore", "backbone",
"showdown",
"text!templates/markdown.html" ],
function($, _, Backbone,
showdown,
markdownTemplate ){
/* The markdownView is a view that will retrieve and parse markdown */
var markdownView = Backbone.View.extend({
className: "markdown",
type: "markdown",
/* Renders the compiled template into HTML */
template: _.template(markdownTemplate),
/* The events that this view listens to */
events: {
},
/* Construct a new instance of markdownView */
initialize: function (options) {
/* highlightStyle = the name of the code syntax highlight style we want to use */
this.highlightStyle = "atom-one-light";
if(typeof options !== "undefined"){
this.markdown = options.markdown || "";
this.citations = options.citations || [];
}
},
/* Render the view */
render: function() {
// once required extensions are tested for and loaded, convert and append markdown
this.stopListening();
this.listenTo(this, "requiredExtensionsLoaded", function(SDextensions){
var converter = new showdown.Converter({
metadata: true,
simplifiedAutoLink:true,
customizedHeaderId:true,
tables:true,
strikethrough: true,
tasklists: true,
emoji: true,
extensions: SDextensions
});
//If there are citations in the markdown text, add it to the markdown
// so it gets rendered.
if( _.contains(SDextensions, "showdown-citation") && this.citations.length ){
// put the bibtex into the markdown so it can be processed by
// the showdown-citations extension.
this.markdown = this.markdown + "\n" + this.citations + "";
}
htmlFromMD = converter.makeHtml( this.markdown ); //
this.$el.append(this.template({ markdown: htmlFromMD }));
this.trigger("mdRendered");
});
// detect which extensions we'll need
this.listRequiredExtensions( this.markdown);
return this;
},
// test which extensions are needed, then load them
listRequiredExtensions: function(markdown){
var markdownView = this;
// SDextensions lists the desired order* of all potentailly required showdown extensions (* order matters! )
var SDextensions = ["xssfilter", "katex", "highlight", "docbook",
"showdown-htags", "bootstrap", "footnotes",
"showdown-citation", "showdown-images"];
var numTestsTodo = SDextensions.length;
// each time an extension is tested for (and loaded if required), updateExtensionList is called.
// when all tests are completed (numTestsTodo == 0), an event is triggered.
// when this event is triggered, markdown is converted and appended (see render)
var updateExtensionList = function(extensionName, required){
numTestsTodo = numTestsTodo - 1;
if(required == false){
var n = SDextensions.indexOf(extensionName);
SDextensions.splice(n, 1);
}
if(numTestsTodo == 0){
markdownView.trigger("requiredExtensionsLoaded", SDextensions);
}
};
// ===== the regular expressions used to test whether showdown extensions are required ===== //
// note: these expressions test the *markdown* and *not* the html
var regexHighlight = new RegExp("'.*'"), // too general?
regexDocbook = new RegExp("<(title|citetitle|emphasis|para|ulink|literallayout|itemizedlist|orderedlist|listitem|subscript|superscript).*>"),
// for bootstrap: test for tables, directly from showdown/src/subParsers/makehtml/tables.js
// if we add more bootstrap classes, this will become more complicated since we have to test the markdown before the initial parse
regexTable1 = /^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,
regexTable2 = /^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm;
regexFootnotes1 = /^\[\^([\d\w]+)\]:( |\n)((.+\n)*.+)$/mg,
regexFootnotes2 = /^\[\^([\d\w]+)\]:\s*((\n+(\s{2,4}|\t).+)+)$/mg,
regexFootnotes3 = /\[\^([\d\w]+)\]/m,
// test for all of the math/katex delimiters
regexKatex = new RegExp("\\[.*\\]|\\(.*\\)|~.*~|$.*$|```asciimath.*```|```latex.*```"),
regexCitation = /\[@.+\]/g;
// test for any tags
regexHtags = new RegExp('#\\s'),
regexImages = /!\[.*\]\(\S+\)/;
// ====== test for and load each as required each showdown extension ====== //
// --- xss --- //
// there is no test for the xss filter because it should always be included.
// it's included via the updateExtensionList function for consistency with the other, optional extensions
require(["showdownXssFilter"], function(showdownKatex){
updateExtensionList("xssfilter", required=true);
})
// --- katex test --- //
if( regexKatex.test(markdown) ){
require(["showdownKatex"], function(showdownKatex){
// custom config needed for katex
var katex = showdownKatex({
delimiters: [
{ left: "$", right: "$", display: false },
{ left: "$$", right: "$$", display: false},
{ left: '~', right: '~', display: false }
]
});
// because custom config, register katex with showdown
showdown.extension("katex", katex);
updateExtensionList("katex", required=true);
});
// css needed for katex
markdownView.$el.append("");
} else {
updateExtensionList("katex", required=false);
};
// --- highlight test --- //
if( regexHighlight.test(markdown) ){
require(["showdownHighlight"], function(showdownHighlight){
updateExtensionList("highlight", required=true);
});
// css needed for highlight
this.$el.append("");
} else {
updateExtensionList("highlight", required=false);
};
// --- docbooks test --- //
if( regexDocbook.test(markdown) ){
require(["showdownDocbook"], function(showdownDocbook){
updateExtensionList("docbook", required=true);
});
} else {
updateExtensionList("docbook", required=false);
};
// --- htag test --- //
if( regexHtags.test(markdown) ){
require(["showdownHtags"], function(showdownHtags){
updateExtensionList("showdown-htags", required=true);
});
} else {
updateExtensionList("showdown-htags", required=false);
};
// --- bootstrap test --- //
if( regexTable1.test(markdown) || regexTable2.test(markdown) ){
require(["showdownBootstrap"], function(showdownBootstrap){
updateExtensionList("bootstrap", required=true);
});
} else {
updateExtensionList("bootstrap", required=false);
};
// --- footnotes test --- //
if( regexFootnotes1.test(markdown) || regexFootnotes2.test(markdown) || regexFootnotes3.test(markdown) ){
require(["showdownFootnotes"], function(showdownFootnotes){
updateExtensionList("footnotes", required=true);
});
} else {
updateExtensionList("footnotes", required=false);
};
// --- citation test --- //
// showdownCitation throws error...
if( regexCitation.test(markdown) ){
require(["showdownCitation"], function(showdownCitation){
updateExtensionList("showdown-citation", required=true);
});
} else {
updateExtensionList("showdown-citation", required=false);
};
// --- images test --- //
if( regexImages.test(markdown) ){
require(["showdownImages"], function(showdownImages){
updateExtensionList("showdown-images", required=true);
});
} else {
updateExtensionList("showdown-images", required=false);
};
},
/* Close and destroy the view */
onClose: function() {
this.remove(); // remove for the DOM, stop listening
this.$el.html(""); // remove appended html
}
});
return markdownView;
});