Image node refactor

ve.ce.ImageNode.js
* Moved in generic stuff from MWImageNode
* Added drag end handler (empty, will be used soon)

ve.ce.MWImageNode.js
* Changed to inherit ImageNode
* Moved generic stuff out

ve.dm.ImageNode.js
* Added attribute extraction/preservation for src, width and height

ve.dm.MWImageNode.js
* Changed to inherit ImageNode
* Re-using ImageNode's attribute handling to extract/preserve attributes on both the image and wrapper level

Change-Id: Ied4e1ece24e6804220eac35330790f7084df55de
This commit is contained in:
Trevor Parscal 2013-04-10 12:56:20 -07:00
parent 8f3e6f152f
commit 9510440640
6 changed files with 147 additions and 87 deletions

View file

@ -17,7 +17,16 @@ ve.ce.ImageNode = function VeCeImageNode( model ) {
// Parent constructor
ve.ce.LeafNode.call( this, model, $( '<img>' ) );
// DOM Changes
// Events
this.model.addListenerMethod( this, 'update', 'onUpdate' );
this.$.on( {
'click': ve.bind( this.onClick, this ),
'dragstart': ve.bind( this.onDragStart, this ),
'dragend': ve.bind( this.onDragEnd, this )
} );
// Initialization
ve.setDomAttributes( this.$[0], this.model.getAttributes(), ['src', 'width', 'height'] );
this.$.addClass( 've-ce-imageNode' );
};
@ -29,6 +38,51 @@ ve.inheritClass( ve.ce.ImageNode, ve.ce.LeafNode );
ve.ce.ImageNode.static.name = 'image';
/* Methods */
/**
* Handle the mouse click.
*
* @method
* @param {jQuery.Event} e Click event
*/
ve.ce.ImageNode.prototype.onClick = function ( e ) {
var range,
surfaceModel = this.getRoot().getSurface().getModel(),
selection = surfaceModel.getSelection();
range = new ve.Range(
this.model.getOffset(),
this.model.getOffset() + this.model.getOuterLength()
);
if ( e.shiftKey ) {
range = ve.Range.newCoveringRange( [ selection, range ], selection.from > range.from );
}
this.getRoot().getSurface().getModel().change( null, range );
};
/**
* Handle the dragstart.
*
* @method
* @param {jQuery.Event} e Dragstart event
*/
ve.ce.ImageNode.prototype.onDragStart = function () {
return false;
};
/**
* Handle the dragend.
*
* @method
* @param {jQuery.Event} e Dragstart event
*/
ve.ce.ImageNode.prototype.onDragEnd = function () {
return false;
};
/* Registration */
ve.ce.nodeFactory.register( ve.ce.ImageNode );

View file

@ -9,38 +9,27 @@
* ContentEditable MediaWiki image node.
*
* @class
* @extends ve.ce.LeafNode
* @extends ve.ce.ImageNode
* @constructor
* @param {ve.dm.MWImageNode} model Model to observe
*/
ve.ce.MWImageNode = function VeCeMWImageNode( model ) {
// Parent constructor
ve.ce.LeafNode.call( this, model, $( '<a>' ) );
// DOM Changes
this.$.addClass( 've-ce-MWImageNode' );
this.$.attr( 'contenteditable', false );
this.$img = $( '<img>' ).appendTo( this.$ );
this.$img.attr( {
'width': this.model.getAttribute( 'width' ),
'height': this.model.getAttribute( 'height' ),
'src': this.model.getAttribute( 'src' )
} );
// Events
this.model.addListenerMethod( this, 'update', 'onUpdate' );
this.$.on( {
'click': ve.bind( this.onClick, this ),
'dragstart': ve.bind( this.onDragstart, this )
} );
ve.ce.ImageNode.call( this, model );
// Initialization
this.$.addClass( 've-ce-MWImageNode' );
this.$image = this.$;
this.$ = $( '<' + ( model.getAttribute( 'isLinked' ) ? 'a' : 'span' ) + '>' );
// Initialization
this.$.attr( 'contenteditable', false ).append( this.$image );
this.onUpdate();
};
/* Inheritance */
ve.inheritClass( ve.ce.MWImageNode, ve.ce.LeafNode );
ve.inheritClass( ve.ce.MWImageNode, ve.ce.ImageNode );
/* Static Properties */
@ -52,40 +41,6 @@ ve.ce.MWImageNode.prototype.onUpdate = function () {
// ...
};
/**
* Handle the mouse click.
*
* @method
* @param {jQuery.Event} e Click event
*/
ve.ce.MWImageNode.prototype.onClick = function ( e ) {
var range,
surfaceModel = this.getRoot().getSurface().getModel(),
selection = surfaceModel.getSelection();
range = new ve.Range(
this.model.getOffset(),
this.model.getOffset() + this.model.getOuterLength()
);
if ( e.shiftKey ) {
range = ve.Range.newCoveringRange( [ selection, range ], selection.from > range.from );
}
this.getRoot().getSurface().getModel().change( null, range );
};
/**
* Handle the dragstart.
*
* @method
* @param {jQuery.Event} e Dragstart event
*/
ve.ce.MWImageNode.prototype.onDragstart = function ( e ) {
e.preventDefault();
return false;
};
/* Registration */
ve.ce.nodeFactory.register( ve.ce.MWImageNode );

