MWBlockImage new DOM fixes

MWBlockImage
* Remove properties which just cache model properties. We can get
  fresh values from the model whenever needed and this just causes
  problems keeping them in sync.
* Tidy up DOM documentation indentation
* Merge setupCaption and setCaptionVisible into updateCaption. The
  caption's visibility can be calculated inside the method from
  model attributes.
* No need to generate figcaption on init, updateCaption  will do
  this for us
* Storing full view and model in this.caption is unnecessary,
  just store this.$caption (view.$element) and this.captionVisible
* Append the caption directly to the figure now there is no container
* Simplify setCaptionVisible
* Add in fix to account for border to figure width
* updateSize can get values from the model if they are not provided
* Remove unnecessary styles being set on this.$element.

MWImageCaption
* Generate as a figcaption instead of a div for direct attachment

MWImage
* Missing docs

CSS
* Cleanup reset styles, remove redundant add in required
* Fix margins for left/right floats to match .tleft/.tright
* Use more specific selector for inner border (thumbimage) to avoid
  matching shields.
* Remove unnecessary frameless styles, it has no border by default.

Change-Id: I52e0e10b465bb9761c2e4be28c98bec37b0dd2ca
This commit is contained in:
Ed Sanders 2013-12-18 21:22:41 +00:00
parent 1cd761dad8
commit 76defa7ce2
4 changed files with 104 additions and 162 deletions

View file

