diff options
Diffstat (limited to 'lib/page/html.js')
-rw-r--r-- | lib/page/html.js | 112 |
1 files changed, 103 insertions, 9 deletions
diff --git a/lib/page/html.js b/lib/page/html.js index 71b29b2..afa2936 100644 --- a/lib/page/html.js +++ b/lib/page/html.js @@ -6,14 +6,8 @@ var slug = require('github-slugid'); var Promise = require('../utils/promise'); var location = require('../utils/location'); -// Render a cheerio DOM as html -function renderDOM($, dom, options) { - if (!dom && $._root && $._root.children) { - dom = $._root.children; - } - options = options|| dom.options || $._options; - return domSerializer(dom, options); -} +// Selector to ignore +var ANNOTATION_IGNORE = '.no-glossary,code,pre,a,script'; function HTMLPipeline(htmlString, opts) { _.bindAll(this); @@ -29,7 +23,13 @@ function HTMLPipeline(htmlString, opts) { onCodeBlock: _.identity, // Output a svg, if returns null the svg is kept inlined - onOutputSVG: _.constant(null) + onOutputSVG: _.constant(null), + + // Words to annotate + annotations: [], + + // When an annotation is applied + onAnnotation: function () { } }); this.$ = cheerio.load(htmlString, { @@ -146,6 +146,33 @@ HTMLPipeline.prototype.transformSvgs = function() { }); }; +// Annotate the content +HTMLPipeline.prototype.applyAnnotations = function() { + var that = this; + + _.each(this.opts.annotations, function(annotation) { + var searchRegex = new RegExp( '\\b(' + pregQuote(annotation.name.toLowerCase()) + ')\\b' , 'gi' ); + + that.$('*').each(function() { + var $this = that.$(this); + + if ( + $this.is(ANNOTATION_IGNORE) || + $this.parents(ANNOTATION_IGNORE).length > 0 + ) return; + + replaceText(that.$, this, searchRegex, function(match) { + that.opts.onAnnotation(annotation); + + return '<a href="'+that.opts.onRelativeLink(annotation.href) + '#' + annotation.id+'" ' + + 'class="glossary-term" title="'+_.escape(annotation.description)+'">' + + match + + '</a>'; + }); + }); + }); +}; + // Write content to the pipeline HTMLPipeline.prototype.output = function() { var that = this; @@ -156,9 +183,76 @@ HTMLPipeline.prototype.output = function() { .then(this.transformHeadings) .then(this.transformCodeBlocks) .then(this.transformSvgs) + .then(this.applyAnnotations) .then(function() { return renderDOM(that.$); }); }; + +// Render a cheerio DOM as html +function renderDOM($, dom, options) { + if (!dom && $._root && $._root.children) { + dom = $._root.children; + } + options = options|| dom.options || $._options; + return domSerializer(dom, options); +} + +// Replace text in an element +function replaceText($, el, search, replace, text_only ) { + return $(el).each(function(){ + var node = this.firstChild, + val, + new_val, + + // Elements to be removed at the end. + remove = []; + + // Only continue if firstChild exists. + if ( node ) { + + // Loop over all childNodes. + while (node) { + + // Only process text nodes. + if ( node.nodeType === 3 ) { + + // The original node value. + val = node.nodeValue; + + // The new value. + new_val = val.replace( search, replace ); + + // Only replace text if the new value is actually different! + if ( new_val !== val ) { + + if ( !text_only && /</.test( new_val ) ) { + // The new value contains HTML, set it in a slower but far more + // robust way. + $(node).before( new_val ); + + // Don't remove the node yet, or the loop will lose its place. + remove.push( node ); + } else { + // The new value contains no HTML, so it can be set in this + // very fast, simple way. + node.nodeValue = new_val; + } + } + } + + node = node.nextSibling; + } + } + + // Time to remove those elements! + if (remove.length) $(remove).remove(); + }); +} + +function pregQuote( str ) { + return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); +} + module.exports = HTMLPipeline; |