diff --git a/lib/index.js b/lib/index.js index b531720..ee38516 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,6 +13,9 @@ var LinkifyIt = require('linkify-it'); var mdurl = require('mdurl'); var punycode = require('punycode'); +/* global Symbol */ +var EVENTS = typeof Symbol === 'function' ? Symbol('events') : '__events'; + var config = { 'default': require('./presets/default'), @@ -341,6 +344,8 @@ function MarkdownIt(presetName, options) { this.helpers = utils.assign({}, helpers); + this[EVENTS] = {}; + this.options = {}; this.configure(presetName); @@ -520,8 +525,12 @@ MarkdownIt.prototype.parse = function (src, env) { var state = new this.core.State(src, this, env); + this.dispatchEvent('willparse', { state: state }); + this.core.process(state); + this.dispatchEvent('parsed', { state: state }); + return state.tokens; }; @@ -555,10 +564,14 @@ MarkdownIt.prototype.render = function (src, env) { **/ MarkdownIt.prototype.parseInline = function (src, env) { var state = new this.core.State(src, this, env); - state.inlineMode = true; + + this.dispatchEvent('willparse', { state: state }); + this.core.process(state); + this.dispatchEvent('parsed', { state: state }); + return state.tokens; }; @@ -578,4 +591,87 @@ MarkdownIt.prototype.renderInline = function (src, env) { }; +function Event(name, params) { + if (!(this instanceof Event)) { + return new Event(name, params); + } + + if (typeof params === 'object') { + var keys = Object.getOwnPropertyNames(params); + + for (var i = 0; i < keys.length; i++) { + this[keys[i]] = params[keys[i]]; + } + } + + this.name = name; + this.timeStamp = +(new Date()); +} + + +/** + * MarkdownIt.addEventListener(event, fn) + * - event (String): event name + * - fn (Function): callback + * + * Registers an event listener. If the function is already subscribed + * to this event, does nothing. The callback function will recieve a + * single argument - the Event object which contains these properties: + * - timestamp: the value of Date.now() + * - name: name of the event + * - state: (where applicable) the parser's state object + * + * The available events are: + * - willparse + * - parsed + **/ +MarkdownIt.prototype.addEventListener = function (event, fn) { + var callbackArray = this[EVENTS][event] = this[EVENTS][event] || []; + + if (typeof fn !== 'function') { + throw new TypeError('Argument 2 must be a function.'); + } + + if (callbackArray.indexOf(fn) === -1) { + callbackArray.push(fn); + } +}; + + +/** + * MarkdownIt.removeEventListener(event, fn) + * - event (String): event name + * - fn (Function): callback + * + * Unsubscribes the callback from the event. If the callback isn't + * subscribed, does nothing. See [[MarkdownIt.addEventListener]] + * for the list of available events. + **/ +MarkdownIt.prototype.removeEventListener = function (event, callback) { + var callbackArray = this[EVENTS][event] = this[EVENTS][event] || []; + var index = callbackArray.indexOf(callback); + + if (index !== -1) { + callbackArray.splice(index, 1); + } +}; + + +/** + * MarkdownIt.dispatchEvent(event) + * - event (String): event name + * - params (Object): event parameters + * + * Call all the functions subscribed to this event. + * See [[MarkdownIt.addEventListener]] for the list of available events. + **/ +MarkdownIt.prototype.dispatchEvent = function (event, params) { + var callbackArray = this[EVENTS][event] = this[EVENTS][event] || []; + var e = new Event(event, params); + + for (var i = 0; i < callbackArray.length; i++) { + callbackArray[i](e); + } +}; + module.exports = MarkdownIt; diff --git a/test/misc.js b/test/misc.js index 8cf3bf5..b45535f 100644 --- a/test/misc.js +++ b/test/misc.js @@ -154,6 +154,95 @@ describe('API', function () { ); }); + it('event-parse', function () { + var md = markdownit(); + var firedPre = false; + var firedPost = false; + var subscribed = true; + + function preBlock(e) { + assert.strictEqual(subscribed, true); + + assert.strictEqual(e.name, 'willparse'); + assert.approximately(e.timeStamp, Date.now(), 10); + assert.strictEqual(e.state.inlineMode, false); + + assert.strictEqual(firedPre, false); + assert.strictEqual(firedPost, false); + firedPre = true; + } + + function postBlock(e) { + assert.strictEqual(subscribed, true); + + assert.strictEqual(e.name, 'parsed'); + assert.approximately(e.timeStamp, Date.now(), 10); + assert.strictEqual(e.state.inlineMode, false); + + assert.strictEqual(firedPre, true); + assert.strictEqual(firedPost, false); + firedPost = true; + } + + function preInline(e) { + assert.strictEqual(subscribed, true); + + assert.strictEqual(e.name, 'willparse'); + assert.approximately(e.timeStamp, Date.now(), 10); + assert.strictEqual(e.state.inlineMode, true); + + assert.strictEqual(firedPre, false); + assert.strictEqual(firedPost, false); + firedPre = true; + } + + function postInline(e) { + assert.strictEqual(subscribed, true); + + assert.strictEqual(e.name, 'parsed'); + assert.approximately(e.timeStamp, Date.now(), 10); + assert.strictEqual(e.state.inlineMode, true); + + assert.strictEqual(firedPre, true); + assert.strictEqual(firedPost, false); + firedPost = true; + } + + md.addEventListener('willparse', preBlock); + md.addEventListener('willparse', preBlock); // double-adding does nothing + md.addEventListener('parsed', postBlock); + + md.render('*a*'); + + assert.strictEqual(firedPre, true); + assert.strictEqual(firedPost, true); + + md.removeEventListener('willparse', preBlock); + md.removeEventListener('willparse', preBlock); // double-removing does nothing + md.removeEventListener('parsed', postBlock); + + firedPre = firedPost = false; + + md.addEventListener('willparse', preInline); + md.addEventListener('parsed', postInline); + + md.renderInline('*a*'); + + assert.strictEqual(firedPre, true); + assert.strictEqual(firedPost, true); + + md.removeEventListener('willparse', preInline); + md.removeEventListener('parsed', postInline); + + firedPre = firedPost = false; + + md.render('*a*'); + md.renderInline('*a*'); + + assert.strictEqual(firedPre, false); + assert.strictEqual(firedPost, false); + }); + });