333 lines
8.9 KiB
JavaScript
333 lines
8.9 KiB
JavaScript
/*
|
|
---
|
|
name: EnlighterJS
|
|
description: Syntax Highlighter based on the famous Lighter.js
|
|
|
|
license: MIT-style X11 License
|
|
|
|
authors:
|
|
- Andi Dittrich
|
|
|
|
requires:
|
|
- Core/1.4.5
|
|
|
|
provides: [EnlighterJS]
|
|
...
|
|
*/
|
|
var EJS = window.EnlighterJS = new Class({
|
|
|
|
Implements : Options,
|
|
|
|
options : {
|
|
language : 'generic',
|
|
theme : 'Enlighter',
|
|
renderer: 'Block',
|
|
indent : -1,
|
|
forceTheme: false,
|
|
rawButton: true,
|
|
windowButton: true,
|
|
infoButton: true,
|
|
ampersandCleanup: true,
|
|
rawcodeDoubleclick: false
|
|
},
|
|
|
|
// used renderer instance
|
|
renderer: null,
|
|
|
|
// used codeblock to highlight
|
|
originalCodeblock: null,
|
|
|
|
// used container to store highlighted code
|
|
container: null,
|
|
|
|
// lightning active ?
|
|
isRendered: false,
|
|
|
|
// language alias manager
|
|
languageManager: null,
|
|
|
|
// toggle raw code
|
|
rawContentContainer: null,
|
|
|
|
// rendered output span/ou/ul container
|
|
output: null,
|
|
|
|
// input/output filter
|
|
textFilter: null,
|
|
|
|
// cached code input
|
|
rawCode: null,
|
|
|
|
/**
|
|
* @constructs
|
|
* @param {Element} originalCodeblock An Element containing code to highlight
|
|
* @param {Object} options The options object.
|
|
* @param {Element} container (optional) The output container - if not defined, the output will be injected after the originalCodeblock
|
|
*/
|
|
initialize : function(originalCodeblock, opt, container) {
|
|
this.setOptions(opt);
|
|
|
|
// create new language alias manager instance
|
|
this.languageManager = new EJS.LanguageManager(this.options);
|
|
|
|
// create new coe filter instance
|
|
this.textFilter = new EJS.TextFilter(this.options);
|
|
|
|
// initialize renderer
|
|
if (this.options.renderer == 'Inline'){
|
|
this.renderer = new EJS.Renderer.InlineRenderer(this.options, this.textFilter);
|
|
}else{
|
|
this.renderer = new EJS.Renderer.BlockRenderer(this.options, this.textFilter);
|
|
}
|
|
|
|
// store codeblock element
|
|
this.originalCodeblock = EJS.Dom.id(originalCodeblock);
|
|
|
|
// store/create container
|
|
if (container){
|
|
this.container = EJS.Dom.id(container);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Takes a codeblock and highlights the code inside of it using the
|
|
* stored parser/compilers. It reads the class name to figure out what
|
|
* language and theme to use for highlighting.
|
|
*
|
|
* @return {EnlighterJS} The current EnlighterJS instance.
|
|
*/
|
|
enlight : function(enabled){
|
|
// show highlighted sourcecode ?
|
|
if (enabled){
|
|
// get element language
|
|
var rawLanguageName = this.originalCodeblock.get('data-enlighter-language');
|
|
|
|
// ignore higlighting ?
|
|
if (rawLanguageName == 'no-highlight'){
|
|
return;
|
|
}
|
|
|
|
// hide original codeblock
|
|
this.originalCodeblock.setStyle('display', 'none');
|
|
|
|
// EnlighterJS exists so just toggle display.
|
|
if (this.isRendered) {
|
|
this.container.setStyle('display', 'inherit');
|
|
return this;
|
|
}
|
|
|
|
// get language name - use alias manager to check language string and validate
|
|
var languageName = this.languageManager.getLanguage(rawLanguageName);
|
|
|
|
// get theme name - use options as fallback
|
|
var themeName = (this.options.forceTheme ? null : this.originalCodeblock.get('data-enlighter-theme')) || this.options.theme || 'Enlighter';
|
|
|
|
// special lines to highlight ?
|
|
var specialLines = new EJS.SpecialLineHighlighter(this.originalCodeblock.get('data-enlighter-highlight'), this.originalCodeblock.get('data-enlighter-lineoffset'));
|
|
|
|
// Load language parser
|
|
var language = new EJS.Language[languageName](this.getRawCode(true));
|
|
|
|
// compile tokens -> generate output
|
|
this.output = this.renderer.render(language, specialLines, {
|
|
lineOffset: (this.originalCodeblock.get('data-enlighter-lineoffset') || null),
|
|
lineNumbers: this.originalCodeblock.get('data-enlighter-linenumbers')
|
|
});
|
|
|
|
// set class and id attributes.
|
|
this.output.addClass(themeName.toLowerCase() + 'EnlighterJS').addClass('EnlighterJS');
|
|
|
|
// add wrapper ?
|
|
if (this.options.renderer == 'Block'){
|
|
// grab content into specific container or after original code block ?
|
|
if (!this.container) {
|
|
this.container = new EJS.Dom.Element('div');
|
|
|
|
// put the highlighted code wrapper behind the original
|
|
this.container.inject(this.originalCodeblock, 'after');
|
|
}
|
|
|
|
// add wrapper class
|
|
this.container.addClass('EnlighterJSWrapper').addClass(themeName.toLowerCase() + 'EnlighterJSWrapper');
|
|
|
|
// add the highlighted code
|
|
this.container.grab(this.output);
|
|
|
|
// create raw content container
|
|
this.rawContentContainer = new EJS.Dom.Element('pre', {
|
|
text: this.getRawCode(false),
|
|
styles: {
|
|
'display': 'none'
|
|
}
|
|
});
|
|
|
|
// add raw content container
|
|
this.container.grab(this.rawContentContainer);
|
|
|
|
// show raw code on double-click ?
|
|
if (this.options.rawcodeDoubleclick){
|
|
this.container.addEvent('dblclick', function(){
|
|
this.toggleRawCode();
|
|
}.bind(this));
|
|
}
|
|
|
|
// toolbar ?
|
|
if (this.options.rawButton || this.options.windowButton || this.options.infoButton){
|
|
this.container.grab(new EJS.UI.Toolbar(this));
|
|
}
|
|
|
|
// normal handling
|
|
}else{
|
|
// grab content into specific container or after original code block ?
|
|
if (this.container) {
|
|
this.container.grab(this.output);
|
|
|
|
// just put the highlighted code behind the original
|
|
}else{
|
|
this.output.inject(this.originalCodeblock, 'after');
|
|
this.container = this.output;
|
|
}
|
|
}
|
|
|
|
// set render flag
|
|
this.isRendered = true;
|
|
|
|
// disable highlighting
|
|
}else{
|
|
// already highlighted ?
|
|
if (this.isRendered) {
|
|
this.originalCodeblock.setStyle('display', 'inherit');
|
|
this.container.setStyle('display', 'none');
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Disable highlighting and remove generated DOM
|
|
*/
|
|
dispose: function(){
|
|
// already highlighted ?
|
|
if (!this.isRendered) {
|
|
return;
|
|
}
|
|
|
|
// restore original codeblock
|
|
this.originalCodeblock.setStyle('display', null);
|
|
|
|
// hide highlighted code
|
|
this.container.setStyle('display', 'none');
|
|
this.rawContentContainer.setStyle('display', 'none');
|
|
|
|
// drop dom
|
|
this.container.dispose();
|
|
this.rawContentContainer.dispose();
|
|
this.container = null;
|
|
this.rawContentContainer = null;
|
|
|
|
// reset flag
|
|
this.isRendered = false;
|
|
},
|
|
|
|
/**
|
|
* Extracts the raw code from given codeblock
|
|
* @return {String} The plain-text code (raw)
|
|
*/
|
|
getRawCode: function(reindent){
|
|
|
|
// cached version available ?
|
|
var code = this.rawCode;
|
|
|
|
if (code==null) {
|
|
// get the raw content
|
|
code = this.originalCodeblock.get('html');
|
|
|
|
// remove empty lines at the beginning+end of the codeblock
|
|
code = code.replace(/(^\s*\n|\n\s*$)/gi, '');
|
|
|
|
// apply input filter
|
|
code = this.textFilter.filterInput(code);
|
|
|
|
// cleanup ampersand ?
|
|
if (this.options.ampersandCleanup === true) {
|
|
code = code.replace(/&/gim, '&');
|
|
}
|
|
|
|
// replace html escaped chars
|
|
code = code.replace(/</gim, '<').replace(/>/gim, '>').replace(/ /gim, ' ');
|
|
|
|
// cache it
|
|
this.rawCode = code;
|
|
}
|
|
|
|
// replace tabs with spaces ?
|
|
if (reindent === true){
|
|
// get indent option value
|
|
var newIndent = this.options.indent.toInt();
|
|
|
|
// re-indent code if specified
|
|
if (newIndent > -1){
|
|
// match all tabs
|
|
code = code.replace(/(\t*)/gim, function(match, p1, offset, string){
|
|
// replace n tabs with n*newIndent spaces
|
|
return (new Array(newIndent * p1.length + 1)).join(' ');
|
|
});
|
|
}
|
|
}
|
|
|
|
return code;
|
|
},
|
|
|
|
/**
|
|
* Hide/Show the RAW Code Container/Toggle Highlighted Code
|
|
*/
|
|
toggleRawCode: function(show){
|
|
// initialization required!
|
|
if (this.output == null){
|
|
return;
|
|
}
|
|
|
|
// argument set ?
|
|
if (typeof(show)!='boolean'){
|
|
show = (this.rawContentContainer.getStyle('display') == 'none');
|
|
}
|
|
|
|
// toggle container visibility
|
|
if (show){
|
|
this.output.setStyle('display', 'none');
|
|
this.rawContentContainer.setStyle('display', 'block');
|
|
}else{
|
|
this.output.setStyle('display', 'block');
|
|
this.rawContentContainer.setStyle('display', 'none');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Takes a codeblock and highlights the code inside. The original codeblock is set to invisible
|
|
* @DEPRECATED since v2.0 - this method will be removed in the future
|
|
*
|
|
* @return {EnlighterJS} The current EnlighterJS instance.
|
|
*/
|
|
light : function(){
|
|
return this.enlight(true);
|
|
},
|
|
|
|
/**
|
|
* Unlights a codeblock by hiding the enlighter element if present and re-displaying the original code.
|
|
* @DEPRECATED since v2.0 - this method will be removed in the future
|
|
*
|
|
* @return {EnlighterJS} The current EnlighterJS instance.
|
|
*/
|
|
unlight : function() {
|
|
return this.enlight(false);
|
|
}
|
|
});
|
|
|
|
// register namespaces
|
|
EJS.Language = {};
|
|
EJS.Tokenizer = {};
|
|
EJS.Renderer = {};
|
|
EJS.Util = {};
|
|
EJS.UI = {};
|
|
|