769 lines
25 KiB
JavaScript
769 lines
25 KiB
JavaScript
window['Rainbow'] = (function() {
|
|
|
|
/**
|
|
* array of replacements to process at the end
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
var replacements = {},
|
|
|
|
/**
|
|
* an array of start and end positions of blocks to be replaced
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
replacement_positions = {},
|
|
|
|
/**
|
|
* an array of the language patterns specified for each language
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
language_patterns = {},
|
|
|
|
/**
|
|
* an array of languages and whether they should bypass the default patterns
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
bypass_defaults = {},
|
|
|
|
/**
|
|
* processing level
|
|
*
|
|
* replacements are stored at this level so if there is a sub block of code
|
|
* (for example php inside of html) it runs at a different level
|
|
*
|
|
* @type {number}
|
|
*/
|
|
CURRENT_LEVEL = 0,
|
|
|
|
/**
|
|
* constant used to refer to the default language
|
|
*
|
|
* @type {number}
|
|
*/
|
|
DEFAULT_LANGUAGE = 0,
|
|
|
|
/**
|
|
* used as counters so we can selectively call setTimeout
|
|
* after processing a certain number of matches/replacements
|
|
*
|
|
* @type {number}
|
|
*/
|
|
match_counter = 0,
|
|
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
replacement_counter = 0,
|
|
|
|
/**
|
|
* @type {null|string}
|
|
*/
|
|
global_class,
|
|
|
|
/**
|
|
* @type {null|Function}
|
|
*/
|
|
onHighlight;
|
|
|
|
/**
|
|
* cross browser get attribute for an element
|
|
*
|
|
* @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method
|
|
*
|
|
* @param {Node} el
|
|
* @param {string} attr attribute you are trying to get
|
|
* @returns {string|number}
|
|
*/
|
|
function _attr(el, attr, attrs, i) {
|
|
var result = (el.getAttribute && el.getAttribute(attr)) || 0;
|
|
|
|
if (!result) {
|
|
attrs = el.attributes;
|
|
|
|
for (i = 0; i < attrs.length; ++i) {
|
|
if (attrs[i].nodeName === attr) {
|
|
return attrs[i].nodeValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* adds a class to a given code block
|
|
*
|
|
* @param {Element} el
|
|
* @param {string} class_name class name to add
|
|
* @returns void
|
|
*/
|
|
function _addClass(el, class_name) {
|
|
el.className += el.className ? ' ' + class_name : class_name;
|
|
}
|
|
|
|
/**
|
|
* checks if a block has a given class
|
|
*
|
|
* @param {Element} el
|
|
* @param {string} class_name class name to check for
|
|
* @returns {boolean}
|
|
*/
|
|
function _hasClass(el, class_name) {
|
|
return (' ' + el.className + ' ').indexOf(' ' + class_name + ' ') > -1;
|
|
}
|
|
|
|
/**
|
|
* gets the language for this block of code
|
|
*
|
|
* @param {Element} block
|
|
* @returns {string|null}
|
|
*/
|
|
function _getLanguageForBlock(block) {
|
|
|
|
// if this doesn't have a language but the parent does then use that
|
|
// this means if for example you have: <pre data-language="php">
|
|
// with a bunch of <code> blocks inside then you do not have
|
|
// to specify the language for each block
|
|
var language = _attr(block, 'data-language') || _attr(block.parentNode, 'data-language');
|
|
|
|
// this adds support for specifying language via a css class
|
|
// you can use the Google Code Prettify style: <pre class="lang-php">
|
|
// or the HTML5 style: <pre><code class="language-php">
|
|
if (!language) {
|
|
var pattern = /\blang(?:uage)?-(\w+)/,
|
|
match = block.className.match(pattern) || block.parentNode.className.match(pattern);
|
|
|
|
if (match) {
|
|
language = match[1];
|
|
}
|
|
}
|
|
|
|
return language;
|
|
}
|
|
|
|
/**
|
|
* makes sure html entities are always used for tags
|
|
*
|
|
* @param {string} code
|
|
* @returns {string}
|
|
*/
|
|
function _htmlEntities(code) {
|
|
return code.replace(/</g, '<').replace(/>/g, '>').replace(/&(?![\w\#]+;)/g, '&');
|
|
}
|
|
|
|
/**
|
|
* determines if a new match intersects with an existing one
|
|
*
|
|
* @param {number} start1 start position of existing match
|
|
* @param {number} end1 end position of existing match
|
|
* @param {number} start2 start position of new match
|
|
* @param {number} end2 end position of new match
|
|
* @returns {boolean}
|
|
*/
|
|
function _intersects(start1, end1, start2, end2) {
|
|
if (start2 >= start1 && start2 < end1) {
|
|
return true;
|
|
}
|
|
|
|
return end2 > start1 && end2 < end1;
|
|
}
|
|
|
|
/**
|
|
* determines if two different matches have complete overlap with each other
|
|
*
|
|
* @param {number} start1 start position of existing match
|
|
* @param {number} end1 end position of existing match
|
|
* @param {number} start2 start position of new match
|
|
* @param {number} end2 end position of new match
|
|
* @returns {boolean}
|
|
*/
|
|
function _hasCompleteOverlap(start1, end1, start2, end2) {
|
|
|
|
// if the starting and end positions are exactly the same
|
|
// then the first one should stay and this one should be ignored
|
|
if (start2 == start1 && end2 == end1) {
|
|
return false;
|
|
}
|
|
|
|
return start2 <= start1 && end2 >= end1;
|
|
}
|
|
|
|
/**
|
|
* determines if the match passed in falls inside of an existing match
|
|
* this prevents a regex pattern from matching inside of a bigger pattern
|
|
*
|
|
* @param {number} start - start position of new match
|
|
* @param {number} end - end position of new match
|
|
* @returns {boolean}
|
|
*/
|
|
function _matchIsInsideOtherMatch(start, end) {
|
|
for (var key in replacement_positions[CURRENT_LEVEL]) {
|
|
key = parseInt(key, 10);
|
|
|
|
// if this block completely overlaps with another block
|
|
// then we should remove the other block and return false
|
|
if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
|
|
delete replacement_positions[CURRENT_LEVEL][key];
|
|
delete replacements[CURRENT_LEVEL][key];
|
|
}
|
|
|
|
if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* takes a string of code and wraps it in a span tag based on the name
|
|
*
|
|
* @param {string} name name of the pattern (ie keyword.regex)
|
|
* @param {string} code block of code to wrap
|
|
* @returns {string}
|
|
*/
|
|
function _wrapCodeInSpan(name, code) {
|
|
return '<span class="' + name.replace(/\./g, ' ') + (global_class ? ' ' + global_class : '') + '">' + code + '</span>';
|
|
}
|
|
|
|
/**
|
|
* finds out the position of group match for a regular expression
|
|
*
|
|
* @see http://stackoverflow.com/questions/1985594/how-to-find-index-of-groups-in-match
|
|
*
|
|
* @param {Object} match
|
|
* @param {number} group_number
|
|
* @returns {number}
|
|
*/
|
|
function _indexOfGroup(match, group_number) {
|
|
var index = 0,
|
|
i;
|
|
|
|
for (i = 1; i < group_number; ++i) {
|
|
if (match[i]) {
|
|
index += match[i].length;
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* matches a regex pattern against a block of code
|
|
* finds all matches that should be processed and stores the positions
|
|
* of where they should be replaced within the string
|
|
*
|
|
* this is where pretty much all the work is done but it should not
|
|
* be called directly
|
|
*
|
|
* @param {RegExp} pattern
|
|
* @param {string} code
|
|
* @returns void
|
|
*/
|
|
function _processPattern(regex, pattern, code, callback)
|
|
{
|
|
var match = regex.exec(code);
|
|
|
|
if (!match) {
|
|
return callback();
|
|
}
|
|
|
|
++match_counter;
|
|
|
|
// treat match 0 the same way as name
|
|
if (!pattern['name'] && typeof pattern['matches'][0] == 'string') {
|
|
pattern['name'] = pattern['matches'][0];
|
|
delete pattern['matches'][0];
|
|
}
|
|
|
|
var replacement = match[0],
|
|
start_pos = match.index,
|
|
end_pos = match[0].length + start_pos,
|
|
|
|
/**
|
|
* callback to process the next match of this pattern
|
|
*/
|
|
processNext = function() {
|
|
var nextCall = function() {
|
|
_processPattern(regex, pattern, code, callback);
|
|
};
|
|
|
|
// every 100 items we process let's call set timeout
|
|
// to let the ui breathe a little
|
|
return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0);
|
|
};
|
|
|
|
// if this is not a child match and it falls inside of another
|
|
// match that already happened we should skip it and continue processing
|
|
if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
|
|
return processNext();
|
|
}
|
|
|
|
/**
|
|
* callback for when a match was successfully processed
|
|
*
|
|
* @param {string} replacement
|
|
* @returns void
|
|
*/
|
|
var onMatchSuccess = function(replacement) {
|
|
// if this match has a name then wrap it in a span tag
|
|
if (pattern['name']) {
|
|
replacement = _wrapCodeInSpan(pattern['name'], replacement);
|
|
}
|
|
|
|
// console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);
|
|
|
|
// store what needs to be replaced with what at this position
|
|
if (!replacements[CURRENT_LEVEL]) {
|
|
replacements[CURRENT_LEVEL] = {};
|
|
replacement_positions[CURRENT_LEVEL] = {};
|
|
}
|
|
|
|
replacements[CURRENT_LEVEL][start_pos] = {
|
|
'replace': match[0],
|
|
'with': replacement
|
|
};
|
|
|
|
// store the range of this match so we can use it for comparisons
|
|
// with other matches later
|
|
replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;
|
|
|
|
// process the next match
|
|
processNext();
|
|
},
|
|
|
|
// if this pattern has sub matches for different groups in the regex
|
|
// then we should process them one at a time by rerunning them through
|
|
// this function to generate the new replacement
|
|
//
|
|
// we run through them backwards because the match position of earlier
|
|
// matches will not change depending on what gets replaced in later
|
|
// matches
|
|
group_keys = keys(pattern['matches']),
|
|
|
|
/**
|
|
* callback for processing a sub group
|
|
*
|
|
* @param {number} i
|
|
* @param {Array} group_keys
|
|
* @param {Function} callback
|
|
*/
|
|
processGroup = function(i, group_keys, callback) {
|
|
if (i >= group_keys.length) {
|
|
return callback(replacement);
|
|
}
|
|
|
|
var processNextGroup = function() {
|
|
processGroup(++i, group_keys, callback);
|
|
},
|
|
block = match[group_keys[i]];
|
|
|
|
// if there is no match here then move on
|
|
if (!block) {
|
|
return processNextGroup();
|
|
}
|
|
|
|
var group = pattern['matches'][group_keys[i]],
|
|
language = group['language'],
|
|
|
|
/**
|
|
* process group is what group we should use to actually process
|
|
* this match group
|
|
*
|
|
* for example if the subgroup pattern looks like this
|
|
* 2: {
|
|
* 'name': 'keyword',
|
|
* 'pattern': /true/g
|
|
* }
|
|
*
|
|
* then we use that as is, but if it looks like this
|
|
*
|
|
* 2: {
|
|
* 'name': 'keyword',
|
|
* 'matches': {
|
|
* 'name': 'special',
|
|
* 'pattern': /whatever/g
|
|
* }
|
|
* }
|
|
*
|
|
* we treat the 'matches' part as the pattern and keep
|
|
* the name around to wrap it with later
|
|
*/
|
|
process_group = group['name'] && group['matches'] ? group['matches'] : group,
|
|
|
|
/**
|
|
* takes the code block matched at this group, replaces it
|
|
* with the highlighted block, and optionally wraps it with
|
|
* a span with a name
|
|
*
|
|
* @param {string} block
|
|
* @param {string} replace_block
|
|
* @param {string|null} match_name
|
|
*/
|
|
_replaceAndContinue = function(block, replace_block, match_name) {
|
|
replacement = _replaceAtPosition(_indexOfGroup(match, group_keys[i]), block, match_name ? _wrapCodeInSpan(match_name, replace_block) : replace_block, replacement);
|
|
processNextGroup();
|
|
};
|
|
|
|
// if this is a sublanguage go and process the block using that language
|
|
if (language) {
|
|
return _highlightBlockForLanguage(block, language, function(code) {
|
|
_replaceAndContinue(block, code);
|
|
});
|
|
}
|
|
|
|
// if this is a string then this match is directly mapped to selector
|
|
// so all we have to do is wrap it in a span and continue
|
|
if (typeof group === 'string') {
|
|
return _replaceAndContinue(block, block, group);
|
|
}
|
|
|
|
// the process group can be a single pattern or an array of patterns
|
|
// _processCodeWithPatterns always expects an array so we convert it here
|
|
_processCodeWithPatterns(block, process_group.length ? process_group : [process_group], function(code) {
|
|
_replaceAndContinue(block, code, group['matches'] ? group['name'] : 0);
|
|
});
|
|
};
|
|
|
|
processGroup(0, group_keys, onMatchSuccess);
|
|
}
|
|
|
|
/**
|
|
* should a language bypass the default patterns?
|
|
*
|
|
* if you call Rainbow.extend() and pass true as the third argument
|
|
* it will bypass the defaults
|
|
*/
|
|
function _bypassDefaultPatterns(language)
|
|
{
|
|
return bypass_defaults[language];
|
|
}
|
|
|
|
/**
|
|
* returns a list of regex patterns for this language
|
|
*
|
|
* @param {string} language
|
|
* @returns {Array}
|
|
*/
|
|
function _getPatternsForLanguage(language) {
|
|
var patterns = language_patterns[language] || [],
|
|
default_patterns = language_patterns[DEFAULT_LANGUAGE] || [];
|
|
|
|
return _bypassDefaultPatterns(language) ? patterns : patterns.concat(default_patterns);
|
|
}
|
|
|
|
/**
|
|
* substring replace call to replace part of a string at a certain position
|
|
*
|
|
* @param {number} position the position where the replacement should happen
|
|
* @param {string} replace the text we want to replace
|
|
* @param {string} replace_with the text we want to replace it with
|
|
* @param {string} code the code we are doing the replacing in
|
|
* @returns {string}
|
|
*/
|
|
function _replaceAtPosition(position, replace, replace_with, code) {
|
|
var sub_string = code.substr(position);
|
|
return code.substr(0, position) + sub_string.replace(replace, replace_with);
|
|
}
|
|
|
|
/**
|
|
* sorts an object by index descending
|
|
*
|
|
* @param {Object} object
|
|
* @return {Array}
|
|
*/
|
|
function keys(object) {
|
|
var locations = [],
|
|
replacement,
|
|
pos;
|
|
|
|
for(var location in object) {
|
|
if (object.hasOwnProperty(location)) {
|
|
locations.push(location);
|
|
}
|
|
}
|
|
|
|
// numeric descending
|
|
return locations.sort(function(a, b) {
|
|
return b - a;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* processes a block of code using specified patterns
|
|
*
|
|
* @param {string} code
|
|
* @param {Array} patterns
|
|
* @returns void
|
|
*/
|
|
function _processCodeWithPatterns(code, patterns, callback)
|
|
{
|
|
// we have to increase the level here so that the
|
|
// replacements will not conflict with each other when
|
|
// processing sub blocks of code
|
|
++CURRENT_LEVEL;
|
|
|
|
// patterns are processed one at a time through this function
|
|
function _workOnPatterns(patterns, i)
|
|
{
|
|
// still have patterns to process, keep going
|
|
if (i < patterns.length) {
|
|
return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
|
|
_workOnPatterns(patterns, ++i);
|
|
});
|
|
}
|
|
|
|
// we are done processing the patterns
|
|
// process the replacements and update the DOM
|
|
_processReplacements(code, function(code) {
|
|
|
|
// when we are done processing replacements
|
|
// we are done at this level so we can go back down
|
|
delete replacements[CURRENT_LEVEL];
|
|
delete replacement_positions[CURRENT_LEVEL];
|
|
--CURRENT_LEVEL;
|
|
callback(code);
|
|
});
|
|
}
|
|
|
|
_workOnPatterns(patterns, 0);
|
|
}
|
|
|
|
/**
|
|
* process replacements in the string of code to actually update the markup
|
|
*
|
|
* @param {string} code the code to process replacements in
|
|
* @param {Function} onComplete what to do when we are done processing
|
|
* @returns void
|
|
*/
|
|
function _processReplacements(code, onComplete) {
|
|
|
|
/**
|
|
* processes a single replacement
|
|
*
|
|
* @param {string} code
|
|
* @param {Array} positions
|
|
* @param {number} i
|
|
* @param {Function} onComplete
|
|
* @returns void
|
|
*/
|
|
function _processReplacement(code, positions, i, onComplete) {
|
|
if (i < positions.length) {
|
|
++replacement_counter;
|
|
var pos = positions[i],
|
|
replacement = replacements[CURRENT_LEVEL][pos];
|
|
code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
|
|
|
|
// process next function
|
|
var next = function() {
|
|
_processReplacement(code, positions, ++i, onComplete);
|
|
};
|
|
|
|
// use a timeout every 250 to not freeze up the UI
|
|
return replacement_counter % 250 > 0 ? next() : setTimeout(next, 0);
|
|
}
|
|
|
|
onComplete(code);
|
|
}
|
|
|
|
var string_positions = keys(replacements[CURRENT_LEVEL]);
|
|
_processReplacement(code, string_positions, 0, onComplete);
|
|
}
|
|
|
|
/**
|
|
* takes a string of code and highlights it according to the language specified
|
|
*
|
|
* @param {string} code
|
|
* @param {string} language
|
|
* @param {Function} onComplete
|
|
* @returns void
|
|
*/
|
|
function _highlightBlockForLanguage(code, language, onComplete) {
|
|
var patterns = _getPatternsForLanguage(language);
|
|
_processCodeWithPatterns(_htmlEntities(code), patterns, onComplete);
|
|
}
|
|
|
|
/**
|
|
* highlight an individual code block
|
|
*
|
|
* @param {Array} code_blocks
|
|
* @param {number} i
|
|
* @returns void
|
|
*/
|
|
function _highlightCodeBlock(code_blocks, i, onComplete) {
|
|
|
|
|
|
if (i < code_blocks.length) {
|
|
var block = code_blocks[i],
|
|
language = _getLanguageForBlock(block);
|
|
|
|
if (!_hasClass(block, 'rainbow') && language) {
|
|
language = language.toLowerCase();
|
|
|
|
_addClass(block, 'rainbow');
|
|
|
|
return _highlightBlockForLanguage(block.innerHTML, language, function(code) {
|
|
block.innerHTML = code;
|
|
|
|
// reset the replacement arrays
|
|
replacements = {};
|
|
replacement_positions = {};
|
|
|
|
// if you have a listener attached tell it that this block is now highlighted
|
|
if (onHighlight) {
|
|
onHighlight(block, language);
|
|
}
|
|
|
|
// process the next block
|
|
setTimeout(function() {
|
|
_highlightCodeBlock(code_blocks, ++i, onComplete);
|
|
}, 0);
|
|
});
|
|
}
|
|
return _highlightCodeBlock(code_blocks, ++i, onComplete);
|
|
}
|
|
|
|
if (onComplete) {
|
|
onComplete();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* start highlighting all the code blocks
|
|
*
|
|
* @returns void
|
|
*/
|
|
function _highlight(node, onComplete) {
|
|
|
|
// the first argument can be an Event or a DOM Element
|
|
// I was originally checking instanceof Event but that makes it break
|
|
// when using mootools
|
|
//
|
|
// @see https://github.com/ccampbell/rainbow/issues/32
|
|
//
|
|
node = node && typeof node.getElementsByTagName == 'function' ? node : document;
|
|
|
|
var pre_blocks = node.getElementsByTagName('pre'),
|
|
code_blocks = node.getElementsByTagName('code'),
|
|
i,
|
|
final_blocks = [];
|
|
|
|
// alert(vl);
|
|
|
|
// @see http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript
|
|
// we are going to process all <code> blocks
|
|
for (i = 0; i < code_blocks.length; ++i) {
|
|
final_blocks.push(code_blocks[i]);
|
|
}
|
|
|
|
// loop through the pre blocks to see which ones we should add
|
|
for (i = 0; i < pre_blocks.length; ++i) {
|
|
|
|
// if the pre block has no code blocks then process it directly
|
|
if (!pre_blocks[i].getElementsByTagName('code').length) {
|
|
final_blocks.push(pre_blocks[i]);
|
|
}
|
|
}
|
|
|
|
_highlightCodeBlock(final_blocks, 0, onComplete);
|
|
}
|
|
|
|
/**
|
|
* public methods
|
|
*/
|
|
return {
|
|
|
|
/**
|
|
* extends the language pattern matches
|
|
*
|
|
* @param {*} language name of language
|
|
* @param {*} patterns array of patterns to add on
|
|
* @param {boolean|null} bypass if true this will bypass the default language patterns
|
|
*/
|
|
extend: function(language, patterns, bypass) {
|
|
|
|
// if there is only one argument then we assume that we want to
|
|
// extend the default language rules
|
|
if (arguments.length == 1) {
|
|
patterns = language;
|
|
language = DEFAULT_LANGUAGE;
|
|
}
|
|
|
|
bypass_defaults[language] = bypass;
|
|
language_patterns[language] = patterns.concat(language_patterns[language] || []);
|
|
},
|
|
|
|
/**
|
|
* call back to let you do stuff in your app after a piece of code has been highlighted
|
|
*
|
|
* @param {Function} callback
|
|
*/
|
|
onHighlight: function(callback) {
|
|
onHighlight = callback;
|
|
},
|
|
|
|
/**
|
|
* method to set a global class that will be applied to all spans
|
|
*
|
|
* @param {string} class_name
|
|
*/
|
|
addClass: function(class_name) {
|
|
global_class = class_name;
|
|
},
|
|
|
|
/**
|
|
* starts the magic rainbow
|
|
*
|
|
* @returns void
|
|
*/
|
|
color: function() {
|
|
|
|
// if you want to straight up highlight a string you can pass the string of code,
|
|
// the language, and a callback function
|
|
if (typeof arguments[0] == 'string') {
|
|
|
|
return _highlightBlockForLanguage(arguments[0], arguments[1], arguments[2]);
|
|
|
|
}
|
|
|
|
// if you pass a callback function then we rerun the color function
|
|
// on all the code and call the callback function on complete
|
|
if (typeof arguments[0] == 'function') {
|
|
return _highlight(0, arguments[0]);
|
|
}
|
|
//alert("lol");
|
|
// otherwise we use whatever node you passed in with an optional
|
|
// callback function as the second parameter
|
|
//alert(arguments[0]);
|
|
_highlight(arguments[0], arguments[1]);
|
|
},
|
|
|
|
beautify : function() {
|
|
|
|
_highlight(arguments[0], arguments[1]);
|
|
|
|
|
|
|
|
}
|
|
};
|
|
}) ();
|
|
|
|
/**
|
|
* adds event listener to start highlighting
|
|
*/
|
|
(function() {
|
|
if (window.addEventListener) {
|
|
return window.addEventListener('load', Rainbow.color, false);
|
|
}
|
|
window.attachEvent('onload', Rainbow.color);
|
|
}) ();
|
|
|
|
// When using Google closure compiler in advanced mode some methods
|
|
// get renamed. This keeps a public reference to these methods so they can
|
|
// still be referenced from outside this library.
|
|
Rainbow["onHighlight"] = Rainbow.onHighlight;
|
|
Rainbow["addClass"] = Rainbow.addClass;
|