View file

@ -31,12 +31,25 @@ ve.dm.ImageNode.static.isContent = true;
ve.dm.ImageNode.static.matchTagNames = [ 'img' ];
ve.dm.ImageNode.static.toDataElement = function () {
return { 'type': 'image' };
ve.dm.ImageNode.static.toDataElement = function ( domElements ) {
var $node = $( domElements[0] ),
width = $node.attr( 'width' ),
height = $node.attr( 'height' );
return {
'type': 'image',
'attributes': {
'src': $node.attr( 'src' ),
'width': width !== undefined && width !== '' ? Number( width ) : null,
'height': height !== undefined && height !== '' ? Number( height ) : null
}
};
};
ve.dm.ImageNode.static.toDomElements = function ( dataElement, doc ) {
return [ doc.createElement( 'img' ) ];
var domElement = doc.createElement( 'img' );
ve.setDomAttributes( domElement, dataElement.attributes, [ 'src', 'width', 'height' ] );
return [ domElement ];
};
/* Registration */

View file

@ -9,53 +9,64 @@
* DataModel MediaWiki image node.
*
* @class
* @extends ve.dm.LeafNode
* @extends ve.dm.ImageNode
* @constructor
* @param {number} [length] Length of content data in document
* @param {Object} [element] Reference to element in linear model
*/
ve.dm.MWImageNode = function VeDmMWImageNode( length, element ) {
ve.dm.LeafNode.call( this, 0, element );
ve.dm.ImageNode.call( this, 0, element );
};
/* Inheritance */
ve.inheritClass( ve.dm.MWImageNode, ve.dm.LeafNode );
ve.inheritClass( ve.dm.MWImageNode, ve.dm.ImageNode );
/* Static Properties */
ve.dm.MWImageNode.static.name = 'MWimage';
ve.dm.MWImageNode.static.isContent = true;
ve.dm.MWImageNode.static.matchTagNames = null;
ve.dm.MWImageNode.static.matchRdfaTypes = [ 'mw:Image' ];
ve.dm.MWImageNode.static.storeHtmlAttributes = false;
ve.dm.MWImageNode.static.toDataElement = function ( domElements ) {
var $node = $( domElements[0].childNodes[0] ),
width = $node.attr( 'width' ),
height = $node.attr( 'height' ),
html = $( '<div>', domElements[0].ownerDocument ).append( $( domElements ).clone() ).html();
var i, j, childNode, children = Array.prototype.slice.call( domElements[0].children, 0 ),
parentResult = ve.dm.ImageNode.static.toDataElement.apply(
this, [ children ].concat( Array.prototype.slice.call( arguments, 1 ) )
),
dataElement = ve.copyObject( parentResult );
return {
'type': this.name,
// Preserve the child nodes' attributes in html/0-i/foo
for ( i = 0; i < domElements[0].childNodes.length; i++ ) {
childNode = domElements[0].childNodes[i];
for ( j = 0; j < childNode.attributes.length; j++ ) {
dataElement.attributes['html/0-' + i + '/' + childNode.attributes[j].name] =
childNode.attributes[j].value;
}
}
return ve.extendObject( true, dataElement, {
'type': 'MWimage',
'attributes': {
'src': $node.attr( 'src' ),
'width': width !== '' ? Number( width ) : null,
'height': height !== '' ? Number( height ) : null,
// TODO: don't store html, just enough attributes to rebuild
'html': html
},
};
'isLinked': domElements[0].nodeName.toLowerCase() === 'a'
}
} );
};
ve.dm.MWImageNode.static.toDomElements = function ( dataElement, doc ) {
//TODO: rebuild html from attributes
var wrapper = doc.createElement( 'div' );
wrapper.innerHTML = dataElement.attributes.html;
// Convert wrapper.children to an array
return Array.prototype.slice.call( wrapper.childNodes, 0 );
var k, wrapper = doc.createElement( dataElement.attributes.isLinked ? 'a' : 'span' ),
imageDomElement = ve.dm.ImageNode.static.toDomElements.apply( this, arguments )[0];
wrapper.appendChild( imageDomElement );
// Restore attributes from html/0-0/*
for ( k in dataElement.attributes ) {
if ( k.indexOf( 'html/0-0/' ) === 0 ) {
imageDomElement.setAttribute( k.substr( 9 ), dataElement.attributes[k] );
}
}
return [ wrapper ];
};
/* Registration */

