mediawiki-extensions-Visual.../modules/ve-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js
Moriel Schottlender 4355ea4f10 Followup to small SVG default size
Make sure svg drawings on block images have the default size as
the wiki default, no matter their original set size.

Also took the opportunity to clarify the synchronization method
for scalable and image type in ve.dm.MWImageNode and call it when
the image model is loaded to make sure the details are always
synchronized before edit.

Followup to I682d832e456a501836f33ed1dfc7dbd78a4edf53

Bug: 62893
Change-Id: Ic47016d171634c532092067957cc2c3d431e9746
2014-06-04 19:38:15 -04:00

572 lines
15 KiB
JavaScript

/*!
* VisualEditor user interface MWMediaEditDialog class.
*
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw */
/**
* Dialog for editing MediaWiki media objects.
*
* @class
* @extends ve.ui.NodeDialog
*
* @constructor
* @param {Object} [config] Configuration options
*/
ve.ui.MWMediaEditDialog = function VeUiMWMediaEditDialog( config ) {
// Parent constructor
ve.ui.MWMediaEditDialog.super.call( this, config );
// Properties
this.mediaNode = null;
this.imageModel = null;
this.store = null;
};
/* Inheritance */
OO.inheritClass( ve.ui.MWMediaEditDialog, ve.ui.NodeDialog );
/* Static Properties */
ve.ui.MWMediaEditDialog.static.name = 'mediaEdit';
ve.ui.MWMediaEditDialog.static.title =
OO.ui.deferMsg( 'visualeditor-dialog-media-title' );
ve.ui.MWMediaEditDialog.static.icon = 'picture';
ve.ui.MWMediaEditDialog.static.defaultSize = 'large';
ve.ui.MWMediaEditDialog.static.modelClasses = [ ve.dm.MWBlockImageNode ];
ve.ui.MWMediaEditDialog.static.toolbarGroups = [
// History
{ 'include': [ 'undo', 'redo' ] },
// No formatting
/* {
'type': 'menu',
'indicator': 'down',
'title': OO.ui.deferMsg( 'visualeditor-toolbar-format-tooltip' ),
'include': [ { 'group': 'format' } ],
'promote': [ 'paragraph' ],
'demote': [ 'preformatted', 'heading1' ]
},*/
// Style
{
'type': 'list',
'icon': 'text-style',
'indicator': 'down',
'title': OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
'include': [ { 'group': 'textStyle' }, 'clear' ],
'promote': [ 'bold', 'italic' ],
'demote': [ 'strikethrough', 'code', 'underline', 'clear' ]
},
// Link
{ 'include': [ 'link' ] },
// Cite
{
'type': 'list',
'label': OO.ui.deferMsg( 'visualeditor-toolbar-cite-label' ),
'indicator': 'down',
'include': [ { 'group': 'cite' } ]
},
// No structure
/* {
'type': 'list',
'icon': 'bullet-list',
'indicator': 'down',
'include': [ { 'group': 'structure' } ],
'demote': [ 'outdent', 'indent' ]
},*/
// Insert
{
'label': OO.ui.deferMsg( 'visualeditor-toolbar-insert' ),
'indicator': 'down',
'include': '*',
'exclude': [
{ 'group': 'format' },
{ 'group': 'structure' },
'referenceList',
'gallery'
],
'promote': [ 'reference', 'mediaInsert' ],
'demote': [ 'language', 'specialcharacter' ]
}
];
ve.ui.MWMediaEditDialog.static.surfaceCommands = [
'undo',
'redo',
'bold',
'italic',
'link',
'clear',
'underline',
'subscript',
'superscript',
'pasteSpecial'
];
ve.ui.MWMediaEditDialog.static.pasteRules = ve.extendObject(
ve.copy( ve.init.mw.Target.static.pasteRules ),
{
'all': {
'blacklist': OO.simpleArrayUnion(
ve.getProp( ve.init.mw.Target.static.pasteRules, 'all', 'blacklist' ) || [],
[
// Tables (but not lists) are possible in wikitext with a leading
// line break but we prevent creating these with the UI
'list', 'listItem', 'definitionList', 'definitionListItem',
'table', 'tableCaption', 'tableSection', 'tableRow', 'tableCell'
]
),
// Headings are also possible, but discouraged
'conversions': {
'mwHeading': 'paragraph'
}
}
}
);
/* Methods */
/**
* @inheritdoc
*/
ve.ui.MWMediaEditDialog.prototype.initialize = function () {
var altTextFieldset, positionFieldset, borderField, positionField;
// Parent method
ve.ui.MWMediaEditDialog.super.prototype.initialize.call( this );
this.$spinner = this.$( '<div>' ).addClass( 've-specialchar-spinner' );
// Set up the booklet layout
this.bookletLayout = new OO.ui.BookletLayout( {
'$': this.$,
'outlined': true
} );
this.generalSettingsPage = new OO.ui.PageLayout( 'general', { '$': this.$ } );
this.advancedSettingsPage = new OO.ui.PageLayout( 'advanced', { '$': this.$ } );
this.bookletLayout.addPages( [
this.generalSettingsPage, this.advancedSettingsPage
] );
this.generalSettingsPage.getOutlineItem()
.setIcon( 'parameter' )
.setLabel( ve.msg( 'visualeditor-dialog-media-page-general' ) );
this.advancedSettingsPage.getOutlineItem()
.setIcon( 'parameter' )
.setLabel( ve.msg( 'visualeditor-dialog-media-page-advanced' ) );
// Define fieldsets for image settings
// Caption
this.captionFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-media-content-section' ),
'icon': 'parameter'
} );
// Alt text
altTextFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-media-alttext-section' ),
'icon': 'parameter'
} );
this.altTextInput = new OO.ui.TextInputWidget( {
'$': this.$
} );
this.altTextInput.$element.addClass( 've-ui-mwMediaEditDialog-altText' );
// Build alt text fieldset
altTextFieldset.$element
.append( this.altTextInput.$element );
// Position
this.positionInput = new OO.ui.ButtonSelectWidget( {
'$': this.$
} );
this.positionInput.addItems( [
new OO.ui.ButtonOptionWidget( 'left', {
'$': this.$,
'icon': 'align-float-left',
'label': ve.msg( 'visualeditor-dialog-media-position-left' )
} ),
new OO.ui.ButtonOptionWidget( 'center', {
'$': this.$,
'icon': 'align-center',
'label': ve.msg( 'visualeditor-dialog-media-position-center' )
} ),
new OO.ui.ButtonOptionWidget( 'right', {
'$': this.$,
'icon': 'align-float-right',
'label': ve.msg( 'visualeditor-dialog-media-position-right' )
} )
], 0 );
this.positionCheckbox = new OO.ui.CheckboxInputWidget( {
'$': this.$
} );
positionField = new OO.ui.FieldLayout( this.positionCheckbox, {
'$': this.$,
'align': 'inline',
'label': ve.msg( 'visualeditor-dialog-media-position-checkbox' )
} );
positionFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-media-position-section' ),
'icon': 'parameter'
} );
// Build position fieldset
positionFieldset.$element.append( [
positionField.$element,
this.positionInput.$element
] );
// Type
this.typeFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-media-type-section' ),
'icon': 'parameter'
} );
this.typeInput = new OO.ui.ButtonSelectWidget( {
'$': this.$
} );
this.typeInput.addItems( [
// TODO: Inline images require a bit of further work, will be coming soon
new OO.ui.ButtonOptionWidget( 'thumb', {
'$': this.$,
'icon': 'image-thumbnail',
'label': ve.msg( 'visualeditor-dialog-media-type-thumb' )
} ),
new OO.ui.ButtonOptionWidget( 'frameless', {
'$': this.$,
'icon': 'image-frameless',
'label': ve.msg( 'visualeditor-dialog-media-type-frameless' )
} ),
new OO.ui.ButtonOptionWidget( 'frame', {
'$': this.$,
'icon': 'image-frame',
'label': ve.msg( 'visualeditor-dialog-media-type-frame' )
} ),
new OO.ui.ButtonOptionWidget( 'none', {
'$': this.$,
'icon': 'image-none',
'label': ve.msg( 'visualeditor-dialog-media-type-none' )
} )
] );
this.borderCheckbox = new OO.ui.CheckboxInputWidget( {
'$': this.$
} );
borderField = new OO.ui.FieldLayout( this.borderCheckbox, {
'$': this.$,
'align': 'inline',
'label': ve.msg( 'visualeditor-dialog-media-type-border' )
} );
// Build type fieldset
this.typeFieldset.$element.append( [
this.typeInput.$element,
borderField.$element
] );
// Size
this.sizeFieldset = new OO.ui.FieldsetLayout( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-media-size-section' ),
'icon': 'parameter'
} );
this.sizeErrorLabel = new OO.ui.LabelWidget( {
'$': this.$,
'label': ve.msg( 'visualeditor-dialog-media-size-originalsize-error' )
} );
this.sizeWidget = new ve.ui.MediaSizeWidget( {}, {
'$': this.$
} );
this.$sizeWidgetElements = this.$( '<div>' ).append( [
this.sizeWidget.$element,
this.sizeErrorLabel.$element
] );
this.sizeFieldset.$element.append( [
this.$spinner,
this.$sizeWidgetElements
] );
// Get wiki default thumbnail size
this.defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' ).defaultUserOptions.defaultthumbsize;
// Events
this.positionCheckbox.connect( this, { 'change': 'onPositionCheckboxChange' } );
this.borderCheckbox.connect( this, { 'change': 'onBorderCheckboxChange' } );
this.positionInput.connect( this, { 'choose': 'onPositionInputSelect' } );
this.typeInput.connect( this, { 'choose': 'onTypeInputSelect' } );
// Initialization
this.generalSettingsPage.$element.append( [
this.captionFieldset.$element,
altTextFieldset.$element
] );
this.advancedSettingsPage.$element.append( [
positionFieldset.$element,
this.typeFieldset.$element,
this.sizeFieldset.$element
] );
this.panels.addItems( [ this.bookletLayout ] );
};
/**
* Handle image model alignment change
* @param {string} alignment Image alignment
*/
ve.ui.MWMediaEditDialog.prototype.onImageModelAlignmentChange = function ( alignment ) {
var item = alignment ? this.positionInput.getItemFromData( alignment ) : null;
alignment = alignment || 'none';
this.positionCheckbox.setValue( alignment !== 'none' );
// Select the item without triggering the 'choose' event
this.positionInput.selectItem( item );
};
/**
* Handle image model type change
* @param {string} alignment Image alignment
*/
ve.ui.MWMediaEditDialog.prototype.onImageModelTypeChange = function ( type ) {
var item = type ? this.typeInput.getItemFromData( type ) : null;
this.typeInput.selectItem( item );
this.borderCheckbox.setDisabled(
!this.imageModel.isBorderable()
);
this.borderCheckbox.setValue(
this.imageModel.isBorderable() && this.imageModel.hasBorder()
);
};
/**
* Handle change event on the positionCheckbox element.
*
* @param {boolean} checked Checkbox status
*/
ve.ui.MWMediaEditDialog.prototype.onPositionCheckboxChange = function ( checked ) {
var newPositionValue;
// Update the image model with a default value
if ( checked ) {
if ( this.imageModel.getImageNodeType() === 'mwInlineImage' ) {
newPositionValue = this.mediaNode.getDocument().getDir() === 'rtl' ? 'left' : 'right';
} else {
newPositionValue = this.imageModel.getDefaultDir();
}
this.imageModel.setAlignment( newPositionValue );
} else {
this.imageModel.setAlignment( 'none' );
this.positionInput.selectItem( null );
}
this.positionInput.setDisabled( !checked );
};
/**
* Handle change event on the positionCheckbox element.
*
* @param {boolean} checked Checkbox status
*/
ve.ui.MWMediaEditDialog.prototype.onBorderCheckboxChange = function ( checked ) {
// Update the image model
this.imageModel.toggleBorder( checked );
};
/**
* Handle change event on the positionInput element.
*
* @param {OO.ui.ButtonOptionWidget} item Selected item
*/
ve.ui.MWMediaEditDialog.prototype.onPositionInputSelect = function ( item ) {
var position = item ? item.getData() : 'default';
this.imageModel.setAlignment( position );
};
/**
* Handle change event on the typeInput element.
*
* @param {OO.ui.ButtonOptionWidget} item Selected item
*/
ve.ui.MWMediaEditDialog.prototype.onTypeInputSelect = function ( item ) {
var type = item ? item.getData() : 'default';
this.imageModel.setType( type );
};
/**
* @inheritdoc
*/
ve.ui.MWMediaEditDialog.prototype.getSetupProcess = function ( data ) {
return ve.ui.MWMediaEditDialog.super.prototype.getSetupProcess.call( this, data )
.next( function () {
var doc = this.getFragment().getSurface().getDocument();
// Properties
this.mediaNode = this.getFragment().getSelectedNode();
// Image model
this.imageModel = ve.dm.MWImageModel.static.newFromImageNode( this.mediaNode );
// Events
this.imageModel.connect( this, {
'alignmentChange': 'onImageModelAlignmentChange',
'typeChange': 'onImageModelTypeChange'
} );
this.store = doc.getStore();
// Set up the caption surface
this.captionSurface = new ve.ui.SurfaceWidget(
this.imageModel.getCaptionDocument(),
{
'$': this.$,
'tools': this.constructor.static.toolbarGroups,
'commands': this.constructor.static.surfaceCommands,
'pasteRules': this.constructor.static.pasteRules
}
);
// Size widget
this.$spinner.hide();
this.sizeErrorLabel.$element.hide();
this.sizeWidget.setScalable( this.imageModel.getScalable() );
// Initialize size
this.sizeWidget.setSizeType(
this.imageModel.isDefaultSize() ?
'default' :
'custom'
);
// Set initial alt text
this.altTextInput.setValue(
this.imageModel.getAltText()
);
// Set initial alignment
this.positionInput.setDisabled(
!this.imageModel.isAligned()
);
this.positionCheckbox.setValue(
this.imageModel.isAligned()
);
this.positionInput.selectItem(
this.imageModel.isAligned() ?
this.positionInput.getItemFromData(
this.imageModel.getAlignment()
) :
null
);
// Border flag
this.borderCheckbox.setDisabled(
!this.imageModel.isBorderable()
);
this.borderCheckbox.setValue(
this.imageModel.isBorderable() && this.imageModel.hasBorder()
);
// Type select
this.typeInput.selectItem(
this.typeInput.getItemFromData(
this.imageModel.getType() || 'none'
)
);
// Initialization
this.captionFieldset.$element.append( this.captionSurface.$element );
this.captionSurface.initialize();
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWMediaEditDialog.prototype.getReadyProcess = function ( data ) {
return ve.ui.MWMediaEditDialog.super.prototype.getReadyProcess.call( this, data )
.next( function () {
// Focus the caption surface
this.captionSurface.focus();
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWMediaEditDialog.prototype.getTeardownProcess = function ( data ) {
return ve.ui.MWMediaEditDialog.super.prototype.getTeardownProcess.call( this, data )
.first( function () {
// Cleanup
this.imageModel.disconnect( this );
this.captionSurface.destroy();
this.captionSurface = null;
this.captionNode = null;
// Reset the considerations for the scalable
// in the image node
this.mediaNode.syncScalableToType();
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWMediaEditDialog.prototype.applyChanges = function () {
var surfaceModel = this.getFragment().getSurface();
// Update from the form
this.imageModel.setAltText(
this.altTextInput.getValue()
);
this.imageModel.setCaptionDocument(
this.captionSurface.getSurface().getModel().getDocument()
);
// Check if the image node changed from inline to block or
// vise versa
if ( this.mediaNode.type !== this.imageModel.getImageNodeType() ) {
// Remove the old image
surfaceModel.change(
ve.dm.Transaction.newFromRemoval(
surfaceModel.getDocument(),
this.mediaNode.getOuterRange()
)
);
// Insert the new image
this.imageModel.insertImageNode( this.getFragment() );
} else {
// Update current node
this.imageModel.updateImageNode( surfaceModel );
}
// Parent method
return ve.ui.MWMediaEditDialog.super.prototype.applyChanges.call( this );
};
/* Registration */
ve.ui.windowFactory.register( ve.ui.MWMediaEditDialog );