mediawiki-extensions-Visual.../modules/ve/ce/nodes/ve.ce.TextNode.js
Catrope 67e11ebbc3 Make VE work again with the link RDFa changes in Parsoid
This is ugly but makes things work again. I intend to clean this up once
we have a better attribute API

* Recognize mw:WikiLink, mw:SimpleWikiLink, mw:ExtLink,
 mw:NumberedExtLink and mw:UrlLink
* Support is incomplete because we can't get to the annotation text with
  the current API
* Preserve all unhandled attributes rather than special-casing data-mw
* Update remaining code using data-mw (sHref and stx extraction) to
  account for the data-mw -> data-rt rename
* Update tests accordingly

Change-Id: Ia13d3008a6d4cdc8828f9acda5aa797566bc597f
2012-07-26 16:23:03 -07:00

345 lines
7.5 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/NumberedExtLink': {
'open': function( data ) {
return '<a href="#">';
},
'close': '</a>'
},
'link/UrlLink': {
'open': function( data ) {
return '<a href="#">';
},
'close': '</a>'
},
'link/WikiLink': {
'open': function( data ) {
return '<a href="#">';
},
'close': '</a>'
},
'link/SimpleWikiLink': {
'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 );