View file

@ -277,7 +277,12 @@ QUnit.test( 'newFromRemoval', 15, function ( assert ) {
'type': 'replace',
'remove': [
'h',
{ 'type': 'image', 'attributes': { 'html/0/src': ve.dm.example.imgSrc } },
{ 'type': 'image', 'attributes': {
'html/0/src': ve.dm.example.imgSrc,
'src': ve.dm.example.imgSrc,
'width': null,
'height': null
} },
{ 'type': '/image' },
'i'
],

View file

@ -253,7 +253,12 @@ ve.dm.example.data = [
// 38 - Plain "h"
'h',
// 39 - Beginning of inline image
{ 'type': 'image', 'attributes': { 'html/0/src': ve.dm.example.imgSrc } },
{ 'type': 'image', 'attributes': {
'html/0/src': ve.dm.example.imgSrc,
'src': ve.dm.example.imgSrc,
'width': null,
'height': null
} },
// 40 - End of inline image
{ 'type': '/image' },
// 41 - Plain "i"
@ -736,7 +741,12 @@ ve.dm.example.domToDataCases = {
'html': '<body><img src="' + ve.dm.example.imgSrc + '"></body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{ 'type': 'image', 'attributes' : { 'html/0/src' : ve.dm.example.imgSrc } },
{ 'type': 'image', 'attributes' : {
'html/0/src' : ve.dm.example.imgSrc,
'width': null,
'height': null,
'src': ve.dm.example.imgSrc
} },
{ 'type' : '/image' },
{ 'type': '/paragraph' }
]
@ -748,10 +758,17 @@ ve.dm.example.domToDataCases = {
{
'type': 'MWimage',
'attributes': {
'html/0-0/alt': 'Wiki.png',
'html/0-0/height': '',
'html/0-0/src': '/index.php?title=Special:FilePath/Wiki.png&width=500',
'html/0-0/width': '500',
'html/0/data-parsoid': '{"tsr":[158,216],"src":"[[Image:Wiki.png|500px|thumb|center|Example wiki file]]","optNames":{"width":"$1px"},"dsr":[158,216,null,null]}',
'html/0/href': './File:Wiki.png',
'html/0/rel': 'mw:Image',
'src': '/index.php?title=Special:FilePath/Wiki.png&width=500',
'width': 500,
'height': null,
'html': ve.dm.example.MWImageHtml
'isLinked': true
}
},
{ 'type': '/MWimage' },
@ -842,7 +859,12 @@ ve.dm.example.domToDataCases = {
'html': '<body><img src="' + ve.dm.example.imgSrc + '">12</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{ 'type': 'image', 'attributes': { 'html/0/src': ve.dm.example.imgSrc } },
{ 'type': 'image', 'attributes': {
'html/0/src': ve.dm.example.imgSrc,
'src': ve.dm.example.imgSrc,
'width': null,
'height': null
} },
{ 'type': '/image' },
'1',
'2',