modules/ve-mw/ce/nodes/ve.ce.MWGalleryNode.js
/*!
* VisualEditor ContentEditable MWGalleryNode class.
*
* @copyright See AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable MediaWiki gallery node.
*
* @class
* @extends ve.ce.BranchNode
* @mixes ve.ce.FocusableNode
*
* @constructor
* @param {ve.dm.MWGalleryNode} model Model to observe
* @param {Object} [config] Configuration options
*/
ve.ce.MWGalleryNode = function VeCeMWGalleryNode() {
// Parent constructor
ve.ce.MWGalleryNode.super.apply( this, arguments );
// DOM hierarchy for MWGalleryNode:
// <ul> this.$element (gallery mw-gallery-{mode})
// <li> ve.ce.MWGalleryCaptionNode (gallerycaption)
// <li> ve.ce.MWGalleryImageNode (gallerybox)
// <li> ve.ce.MWGalleryImageNode (gallerybox)
// ⋮
// Mixin constructors
ve.ce.FocusableNode.call( this, this.getFocusableElement() );
this.onUpdateDebounced = ve.debounce( this.onUpdate.bind( this ) );
// Events
this.model.connect( this, {
update: 'onUpdateDebounced',
// Update $focusable when number of images changes
splice: 'onUpdateDebounced',
attributeChange: 'onAttributeChange'
} );
// Initialization
this.$element.addClass( 'gallery' );
this.onUpdate();
};
/* Inheritance */
OO.inheritClass( ve.ce.MWGalleryNode, ve.ce.BranchNode );
OO.mixinClass( ve.ce.MWGalleryNode, ve.ce.FocusableNode );
/* Static Properties */
ve.ce.MWGalleryNode.static.name = 'mwGallery';
ve.ce.MWGalleryNode.static.tagName = 'ul';
ve.ce.MWGalleryNode.static.iconWhenInvisible = 'imageGallery';
ve.ce.MWGalleryNode.static.primaryCommandName = 'gallery';
/* Methods */
/**
* Handle model update events.
*/
ve.ce.MWGalleryNode.prototype.onUpdate = function () {
const mwAttrs = this.model.getAttribute( 'mw' ).attrs;
const defaults = mw.config.get( 'wgVisualEditorConfig' ).galleryOptions;
const mode = mwAttrs.mode || defaults.mode;
// `.attr( …, undefined )` does nothing - it's required to use `null` to remove an attribute.
// (This also clears the 'max-width', set below, if it's not needed.)
this.$element.attr( 'style', mwAttrs.style || null );
if ( mwAttrs.perrow && ( mode === 'traditional' || mode === 'nolines' ) ) {
// Magic 30 and 8 matches the code in ve.ce.MWGalleryImageNode
const imageWidth = parseInt( mwAttrs.widths || defaults.imageWidth );
const imagePadding = ( mode === 'traditional' ? 30 : 0 );
this.$element.css( 'max-width', mwAttrs.perrow * ( imageWidth + imagePadding + 8 ) );
}
// Update $focusable/$bounding, similar to ve.ce.GeneratedContentNode
if ( this.live ) {
this.emit( 'teardown' );
}
this.$focusable = this.getFocusableElement();
this.$bounding = this.$focusable;
if ( this.live ) {
this.emit( 'setup' );
}
this.updateInvisibleIcon();
};
/**
* Handle attribute changes to keep the live HTML element updated.
*
* @param {string} key Attribute name
* @param {any} from Old value
* @param {any} to New value
*/
ve.ce.MWGalleryNode.prototype.onAttributeChange = function ( key, from, to ) {
const defaults = mw.config.get( 'wgVisualEditorConfig' ).galleryOptions;
if ( key !== 'mw' ) {
return;
}
if ( from.attrs.class !== to.attrs.class ) {
// We can't overwrite the whole 'class' HTML attribute, because it also contains a class
// generated from the 'mode' MW attribute, and VE internal classes like 've-ce-focusableNode'
// eslint-disable-next-line mediawiki/class-doc
this.$element
.removeClass( from.attrs.class )
.addClass( to.attrs.class );
}
if ( from.attrs.mode !== to.attrs.mode ) {
// The following classes are used here:
// * mw-gallery-traditional
// * mw-gallery-nolines
// * mw-gallery-packed
// * mw-gallery-packed-overlay
// * mw-gallery-packed-hover
// * mw-gallery-slideshow
this.$element
.removeClass( 'mw-gallery-' + ( from.attrs.mode || defaults.mode ) )
.addClass( 'mw-gallery-' + ( to.attrs.mode || defaults.mode ) );
}
};
/**
* Get the focusable element
*
* As seen in ve.ce.GeneratedContentNode.
* TODO: Consider making this a core ve.ce.FocsableNode feature.
*
* @return {jQuery} Focusable element
*/
ve.ce.MWGalleryNode.prototype.getFocusableElement = function () {
return this.$element.find( '.gallerybox .thumb' );
};
/* Registration */
ve.ce.nodeFactory.register( ve.ce.MWGalleryNode );