mediawiki-extensions-Visual.../modules/ve/ce/nodes/ve.ce.TextNode.js
Trevor Parscal 6b34f09df2 Removed some whitespace
And added a license to some files that didn't have it yet

Change-Id: I3a7e60374d1198d369a0475b8f65f7415012a337
2012-07-19 14:25:16 -07:00

327 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* VisualEditor content editable TextNode class.
*
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable node for text.
*
* @class
* @constructor
* @extends {ve.ce.LeafNode}
* @param model {ve.dm.TextNode} Model to observe
*/
ve.ce.TextNode = function( model ) {
// Inheritance
ve.ce.LeafNode.call( this, 'text', model, $( document.createTextNode('') ) );
// Events
this.model.addListenerMethod( this, 'update', 'onUpdate' );
// Intialization
this.onUpdate( true );
};
/* Static Members */
/**
* Node rules.
*
* @see ve.ce.NodeFactory
* @static
* @member
*/
ve.ce.TextNode.rules = {
'canBeSplit': true
};
/**
* Mapping of character and HTML entities or renderings.
*
* @static
* @member
*/
ve.ce.TextNode.htmlCharacters = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'\'': '&#039;',
'"': '&quot;',
'\n': '&crarr;',
'\t': '&#10142;'
};
/**
* List of annotation rendering implementations.
*
* Each supported annotation renderer must have an open and close property, each either a string or
* a function which accepts a data argument.
*
* @static
* @member
*/
ve.ce.TextNode.annotationRenderers = {
'textStyle/italic': {
'open': '<i>',
'close': '</i>'
},
'textStyle/bold': {
'open': '<b>',
'close': '</b>'
},
'textStyle/underline': {
'open': '<u>',
'close': '</u>'
},
'textStyle/strike': {
'open': '<s>',
'close': '</s>'
},
'textStyle/small': {
'open': '<small>',
'close': '</small>'
},
'textStyle/big': {
'open': '<big>',
'close': '</big>'
},
'textStyle/span': {
// TODO recognize attributes
'open': '<span>',
'close': '</span>'
},
'textStyle/strong': {
'open': '<strong>',
'close': '</strong>'
},
'textStyle/emphasize': {
'open': '<em>',
'close': '<em>'
},
'textStyle/superScript': {
'open': '<sup>',
'close': '</sup>'
},
'textStyle/subScript': {
'open': '<sub>',
'close': '</sub>'
},
'link/extLink': {
'open': function( data ) {
return '<a href="#">';
},
'close': '</a>'
},
'link/wikiLink': {
'open': function( data ) {
return '<a href="#">';
},
'close': '</a>'
},
'link/unknown': {
'open': function( data ) {
return '<a href="#">';
},
'close': '</a>'
}
};
/* Methods */
/**
* Responds to model update events.
*
* If the source changed since last update the image's src attribute will be updated accordingly.
*
* @method
*/
ve.ce.TextNode.prototype.onUpdate = function( force ) {
if ( !force && !this.root.getSurface ) {
throw 'Can not update a text node that is not attached to a document';
}
if ( force === true || this.root.getSurface().render === true ) {
// XXX: Why is this being passed to $(), creating a new jQuery object? krinkle 20120719
var $new = $( $( '<span>' + this.getHtml() + '</span>' ).contents() );
if ( $new.length === 0 ) {
$new = $new.add( document.createTextNode( '' ) );
}
this.$.replaceWith( $new );
this.$ = $new;
if ( this.parent ) {
this.parent.clean();
if ( ve.debug ) {
this.parent.$.css('backgroundColor', '#F6F6F6');
setTimeout( ve.proxy( function () {
this.parent.$.css('backgroundColor', 'transparent');
}, this ), 350 );
}
}
}
};
/**
* Gets an HTML rendering of data within content model.
*
* @method
* @param {String} Rendered HTML of data within content model
*/
ve.ce.TextNode.prototype.getHtml = function() {
var data = this.model.getDocument().getDataFromNode( this.model ),
htmlChars = ve.ce.TextNode.htmlCharacters,
renderers = ve.ce.TextNode.annotationRenderers,
out = '',
i,
j,
hash,
left = '',
right,
character,
nextCharacter,
open,
close,
index,
leftPlain,
rightPlain,
hashStack = [],
annotationStack = {},
chr;
function replaceWithNonBreakingSpace( index, data ) {
if ( ve.isArray( data[index] ) ) {
data[index][0] = '&nbsp;';
} else {
data[index] = '&nbsp;';
}
}
if ( data.length > 0 ) {
character = data[0];
if ( ve.isArray( character ) ? character[0] === ' ' : character === ' ' ) {
replaceWithNonBreakingSpace( 0, data );
}
}
if ( data.length > 1 ) {
character = data[data.length - 1];
if ( ve.isArray( character ) ? character[0] === ' ' : character === ' ' ) {
replaceWithNonBreakingSpace( data.length - 1, data );
}
}
if ( data.length > 2 ) {
for ( i = 1; i < data.length - 1; i++ ) {
character = data[i];
nextCharacter = data[i + 1];
if (
( ve.isArray( character ) ? character[0] === ' ' : character === ' ' ) &&
( ve.isArray( nextCharacter ) ? nextCharacter[0] === ' ' : nextCharacter === ' ' )
) {
replaceWithNonBreakingSpace( i + 1, data );
i++;
}
}
}
function openAnnotations( annotations ) {
var out = '',
annotation, hash;
for ( hash in annotations ) {
annotation = annotations[hash];
out += typeof renderers[annotation.type].open === 'function' ?
renderers[annotation.type].open( annotation.data ) :
renderers[annotation.type].open;
hashStack.push( hash );
annotationStack[hash] = annotation;
}
return out;
}
function closeAnnotations( annotations ) {
var out = '',
annotation, hash;
for ( hash in annotations ) {
annotation = annotations[hash];
out += typeof renderers[annotation.type].close === 'function' ?
renderers[annotation.type].close( annotation.data ) :
renderers[annotation.type].close;
hashStack.pop();
delete annotationStack[hash];
}
return out;
}
for ( i = 0; i < data.length; i++ ) {
right = data[i];
leftPlain = typeof left === 'string';
rightPlain = typeof right === 'string';
if ( !leftPlain && rightPlain ) {
// [formatted][plain]
close = {};
for ( j = hashStack.length - 1; j >= 0; j-- ) {
close[hashStack[j]] = annotationStack[hashStack[j]];
}
out += closeAnnotations( close );
} else if ( leftPlain && !rightPlain ) {
// [plain][formatted]
out += openAnnotations( right[1] );
} else if ( !leftPlain && !rightPlain ) {
// [formatted][formatted]
// setting index to undefined is is necessary to it does not use value from
// the previous iteration
open = {},
index = undefined;
for ( hash in left[1] ) {
if ( !( hash in right[1] ) ) {
index = ( index === undefined ) ?
hashStack.indexOf( hash ) :
Math.min( index, hashStack.indexOf( hash ) );
}
}
if ( index !== undefined ) {
close = {};
for ( j = hashStack.length - 1; j >= index; j-- ) {
close[hashStack[j]] = annotationStack[hashStack[j]];
}
for ( j = index; j < hashStack.length; j++ ) {
if ( hashStack[j] in right[1] && hashStack[j] in left[1] ) {
open[hashStack[j]] = annotationStack[hashStack[j]];
}
}
out += closeAnnotations( close );
}
for ( hash in right[1] ) {
if ( !( hash in left[1] ) ) {
open[hash] = right[1][hash];
}
}
out += openAnnotations( open );
}
chr = rightPlain ? right : right[0];
out += chr in htmlChars ? htmlChars[chr] : chr;
left = right;
}
close = {};
for ( j = hashStack.length - 1; j >= 0; j-- ) {
close[hashStack[j]] = annotationStack[hashStack[j]];
}
out += closeAnnotations( close );
return out;
};
/* Registration */
ve.ce.nodeFactory.register( 'text', ve.ce.TextNode );
/* Inheritance */
ve.extendClass( ve.ce.TextNode, ve.ce.LeafNode );