Skip to content

Commit

Permalink
feat(eventDispatcher): add an event dispatcher to converter
Browse files Browse the repository at this point in the history
  • Loading branch information
tivie committed Aug 3, 2015
1 parent ea6031a commit 2734326
Show file tree
Hide file tree
Showing 23 changed files with 392 additions and 124 deletions.
210 changes: 156 additions & 54 deletions dist/showdown.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/showdown.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/showdown.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/showdown.min.js.map

Large diffs are not rendered by default.

89 changes: 68 additions & 21 deletions src/converter.js
Expand Up @@ -38,18 +38,8 @@ showdown.Converter = function (converterOptions) {
*/
outputModifiers = [],

/**
* The parser Order
* @private
* @type {string[]}
*/
parserOrder = [
'githubCodeBlocks',
'hashHTMLBlocks',
'stripLinkDefinitions',
'blockGamut',
'unescapeSpecialChars'
];
listeners = {
};

_constructor();

Expand Down Expand Up @@ -128,19 +118,24 @@ showdown.Converter = function (converterOptions) {

for (var i = 0; i < ext.length; ++i) {
switch (ext[i].type) {

case 'lang':
langExtensions.push(ext[i]);
break;

case 'output':
outputModifiers.push(ext[i]);
break;

default:
// should never reach here
throw Error('Extension loader error: Type unrecognized!!!');
}
if (ext[i].hasOwnProperty(listeners)) {
for (var ln in ext[i].listeners) {
if (ext[i].listeners.hasOwnProperty(ln)) {
listen(ln, ext[i].listeners[ln]);
}
}
}
}

}

/**
Expand Down Expand Up @@ -175,6 +170,57 @@ showdown.Converter = function (converterOptions) {
}
}

/**
* Listen to an event
* @param {string} name
* @param {function} callback
*/
function listen(name, callback) {
if (!showdown.helper.isString(name)) {
throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given');
}

if (typeof callback !== 'function') {
throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given');
}

if (!listeners.hasOwnProperty(name)) {
listeners[name] = [];
}
listeners[name].push(callback);
}

/**
* Dispatch an event
* @private
* @param {string} evtName Event name
* @param {string} text Text
* @param {{}} options Converter Options
* @returns {string}
*/
this._dispatch = function dispatch (evtName, text, options) {
if (listeners.hasOwnProperty(evtName)) {
for (var ei = 0; ei < listeners[evtName].length; ++ei) {
var nText = listeners[evtName][ei](evtName, text, this, options);
if (nText && typeof nText !== 'undefined') {
text = nText;
}
}
}
return text;
};

/**
* Listen to an event
* @param {string} name
* @param {function} callback
* @returns {showdown.Converter}
*/
this.listen = function (name, callback) {
listen(name, callback);
return this;
};

/**
* Converts a markdown string into HTML
* @param {string} text
Expand Down Expand Up @@ -227,11 +273,12 @@ showdown.Converter = function (converterOptions) {
text = showdown.subParser('runExtension')(ext, text, options, globals);
});

// Run all registered parsers
for (var i = 0; i < parserOrder.length; ++i) {
var name = parserOrder[i];
text = parsers[name](text, options, globals);
}
// run the sub parsers
text = showdown.subParser('githubCodeBlocks')(text, options, globals);
text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
text = showdown.subParser('stripLinkDefinitions')(text, options, globals);
text = showdown.subParser('blockGamut')(text, options, globals);
text = showdown.subParser('unescapeSpecialChars')(text, options, globals);

// attacklab: Restore dollar signs
text = text.replace(/~D/g, '$$');
Expand Down
51 changes: 35 additions & 16 deletions src/showdown.js
Expand Up @@ -245,45 +245,64 @@ function validate(extension, name) {
type = ext.type = 'output';
}

if (type !== 'lang' && type !== 'output') {
if (type !== 'lang' && type !== 'output' && type !== 'listener') {
ret.valid = false;
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang" or "output"';
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
return ret;
}

if (type === 'listener') {
if (showdown.helper.isUndefined(ext.listeners)) {
ret.valid = false;
ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
return ret;
}
} else {
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.valid = false;
ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
return ret;
}
}

if (ext.listeners) {
if (typeof ext.listeners !== 'object') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
return ret;
}
for (var ln in ext.listeners) {
if (ext.listeners.hasOwnProperty(ln)) {
if (typeof ext.listeners[ln] !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
' must be a function but ' + typeof ext.listeners[ln] + ' given';
return ret;
}
}
}
}

if (ext.filter) {
if (typeof ext.filter !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
return ret;
}

} else if (ext.regex) {
if (showdown.helper.isString(ext.regex)) {
ext.regex = new RegExp(ext.regex, 'g');
}
if (!ext.regex instanceof RegExp) {
ret.valid = false;
ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' +
typeof ext.regex + ' given';
ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
return ret;
}
if (showdown.helper.isUndefined(ext.replace)) {
ret.valid = false;
ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
return ret;
}

} else {
ret.valid = false;
ret.error = baseMsg + 'extensions must define either a "regex" property or a "filter" method';
return ret;
}

if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.valid = false;
ret.error = baseMsg + 'output extensions must define a filter property';
return ret;
}
}
return ret;
Expand Down
12 changes: 7 additions & 5 deletions src/subParsers/anchors.js
@@ -1,9 +1,11 @@
/**
* Turn Markdown link shortcuts into XHTML <a> tags.
*/
showdown.subParser('anchors', function (text, config, globals) {
showdown.subParser('anchors', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('anchors.before', text, options);

var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
if (showdown.helper.isUndefined(m7)) {
m7 = '';
Expand Down Expand Up @@ -73,7 +75,7 @@ showdown.subParser('anchors', function (text, config, globals) {
)()()()() // pad remaining backreferences
/g,_DoAnchors_callback);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g, writeAnchorTag);

//
// Next, inline-style links: [link text](url "optional title")
Expand Down Expand Up @@ -106,7 +108,7 @@ showdown.subParser('anchors', function (text, config, globals) {
)
/g,writeAnchorTag);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
writeAnchorTag);

//
Expand All @@ -124,8 +126,8 @@ showdown.subParser('anchors', function (text, config, globals) {
)()()()()() // pad rest of backreferences
/g, writeAnchorTag);
*/
text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
text = text.replace(/(\[([^\[\]]+)])()()()()()/g, writeAnchorTag);

text = globals.converter._dispatch('anchors.after', text, options);
return text;

});
6 changes: 4 additions & 2 deletions src/subParsers/autoLinks.js
@@ -1,7 +1,7 @@
showdown.subParser('autoLinks', function (text, options) {
showdown.subParser('autoLinks', function (text, options, globals) {
'use strict';

//simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
text = globals.converter._dispatch('autoLinks.before', text, options);

var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi,
delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,
Expand All @@ -23,5 +23,7 @@ showdown.subParser('autoLinks', function (text, options) {
return showdown.subParser('encodeEmailAddress')(unescapedStr);
}

text = globals.converter._dispatch('autoLinks.after', text, options);

return text;
});
5 changes: 4 additions & 1 deletion src/subParsers/blockGamut.js
Expand Up @@ -5,6 +5,8 @@
showdown.subParser('blockGamut', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('blockGamut.before', text, options);

text = showdown.subParser('headers')(text, options, globals);

// Do Horizontal Rules:
Expand All @@ -25,6 +27,7 @@ showdown.subParser('blockGamut', function (text, options, globals) {
text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
text = showdown.subParser('paragraphs')(text, options, globals);

return text;
text = globals.converter._dispatch('blockGamut.after', text, options);

return text;
});
3 changes: 3 additions & 0 deletions src/subParsers/blockQuotes.js
@@ -1,6 +1,7 @@
showdown.subParser('blockQuotes', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('blockQuotes.before', text, options);
/*
text = text.replace(/
( // Wrap whole match in $1
Expand Down Expand Up @@ -39,5 +40,7 @@ showdown.subParser('blockQuotes', function (text, options, globals) {

return showdown.subParser('hashBlock')('<blockquote>\n' + bq + '\n</blockquote>', options, globals);
});

text = globals.converter._dispatch('blockQuotes.after', text, options);
return text;
});
2 changes: 2 additions & 0 deletions src/subParsers/codeBlocks.js
Expand Up @@ -4,6 +4,7 @@
showdown.subParser('codeBlocks', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('codeBlocks.before', text, options);
/*
text = text.replace(text,
/(?:\n\n|^)
Expand Down Expand Up @@ -44,5 +45,6 @@ showdown.subParser('codeBlocks', function (text, options, globals) {
// attacklab: strip sentinel
text = text.replace(/~0/, '');

text = globals.converter._dispatch('codeBlocks.after', text, options);
return text;
});
4 changes: 3 additions & 1 deletion src/subParsers/codeSpans.js
Expand Up @@ -23,9 +23,10 @@
*
* ... type <code>`bar`</code> ...
*/
showdown.subParser('codeSpans', function (text) {
showdown.subParser('codeSpans', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('codeSpans.before', text, options);
//special case -> literal html code tag
text = text.replace(/(<code[^><]*?>)([^]*?)<\/code>/g, function (wholeMatch, tag, c) {
c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
Expand Down Expand Up @@ -56,5 +57,6 @@ showdown.subParser('codeSpans', function (text) {
}
);

text = globals.converter._dispatch('codeSpans.after', text, options);
return text;
});
5 changes: 4 additions & 1 deletion src/subParsers/githubCodeBlocks.js
Expand Up @@ -16,6 +16,8 @@ showdown.subParser('githubCodeBlocks', function (text, options, globals) {
return text;
}

text = globals.converter._dispatch('githubCodeBlocks.before', text, options);

text += '~0';

text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) {
Expand All @@ -34,6 +36,7 @@ showdown.subParser('githubCodeBlocks', function (text, options, globals) {
// attacklab: strip sentinel
text = text.replace(/~0/, '');

return text;
text = globals.converter._dispatch('githubCodeBlocks.after', text, options);

return text;
});
3 changes: 3 additions & 0 deletions src/subParsers/headers.js
@@ -1,6 +1,8 @@
showdown.subParser('headers', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('headers.before', text, options);

var prefixHeader = options.prefixHeaderId,
headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),

Expand Down Expand Up @@ -68,5 +70,6 @@ showdown.subParser('headers', function (text, options, globals) {
return title;
}

text = globals.converter._dispatch('headers.after', text, options);
return text;
});
3 changes: 3 additions & 0 deletions src/subParsers/images.js
Expand Up @@ -4,6 +4,8 @@
showdown.subParser('images', function (text, options, globals) {
'use strict';

text = globals.converter._dispatch('images.before', text, options);

var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,
referenceRegExp = /!\[(.*?)][ ]?(?:\n[ ]*)?\[(.*?)]()()()()()/g;

Expand Down Expand Up @@ -70,5 +72,6 @@ showdown.subParser('images', function (text, options, globals) {
// Next, handle inline images: ![alt text](url =<width>x<height> "optional title")
text = text.replace(inlineRegExp, writeImageTag);

text = globals.converter._dispatch('images.after', text, options);
return text;
});

0 comments on commit 2734326

Please sign in to comment.