UNPKG

bem-md-renderer

Version:

The builder markdown to html with custom renderer, which generates the html headlines like github

234 lines (200 loc) 7.25 kB
var marked = require('marked'), extend = require('util')._extend, usedAnchors = {}, renderer; /** * Render html from markdown with custom bem-site renderer * @param {String} markdown(required) - source md * @param {Options} options - custom options to marked module * @param {Function} cb(required) - callback function */ function render(markdown, options, cb) { if (!markdown || !typeof markdown === 'string') throw new Error('First argument must be a markdown string'); if (typeof options === 'function') { cb = options; options = {}; } // render html from markdown return marked(markdown, extend({ gfm: true, pedantic: false, sanitize: false, // in many cases md uses html to insert iframe or img on bem.info renderer: getRenderer() }, options), cb); } /** * Create instance of new Marked renderer * Contain custom renderer for: * 1. Heading, GitHub style with anchors and links inside * 2. Table for fix trouble with page scroll, when table is so wide * @returns {*|Renderer} */ function createRenderer() { var Renderer = marked.Renderer, baseRenderer = Renderer.prototype; renderer = new Renderer(); /** * Fix marked issue with cyrillic symbols replacing. * * @param {String} text test of header * @param {Number} level index of header * @param {String} raw * @param {Object} options options * @returns {String} result header string */ renderer.heading = function (text, level, raw, options) { var anchor; options = options || {}; options.headerPrefix = options.headerPrefix || ''; anchor = options.headerPrefix + getAnchor(raw); anchor = modifyDuplicate(anchor); return '<h' + level + ' id="' + anchor + '"><a href="#' + anchor + '" class="anchor"></a>' + text + '</h' + level + '>\n'; }; /** * Replace video`s share service links to iframe video block * Examples markdown: * [Youtube 1](https://youtu.be/1GWoMnYldYc) * [Youtube 2](https://www.youtube.com/watch?v=4jrUgqMlvP0) * [Yandex](https://video.yandex.ru/iframe/ya-events/clg24o8tu3.7046/) * [Vimeo 1](https://vimeo.com/53219242/) * [Vimeo 2](https://player.vimeo.com/video/30838628) * * p.s. By default gets base renderer link * @param {String} href * @param {String} title * @param {String} text */ renderer.link = function (href, title, text) { var result = baseRenderer.link.apply(this, arguments), services = { youtube: ['youtu.be', 'youtube.com'], yandex: [ 'video.yandex.ru', 'static.video.yandex.ru' ], vimeo: ['vimeo.com', 'player.vimeo.com'] }; Object.keys(services).forEach(function (service) { var isSupportedService = services[service].some(function (link) { return href.indexOf(link) > -1; }); // href contains link to support video service if (isSupportedService) { result = getIframe(href, service); return false; } }); return result; }; // Fix(hidden) post scroll, when it contains wide table renderer.table = function (header, body) { return '<div class="table-container">' + '<table>\n' + '<thead>\n' + header + '</thead>\n' + '<tbody>\n' + body + '</tbody>\n' + '</table>\n' + '</div>'; }; // Add container for inline html tables renderer.html = function (source) { var newHtml = source.replace(/<table>/, '<div class="table-container"><table>'); return newHtml.replace(/<\/table>/, '</table></div>'); }; return renderer; } /** * Get service iframe video by passed name * Required href: * - youtube: https://youtu.be/1GWoMnYldYc or https://www.youtube.com/watch?v=1GWoMnYldYc * - yandex: https://video.yandex.ru/iframe/ya-events/clg24o8tu3.7046/ * - vimeo: https://vimeo.com/30838628 or https://player.vimeo.com/video/30838628 * @param {String} href - full video href, examples see above * @param {String} serviceName - name of the service [youtube, yandex, vimeo] * @returns {String} - iframe video block */ function getIframe(href, serviceName) { var serviceToUrl = { youtube: 'www.youtube.com/embed/', yandex: 'video.yandex.ru/iframe/ya-events/', vimeo: 'player.vimeo.com/video/' }, arr = href.split('/'), key = arr.pop(); /** * Get the second last href arr item for key, * if href contains slash at the end and our key empty * For example: https://vimeo.com/53219242/ */ if (!key.length) { key = arr[arr.length - 1]; } /** * Example: * Original href = http://www.youtube.com/watch?v=1GWoMnYldYc&t=4h30m36s * key = watch?v=1GWoMnYldYc&t=4h30m36s */ if (serviceName === 'youtube') { var query = require('querystring').parse(require('url').parse(key).query); if (query.v) { key = query.v; } if (query.t) { var hours = +query.t.split('h')[0], minutes = +query.t.replace(hours + 'h', '').split('m')[0], seconds = +query.t.replace(hours + 'h' + minutes + 'm', '').replace('s', ''), timestamp = hours * 60 * 60 + minutes * 60 + seconds; key += '?start=' + timestamp; } } var src = serviceToUrl[serviceName] + key; return '<iframe src="//' + src + '" width="560" height="315" frameborder="0" allowfullscreen></iframe>'; } /** * Return an instance of custom marked renderer * Reset usedAnchors variable, * which needed to check duplicate headers in the markdown * @returns {*} */ function getRenderer() { usedAnchors = {}; return renderer || (renderer = createRenderer()); } /** * Returns an anchor for a given header * @param {String} headerText -> 'BEM templates' -> 'BEM-templates' * @returns {String} */ function getAnchor(headerText) { var anchor = headerText.replace(/( )/g, '-'), allowedChars = new RegExp('[A-Za-zА-Яа-яЁё0-9_\\- ]', 'g'); anchor = anchor.match(allowedChars) || []; var _anchor = ''; for (var i = 0; i < anchor.length; i++) { _anchor += anchor[i].match(/[A-Z]/) ? anchor[i].toLowerCase() : anchor[i]; } return _anchor; } /** * Modify duplicate headers on the page by GitHub rules * For example: we found two identical header: examples and examples * In this case, to make the anchors on these different headers, * we need add to second header anchor him count, e.g. examples-1 * @param {String} anchor source anchor * @returns {String} modify anchor */ function modifyDuplicate(anchor) { if (usedAnchors.hasOwnProperty(anchor)) { anchor += '-' + usedAnchors[anchor]++; } else { usedAnchors[anchor] = 1; } return anchor; } exports.getRenderer = getRenderer; exports.getAnchor = getAnchor; exports.render = render;