mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 10:35:48 +00:00
74a20b8ec9
Once more with feeling; default alignment when switching between inline to block and vise versa. This time, the problem was mainly that the checkbox for position cannot be updated without triggering its update event. It was triggered, however, before setting up the positioning, which caused the model to run positioning twice and get things wrong. Another smaller fix in this commit was done to make sure that the image model remembers the previous alignment, so when image type changes from inline to block and vise versa, and the alignment was default, it can recalculate default value for the new node. Bug: 65564 Change-Id: I6c16a7855ad7e3ce302fa3d042d0a402251b31f0
570 lines
14 KiB
JavaScript
570 lines
14 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.positionInput.getSelectedItem() ) {
|
|
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.positionInput.selectItem(
|
|
this.imageModel.isAligned() ?
|
|
this.positionInput.getItemFromData(
|
|
this.imageModel.getAlignment()
|
|
) :
|
|
null
|
|
);
|
|
this.positionCheckbox.setValue(
|
|
this.imageModel.isAligned()
|
|
);
|
|
|
|
// 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.updateType();
|
|
}, 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 );
|