395 lines
12 KiB
JavaScript
395 lines
12 KiB
JavaScript
|
/*
|
||
|
===============================================================================
|
||
|
Chili is the jQuery code highlighter plugin
|
||
|
...............................................................................
|
||
|
Copyright 2007 / Andrea Ercolino
|
||
|
-------------------------------------------------------------------------------
|
||
|
LICENSE: http://www.opensource.org/licenses/mit-license.php
|
||
|
WEBSITE: http://noteslog.com/chili/
|
||
|
===============================================================================
|
||
|
|
||
|
===============================================================================
|
||
|
Metaobjects is the jQuery metadata plugin on steroids
|
||
|
...............................................................................
|
||
|
Copyright 2007 / Andrea Ercolino
|
||
|
-------------------------------------------------------------------------------
|
||
|
LICENSE: http://www.opensource.org/licenses/mit-license.php
|
||
|
WEBSITE: http://noteslog.com/metaobjects/
|
||
|
===============================================================================
|
||
|
*/
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
( function($) {
|
||
|
|
||
|
ChiliBook = { //implied global
|
||
|
|
||
|
version: "1.8b" // 2007-05-26
|
||
|
|
||
|
, automatic: true
|
||
|
, automaticSelector: "code"
|
||
|
|
||
|
, codeLanguage: function( el ) {
|
||
|
var recipeName = $( el ).attr( "class" );
|
||
|
return recipeName ? recipeName : '';
|
||
|
}
|
||
|
|
||
|
, metadataSelector: "object.chili" // use an empty string for not executing
|
||
|
|
||
|
, recipeLoading: true
|
||
|
, recipeFolder: "" // used like: recipeFolder + recipeName + '.js'
|
||
|
, stylesheetLoading: true
|
||
|
, stylesheetFolder: "" // used like: stylesheetFolder + recipeName + '.css'
|
||
|
|
||
|
, defaultReplacement: '<span class="$0">$$</span>'
|
||
|
|
||
|
, replaceSpace: " " // use an empty string for not replacing
|
||
|
, replaceTab: "    " // use an empty string for not replacing
|
||
|
, replaceNewLine: " <br/>" // use an empty string for not replacing
|
||
|
|
||
|
, recipes: {} //repository
|
||
|
, queue: {} //register
|
||
|
|
||
|
//fix for IE: copy of PREformatted text strips off all html, losing newlines
|
||
|
, preFixCopy: document.selection && document.selection.createRange
|
||
|
, preContent: ""
|
||
|
, preElement: null
|
||
|
};
|
||
|
|
||
|
|
||
|
$.metaobjects = function( options ) {
|
||
|
|
||
|
options = $.extend( {
|
||
|
context: document
|
||
|
, clean: true
|
||
|
, selector: 'object.metaobject'
|
||
|
}, options );
|
||
|
|
||
|
function jsValue( value ) {
|
||
|
eval( 'value = ' + value + ";" );
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
return $( options.selector, options.context )
|
||
|
.each( function() {
|
||
|
|
||
|
var settings = { target: this.parentNode };
|
||
|
$( '> param[@name=metaparam]', this )
|
||
|
.each( function() {
|
||
|
$.extend( settings, jsValue( this.value ) );
|
||
|
} );
|
||
|
|
||
|
$( '> param', this )
|
||
|
.not( '[@name=metaparam]' )
|
||
|
.each( function() {
|
||
|
var name = this.name, value = jsValue( this.value );
|
||
|
$( settings.target )
|
||
|
.each( function() {
|
||
|
this[ name ] = value;
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
if( options.clean ) {
|
||
|
$( this ).remove();
|
||
|
}
|
||
|
} );
|
||
|
};
|
||
|
|
||
|
$.fn.chili = function( options ) {
|
||
|
var book = $.extend( {}, ChiliBook, options || {} );
|
||
|
|
||
|
function cook( ingredients, recipe ) {
|
||
|
|
||
|
function prepareStep( stepName, step ) {
|
||
|
var exp = ( typeof step.exp == "string" ) ? step.exp : step.exp.source;
|
||
|
steps.push( {
|
||
|
stepName: stepName
|
||
|
, exp: "(" + exp + ")"
|
||
|
, length: 1 // add 1 to account for the newly added parentheses
|
||
|
+ (exp // count number of submatches in here
|
||
|
.replace( /\\./g, "%" ) // disable any escaped character
|
||
|
.replace( /\[.*?\]/g, "%" ) // disable any character class
|
||
|
.match( /\((?!\?)/g ) // match any open parenthesis, not followed by a ?
|
||
|
|| [] // make sure it is an empty array if there are no matches
|
||
|
).length // get the number of matches
|
||
|
, replacement: (step.replacement) ? step.replacement : book.defaultReplacement
|
||
|
} );
|
||
|
} // function prepareStep( stepName, step )
|
||
|
|
||
|
function knowHow() {
|
||
|
var prevLength = 0;
|
||
|
var exps = new Array;
|
||
|
for (var i = 0; i < steps.length; i++) {
|
||
|
var exp = steps[ i ].exp;
|
||
|
// adjust backreferences
|
||
|
exp = exp.replace( /\\\\|\\(\d+)/g, function( m, aNum ) {
|
||
|
return !aNum ? m : "\\" + ( prevLength + 1 + parseInt( aNum, 10 ) );
|
||
|
} );
|
||
|
exps.push( exp );
|
||
|
prevLength += steps[ i ].length;
|
||
|
}
|
||
|
var source = exps.join( "|" );
|
||
|
return new RegExp( source, (recipe.ignoreCase) ? "gi" : "g" );
|
||
|
} // function knowHow()
|
||
|
|
||
|
function escapeHTML( str ) {
|
||
|
return str.replace( /&/g, "&" ).replace( /</g, "<" );
|
||
|
} // function escapeHTML( str )
|
||
|
|
||
|
function replaceSpaces( str ) {
|
||
|
return str.replace( / +/g, function( spaces ) {
|
||
|
return spaces.replace( / /g, replaceSpace );
|
||
|
} );
|
||
|
} // function replaceSpaces( str )
|
||
|
|
||
|
function filter( str ) {
|
||
|
str = escapeHTML( str );
|
||
|
if( replaceSpace ) {
|
||
|
str = replaceSpaces( str );
|
||
|
}
|
||
|
return str;
|
||
|
} // function filter( str )
|
||
|
|
||
|
function chef( matched ) {
|
||
|
var i = 0; // iterate steps
|
||
|
var j = 1; // iterate chef's arguments
|
||
|
var step;
|
||
|
while( step = steps[ i++ ] ) {
|
||
|
var aux = arguments; // this unmasks chef's arguments inside the next function
|
||
|
if( aux[ j ] ) {
|
||
|
var pattern = /(\\\$)|(?:\$\$)|(?:\$(\d+))/g;
|
||
|
var replacement = step.replacement
|
||
|
.replace( pattern, function( m, escaped, K ) {
|
||
|
var bit = '';
|
||
|
if( escaped ) { /* \$ */
|
||
|
return "$";
|
||
|
}
|
||
|
else if( !K ) { /* $$ */
|
||
|
return filter( aux[ j ] );
|
||
|
}
|
||
|
else if( K == "0" ) { /* $0 */
|
||
|
return step.stepName;
|
||
|
}
|
||
|
else { /* $K */
|
||
|
return filter( aux[ j + parseInt( K, 10 ) ] );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
var offset = arguments[ arguments.length - 2 ];
|
||
|
var input = arguments[ arguments.length - 1 ];
|
||
|
var unmatched = input.substring( lastIndex, offset );
|
||
|
lastIndex = offset + matched.length; // lastIndex for the next call to chef
|
||
|
|
||
|
perfect += filter( unmatched ) + replacement; // use perfect for all the replacing
|
||
|
return replacement;
|
||
|
}
|
||
|
else {
|
||
|
j+= step.length;
|
||
|
}
|
||
|
}
|
||
|
} // function chef( matched )
|
||
|
|
||
|
var replaceSpace = book.replaceSpace;
|
||
|
var steps = new Array;
|
||
|
for( var stepName in recipe.steps ) {
|
||
|
prepareStep( stepName, recipe.steps[ stepName ] );
|
||
|
}
|
||
|
|
||
|
var perfect = ""; //replace doesn't provide a handle to the ongoing partially replaced string
|
||
|
var lastIndex = 0; //regexp.lastIndex is available after a string.match, but not in a string.replace
|
||
|
ingredients.replace( knowHow(), chef );
|
||
|
var lastUnmatched = ingredients.substring( lastIndex, ingredients.length );
|
||
|
perfect += filter( lastUnmatched );
|
||
|
|
||
|
return perfect;
|
||
|
|
||
|
} // function cook( ingredients, recipe )
|
||
|
|
||
|
function checkCSS( stylesheetPath ) {
|
||
|
if( ! book.queue[ stylesheetPath ] ) {
|
||
|
var e = document.createElement( "link" );
|
||
|
e.rel = "stylesheet";
|
||
|
e.type = "text/css";
|
||
|
e.href = stylesheetPath;
|
||
|
document.getElementsByTagName( "head" )[0].appendChild( e );
|
||
|
|
||
|
book.queue[ stylesheetPath ] = true;
|
||
|
}
|
||
|
} // function checkCSS( recipeName )
|
||
|
|
||
|
function makeDish( el, recipePath ) {
|
||
|
var recipe = book.recipes[ recipePath ];
|
||
|
if( ! recipe ) {
|
||
|
return;
|
||
|
}
|
||
|
var chunks = el && el.childNodes && el.childNodes.length;
|
||
|
if( chunks && chunks > 1 ) {
|
||
|
return;
|
||
|
}
|
||
|
var ingredients = el.childNodes[0] && el.childNodes[0].data;
|
||
|
if( ! ingredients ) {
|
||
|
return;
|
||
|
}
|
||
|
// hack for IE: \r is used instead of \n
|
||
|
ingredients = ingredients.replace(/\r\n?/g, "\n");
|
||
|
|
||
|
var dish = cook( ingredients, recipe ); // all happens here
|
||
|
|
||
|
if( book.replaceTab ) {
|
||
|
dish = dish.replace( /\t/g, book.replaceTab );
|
||
|
}
|
||
|
if( book.replaceNewLine ) {
|
||
|
dish = dish.replace( /\n/g, book.replaceNewLine );
|
||
|
}
|
||
|
|
||
|
$( el ).html( dish );
|
||
|
if( ChiliBook.preFixCopy ) {
|
||
|
$( el )
|
||
|
.parents()
|
||
|
.filter( "pre" )
|
||
|
.bind( "mousedown", function() {
|
||
|
ChiliBook.preElement = this;
|
||
|
} )
|
||
|
.bind( "mouseup", function() {
|
||
|
if( ChiliBook.preElement == this ) {
|
||
|
ChiliBook.preContent = document.selection.createRange().htmlText;
|
||
|
}
|
||
|
} )
|
||
|
;
|
||
|
}
|
||
|
} // function makeDish( el )
|
||
|
|
||
|
function getPath( recipeName, options ) {
|
||
|
var settingsDef = {
|
||
|
recipeFolder: book.recipeFolder
|
||
|
, recipeFile: recipeName + ".js"
|
||
|
, stylesheetFolder: book.stylesheetFolder
|
||
|
, stylesheetFile: recipeName + ".css"
|
||
|
};
|
||
|
var settings;
|
||
|
if( options && typeof options == "object" ) {
|
||
|
settings = $.extend( settingsDef, options );
|
||
|
}
|
||
|
else {
|
||
|
settings = settingsDef;
|
||
|
}
|
||
|
return {
|
||
|
recipe : settings.recipeFolder + settings.recipeFile
|
||
|
, stylesheet: settings.stylesheetFolder + settings.stylesheetFile
|
||
|
};
|
||
|
} //function getPath( recipeName, options )
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// initializations
|
||
|
if( book.metadataSelector ) {
|
||
|
$.metaobjects( { context: this, selector: book.metadataSelector } );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// the coloring starts here
|
||
|
this
|
||
|
.each( function() {
|
||
|
var el = this;
|
||
|
var recipeName = book.codeLanguage( el );
|
||
|
if( '' != recipeName ) {
|
||
|
var path = getPath( recipeName, el.chili );
|
||
|
if( book.recipeLoading || el.chili ) {
|
||
|
/* dynamic setups come here */
|
||
|
if( ! book.queue[ path.recipe ] ) {
|
||
|
/* this is a new recipe to download */
|
||
|
try {
|
||
|
book.queue[ path.recipe ] = [ el ];
|
||
|
$.getJSON( path.recipe, function( recipeLoaded ) {
|
||
|
recipeLoaded.path = path.recipe;
|
||
|
book.recipes[ path.recipe ] = recipeLoaded;
|
||
|
if( book.stylesheetLoading ) {
|
||
|
checkCSS( path.stylesheet );
|
||
|
}
|
||
|
var q = book.queue[ path.recipe ];
|
||
|
for( var i = 0, iTop = q.length; i < iTop; i++ ) {
|
||
|
makeDish( q[ i ], path.recipe );
|
||
|
}
|
||
|
} );
|
||
|
}
|
||
|
catch( recipeNotAvailable ) {
|
||
|
alert( "the recipe for '" + recipeName + "' was not found in '" + path.recipe + "'" );
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
/* not a new recipe, so just enqueue this element */
|
||
|
book.queue[ path.recipe ].push( el );
|
||
|
}
|
||
|
/* a recipe could have been already downloaded */
|
||
|
makeDish( el, path.recipe );
|
||
|
}
|
||
|
else {
|
||
|
/* static setups come here */
|
||
|
makeDish( el, path.recipe );
|
||
|
}
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
return this;
|
||
|
//-----------------------------------------------------------------------------
|
||
|
};
|
||
|
|
||
|
//main
|
||
|
$( function() {
|
||
|
|
||
|
if( ChiliBook.automatic ) {
|
||
|
if( ChiliBook.elementPath ) {
|
||
|
//preserve backward compatibility
|
||
|
ChiliBook.automaticSelector = ChiliBook.elementPath;
|
||
|
if( ChiliBook.elementClass ) {
|
||
|
ChiliBook.codeLanguage = function ( el ) {
|
||
|
var selectClass = new RegExp( "\\b" + ChiliBook.elementClass + "\\b", "gi" );
|
||
|
var elClass = $( el ).attr( "class" );
|
||
|
if( ! elClass ) {
|
||
|
return '';
|
||
|
}
|
||
|
var recipeName = $.trim( elClass.replace( selectClass, "" ) );
|
||
|
return recipeName;
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$( ChiliBook.automaticSelector ).chili();
|
||
|
}
|
||
|
|
||
|
if( ChiliBook.preFixCopy ) {
|
||
|
function preformatted( text ) {
|
||
|
if( '' == text ) {
|
||
|
return "";
|
||
|
}
|
||
|
do {
|
||
|
var newline_flag = (new Date()).valueOf();
|
||
|
}
|
||
|
while( text.indexOf( newline_flag ) > -1 );
|
||
|
text = text.replace( /\<br[^>]*?\>/ig, newline_flag );
|
||
|
var el = document.createElement( '<pre>' );
|
||
|
el.innerHTML = text;
|
||
|
text = el.innerText.replace( new RegExp( newline_flag, "g" ), '\r\n' );
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
$( "body" )
|
||
|
.bind( "copy", function() {
|
||
|
if( '' != ChiliBook.preContent ) {
|
||
|
window.clipboardData.setData( 'Text', preformatted( ChiliBook.preContent ) );
|
||
|
event.returnValue = false;
|
||
|
}
|
||
|
} )
|
||
|
.bind( "mousedown", function() {
|
||
|
ChiliBook.preContent = "";
|
||
|
} )
|
||
|
.bind( "mouseup", function() {
|
||
|
ChiliBook.preElement = null;
|
||
|
} )
|
||
|
;
|
||
|
}
|
||
|
|
||
|
} );
|
||
|
|
||
|
} ) ( jQuery );
|
||
|
// load progress bar
|
||
|
myBar.loaded('chili.js');
|