@ -17,28 +17,24 @@
* @param {Object} [config] Configuration options
*/
ve.ce.MWBlockImageNode = function VeCeMWBlockImageNode( model, config ) {
var type, align;
// Parent constructor
ve.ce.BranchNode.call( this, model, config );
this.$element.addClass( 've-ce-mwBlockImageNode ' );
type = this.model.getAttribute( 'type' );
align = this.model.getAttribute( 'align' );
// Properties
this.type = this.model.getAttribute( 'type' );
this.alignment = this.model.getAttribute( 'align' );
this.size = {
'width': this.model.getAttribute( 'width' ),
'height': this.model.getAttribute( 'height' )
};
this.captionVisible = false;
this.typeToRdfa = this.getTypeToRdfa();
// DOM Hierarchy for BlockImageNode:
// <div> this.$element
// - <figure> this.$figure ( ve-ce-mwBlockImageNode-type (thumb) (tright/tleft/etc) )
// - <a> this.$a
// - <img> this.$image (thumbimage)
// - <figcaption> this.$figcaption ( thumbcaption )
// <figure> this.$figure (ve-ce-mwBlockImageNode-type (thumb) (tright/tleft/etc))
// <a> this.$a
// <img> this.$image (thumbimage)
// <figcaption> this.caption.view.$element (thumbcaption)
// Build DOM:
this.$a = this.$( '<a>' )
@ -47,40 +43,23 @@ ve.ce.MWBlockImageNode = function VeCeMWBlockImageNode( model, config ) {
this.$image = this.$( '<img>' )
.attr( 'src', this.getResolvedAttribute( 'src' ) )
.attr( 'width', this.size.width )
.attr( 'height', this.size.height )
.appendTo( this.$a );
this.$figure = this.$( '<figure>' )
.appendTo( this.$element )
.append( this.$a )
.addClass( 've-ce-mwBlockImageNode-type-' + this.type )
.addClass( 've-ce-mwBlockImageNode-type-' + type )
// 'typeof' should appear with the proper Parsoid-generated
// type. The model deals with converting it
.attr( 'typeof', this.typeToRdfa[ this.type ] );
.attr( 'typeof', this.typeToRdfa[ type ] );
this.$element
.addClass( 've-ce-mwBlockImageNode-align-' + this.alignment );
this.updateCaption();
// Update size:
this.updateSize( this.size.height, this.size.width );
this.updateSize();
// Mixin constructors
ve.ce.MWImageNode.call( this, this.$figure, this.$image );
// I smell a caption!
this.$figcaption = this.$( '<figcaption> ');
// attach the figcaption always (to prepare for option of adding one):
this.$figcaption.appendTo( this.$figure );
this.caption = {};
if ( this.type !== 'none' && this.type !== 'frameless' && this.model.children.length === 1 ) {
this.setCaptionVisible( true );
} else {
this.setCaptionVisible( false );
}
// Events
this.model.connect( this, { 'attributeChange': 'onAttributeChange' } );
};
@ -136,111 +115,85 @@ ve.ce.MWBlockImageNode.prototype.getTypeToRdfa = function () {
};
/**
* Setup a caption node according to the model
* Update the caption based on the current model state
*/
ve.ce.MWBlockImageNode.prototype.setupCaption = function () {
// only create a new caption if we need it:
if ( !this.caption.view ) {
this.caption.model = this.model.children[0];
this.caption.view = ve.ce.nodeFactory.create( this.caption.model.getType(), this.caption.model );
this.caption.model.connect( this, { 'update': 'onModelUpdate' } );
this.children.push( this.caption.view );
this.caption.view.attach( this );
if ( this.live !== this.caption.view.isLive() ) {
this.caption.view.setLive( this.live );
ve.ce.MWBlockImageNode.prototype.updateCaption = function () {
var model, view,
type = this.model.getAttribute( 'type' );
this.captionVisible = type !== 'none' && type !== 'frameless' && this.model.children.length === 1;
if ( this.captionVisible ) {
// Only create a caption if we need it
if ( !this.$caption ) {
model = this.model.children[0];
view = ve.ce.nodeFactory.create( model.getType(), model );
model.connect( this, { 'update': 'onModelUpdate' } );
this.children.push( view );
view.attach( this );
if ( this.live !== view.isLive() ) {
view.setLive( this.live );
}
this.$caption = view.$element;
this.$figure.append( this.$caption );
}
// Make it a 'figcaption'
this.caption.view.$element.appendTo( this.$figcaption );
}
if ( this.$caption ) {
this.$caption.toggle( this.captionVisible );
}
};
/**
* Set caption either visible or invisible
* Update CSS classes based on alignment and type
*
* @param {boolean} isVisible declares whether caption will be visible
* @param {string} [oldAlign] The old alignment, for removing classes
*/
ve.ce.MWBlockImageNode.prototype.setCaptionVisible = function ( isVisible ) {
if ( isVisible ) {
// update the caption:
this.setupCaption();
this.$figcaption.show();
} else {
// hide the caption:
this.$figcaption.hide();
}
// update caption visibility state:
this.caption.visible = isVisible;
};
ve.ce.MWBlockImageNode.prototype.updateClasses = function ( oldAlign ) {
var align = this.model.getAttribute( 'align' ),
type = this.model.getAttribute( 'type' );
/**
* Update image alignment, including any style changes that occur
*
* @param {string} from The old alignment
* @param {string} to The new alignment
* @param {string} type The type of the image to which to align
*/
ve.ce.MWBlockImageNode.prototype.updateAlignment = function ( from, to, type ) {
if ( from !== to ) {
// remove previous alignment:
if ( oldAlign && oldAlign !== align ) {
// Remove previous alignment
this.$figure
.removeClass(
this.getCssClass( 'none', from )
)
.removeClass(
this.getCssClass( 'default', from )
)
.removeClass( 've-ce-mwBlockImageNode-align-' + from )
// add new alignment:
.addClass( 've-ce-mwBlockImageNode-align-' + to );
.removeClass( this.getCssClass( 'none', oldAlign ) )
.removeClass( this.getCssClass( 'default', oldAlign ) );
}
if ( type !== 'none' && type !== 'frameless' ) {
this.$image
.addClass( 've-ce-mwBlockImageNode-thumbimage' );
this.$image.addClass( 've-ce-mwBlockImageNode-thumbimage' );
this.$figure
.addClass(
this.getCssClass( 'default', to )
)
.addClass( this.getCssClass( 'default', align ) )
.addClass( 've-ce-mwBlockImageNode-borderwrap' );
} else {
this.$image
.removeClass( 've-ce-mwBlockImageNode-thumbimage' );
this.$image.removeClass( 've-ce-mwBlockImageNode-thumbimage' );
this.$figure
.addClass(
this.getCssClass( 'none', to )
)
.addClass( this.getCssClass( 'none', align ) )
.removeClass( 've-ce-mwBlockImageNode-borderwrap' );
}
this.alignment = to;
// update dimensions if alignment involved 'center'
if ( to === 'center' || from === 'center' ) {
this.updateSize( this.size.height, this.size.width );
}
};
/**
* Resize the image and its wrappers
* Redraw the image and its wrappers at the specified dimensions
*
* @param {Number} height Height of the image
* @param {Number} width Width of the image
* @param {Number} [dimensions] Dimensions to update to, uses the model values if undefined
*/
ve.ce.MWBlockImageNode.prototype.updateSize = function ( height, width ) {
this.$image
.attr( 'height', height )
.attr( 'width', width );
ve.ce.MWBlockImageNode.prototype.updateSize = function ( dimensions ) {
if ( !dimensions ) {
dimensions = {
'width': this.model.getAttribute( 'width' ),
'height': this.model.getAttribute( 'height' )
};
}
this.$figure
.css( {
'width': width + 5
} );
this.$image.attr( dimensions );
// update:
this.size = {
height: height,
width: width
};
this.$figure.css( {
// If we have a border then the width is increased by 2
'width': dimensions.width + ( this.captionVisible ? 2 : 0 ),
'height': this.captionVisible ? 'auto' : dimensions.height
} );
};
/**
* Get the right CSS class to use for alignment
*
@ -268,14 +221,10 @@ ve.ce.MWBlockImageNode.prototype.getCssClass = function ( type, alignment ) {
* @method
*/
ve.ce.MWBlockImageNode.prototype.onSetup = function () {
var type = this.model.getAttribute( 'type' );
// Parent method
ve.ce.BranchNode.prototype.onSetup.call( this );
this.updateAlignment( this.alignment, this.alignment, type );
if ( type !== 'none' && type !== 'frameless' ) {
this.$element.addClass( this.getCssClass( 'default', this.model.getAttribute( 'align' ) ) );
}
this.updateClasses();
};
/**
@ -296,37 +245,25 @@ ve.ce.MWBlockImageNode.prototype.onAttributeChange = function ( key, from, to )
if ( from !== to ) {
switch ( key ) {
case 'align':
this.updateAlignment( from, to, this.type );
this.updateClasses( from );
break;
case 'src':
this.$image.attr( 'src', this.getResolvedAttribute( 'src' ) );
break;
case 'width':
this.size.width = to;
this.updateSize( this.size.height, this.size.width );
break;
case 'height':
this.size.height = to;
this.updateSize( this.size.height, this.size.width );
this.updateSize();
break;
case 'type':
this.$element.removeClass( 've-ce-mwBlockImageNode-type-' + from );
this.$element.addClass( 've-ce-mwBlockImageNode-type-' + to );
this.$figure
.removeClass( 've-ce-mwBlockImageNode-type-' + from )
.addClass( 've-ce-mwBlockImageNode-type-' + to )
.attr( 'typeof', this.typeToRdfa[ to ] );
// Update 'typeof' property
this.$figure.attr( 'typeof', this.typeToRdfa[ this.type ] );
// marking update with same 'to/from' alignment will only update
// the alignment based on the new type
this.updateAlignment( this.align, this.align, to );
// hide/show caption:
if ( to !== 'none' && to !== 'frameless' && this.model.children.length === 1 ) {
this.setCaptionVisible( true );
} else {
this.setCaptionVisible( false );
}
this.updateClasses();
this.updateCaption();
break;
// Other image attributes if they exist:
// Other image attributes if they exist
case 'alt':
this.$image.attr( key, to );
break;
@ -339,7 +276,7 @@ ve.ce.MWBlockImageNode.prototype.onResizableResizing = function ( dimensions ) {
if ( !this.outline ) {
ve.ce.ResizableNode.prototype.onResizableResizing.call( this, dimensions );
this.updateSize( dimensions.height, dimensions.width );
this.updateSize( dimensions );
}
};

View file

@ -32,7 +32,7 @@ OO.inheritClass( ve.ce.MWImageCaptionNode, ve.ce.BranchNode );
ve.ce.MWImageCaptionNode.static.name = 'mwImageCaption';
ve.ce.MWImageCaptionNode.static.tagName = 'div';
ve.ce.MWImageCaptionNode.static.tagName = 'figcaption';
/* Methods */

View file

@ -19,6 +19,8 @@
* @mixins ve.ce.MWResizableNode
*
* @constructor
* @param {jQuery} $figure Figure element
* @param {jQuery} $image Image element
* @param {Object} [config] Configuration options
*/
ve.ce.MWImageNode = function VeCeMWImageNode( $figure, $image, config ) {

View file

@ -47,14 +47,21 @@
}
/* ve.ce.MWBlockImageNode */
.ve-ce-mwBlockImageNode {
margin: 5px;
figure[typeof*='mw:Image'] {
margin: 0;
}
figure[typeof*='mw:Image'] a {
border: 0;
}
/* TODO: Merge with div.tright styles */
/* @noflip */
figure[typeof*='mw:Image'].mw-halign-right {
clear: right;
float: right;
margin: .5em 0 1.3em 1.4em;
}
figure[typeof*='mw:Image'].mw-halign-center {
@ -62,31 +69,27 @@ figure[typeof*='mw:Image'].mw-halign-center {
margin-right: auto;
}
/* TODO: Merge with div.tleft styles */
/* @noflip */
figure[typeof*='mw:Image'].mw-halign-left {
clear: left;
float: left;
margin: .5em 1.4em 1.3em 0;
}
/* TODO: Merge with div.thumbinner styles */
figure[typeof='mw:Image/Thumb'], figure[typeof='mw:Image/Frame'] {
background-color: #f9f9f9;
border: 1px solid #cccccc;
padding: 3px !important;
overflow: hidden;
font-size: 94%;
text-align: center;
}
figure[typeof*='mw:Image'] figcaption {
border: medium none;
font-size: 94%;
line-height: 1.4em;
padding: 3px !important;
}
figure[typeof*='mw:Image'] img {
background-color: #f9f9f9;
border: 1px solid #cccccc;
padding: 3px !important;
overflow: hidden;
font-size: 94%;
text-align: center;
}
/* TODO: Merge with .thumbimage styles */
figure[typeof*='mw:Image'] .ve-ce-mwBlockImageNode-thumbimage {
border: 1px solid #cccccc;
vertical-align: middle;
}
/* ve.ce.MWAlienExtensionNode */
@ -110,10 +113,10 @@ figure[typeof*='mw:Image'] img {
/* ve.ce.MWNumberedExternalLinkNode */
.ve-ce-surface {
counter-reset: ve-ce-mwNumberedExternalLinkNode;
counter-reset: ve-ce-mwNumberedExternalLinkNode;
}
a[rel~="mw:ExtLink"]:empty:after {
content: "[" counter(ve-ce-mwNumberedExternalLinkNode) "]";
counter-increment: ve-ce-mwNumberedExternalLinkNode;
content: "[" counter(ve-ce-mwNumberedExternalLinkNode) "]";
counter-increment: ve-ce-mwNumberedExternalLinkNode;
}