mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 10:35:48 +00:00
1e6daf1ee6
Extend default paste rules to prevent lists/tables/references/headings as applicable. Also update submodule to master (76ff210). New changes: f1a927f Internal paste rules and type conversions Bug: 59603 Change-Id: I952f98477219a55d25a2bd817344bde58a4862d4
405 lines
10 KiB
JavaScript
405 lines
10 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.MWDialog
|
|
*
|
|
* @constructor
|
|
* @param {ve.ui.WindowSet} windowSet Window set this dialog is part of
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.ui.MWMediaEditDialog = function VeUiMWMediaEditDialog( windowSet, config ) {
|
|
// Parent constructor
|
|
ve.ui.MWDialog.call( this, windowSet, config );
|
|
|
|
// Properties
|
|
this.mediaNode = null;
|
|
this.captionNode = null;
|
|
this.store = null;
|
|
this.filename = null;
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.ui.MWMediaEditDialog, ve.ui.MWDialog );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ui.MWMediaEditDialog.static.name = 'mediaEdit';
|
|
|
|
ve.ui.MWMediaEditDialog.static.titleMessage = 'visualeditor-dialog-media-title';
|
|
|
|
ve.ui.MWMediaEditDialog.static.icon = 'picture';
|
|
|
|
ve.ui.MWMediaEditDialog.static.toolbarGroups = [
|
|
// History
|
|
{ 'include': [ 'undo', 'redo' ] },
|
|
// No formatting
|
|
/* {
|
|
'type': 'menu',
|
|
'indicator': 'down',
|
|
'include': [ { 'group': 'format' } ],
|
|
'promote': [ 'paragraph' ],
|
|
'demote': [ 'preformatted', 'heading1' ]
|
|
},*/
|
|
// Style
|
|
{
|
|
'type': 'list',
|
|
'icon': 'text-style',
|
|
'indicator': 'down',
|
|
'include': [ { 'group': 'textStyle' }, 'clear' ],
|
|
'promote': [ 'bold', 'italic' ],
|
|
'demote': [ 'strikethrough', 'code', 'underline', 'clear' ]
|
|
},
|
|
// Link
|
|
{ 'include': [ 'link' ] },
|
|
// No structure
|
|
/* {
|
|
'type': 'bar',
|
|
'include': [ 'number', 'bullet', 'outdent', 'indent' ]
|
|
},*/
|
|
// Insert
|
|
{
|
|
'label': 'visualeditor-toolbar-insert',
|
|
'indicator': 'down',
|
|
'include': '*',
|
|
'exclude': [ { 'group': 'format' }, { 'group': 'structure' }, 'referenceList' ],
|
|
'demote': [ '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.ViewPageTarget.static.pasteRules ),
|
|
{
|
|
'all': {
|
|
'blacklist': OO.simpleArrayUnion(
|
|
ve.getProp( ve.init.mw.ViewPageTarget.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 () {
|
|
// Parent method
|
|
ve.ui.MWDialog.prototype.initialize.call( this );
|
|
|
|
// TODO: Create a ve-wide spinner class instead of the local
|
|
// classes using spinners
|
|
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.$,
|
|
'label': ve.msg( 'visualeditor-dialog-media-page-general' ),
|
|
'icon': 'parameter'
|
|
} );
|
|
|
|
this.advancedSettingsPage = new OO.ui.PageLayout( 'advanced', {
|
|
'$': this.$,
|
|
'label': ve.msg( 'visualeditor-dialog-media-page-advanced' ),
|
|
'icon': 'parameter'
|
|
} );
|
|
|
|
this.bookletLayout.addPages( [
|
|
this.generalSettingsPage, this.advancedSettingsPage
|
|
] );
|
|
|
|
// Define fieldsets for image settings
|
|
|
|
// Caption
|
|
this.captionFieldset = new OO.ui.FieldsetLayout( {
|
|
'$': this.$,
|
|
'label': ve.msg( 'visualeditor-dialog-media-content-section' ),
|
|
'icon': 'parameter'
|
|
} );
|
|
|
|
// Size
|
|
this.sizeFieldset = new OO.ui.FieldsetLayout( {
|
|
'$': this.$,
|
|
'label': ve.msg( 'visualeditor-dialog-media-size-section' ),
|
|
'icon': 'parameter'
|
|
} );
|
|
|
|
this.sizeErrorLabel = new OO.ui.InputLabelWidget( {
|
|
'$': this.$,
|
|
'label': ve.msg( 'visualeditor-dialog-media-size-originalsize-error' )
|
|
} );
|
|
|
|
this.sizeWidget = new ve.ui.MediaSizeWidget( {
|
|
'$': this.$
|
|
} );
|
|
|
|
this.sizeFieldset.$element.append( [
|
|
this.sizeWidget.$element,
|
|
this.sizeErrorLabel.$element,
|
|
this.$spinner
|
|
] );
|
|
this.sizeErrorLabel.$element.hide();
|
|
this.$spinner.hide();
|
|
|
|
this.applyButton = new OO.ui.ButtonWidget( {
|
|
'$': this.$,
|
|
'label': ve.msg( 'visualeditor-dialog-action-apply' ),
|
|
'flags': ['primary']
|
|
} );
|
|
|
|
// Events
|
|
this.applyButton.connect( this, { 'click': [ 'close', { 'action': 'apply' } ] } );
|
|
|
|
// Initialization
|
|
this.generalSettingsPage.$element.append( this.captionFieldset.$element );
|
|
this.advancedSettingsPage.$element.append( this.sizeFieldset.$element );
|
|
|
|
this.$body.append( this.bookletLayout.$element );
|
|
this.$foot.append( this.applyButton.$element );
|
|
};
|
|
|
|
/**
|
|
* Get the original size of the media object from the API, if it exists
|
|
* @returns {jQuery.Promise}
|
|
*/
|
|
ve.ui.MWMediaEditDialog.prototype.getOriginalDimensions = function () {
|
|
var index = this.store.indexOfHash( this.constructor.static.getSizeHash( this.filename ) ),
|
|
deferred = $.Deferred();
|
|
|
|
if ( index ) {
|
|
// The image size is already cached
|
|
deferred.resolve( this.store.value( index ) );
|
|
} else {
|
|
// Look for the media size through the API
|
|
$.ajax( {
|
|
'url': mw.util.wikiScript( 'api' ),
|
|
'data': {
|
|
'action': 'query',
|
|
'prop': 'imageinfo',
|
|
'indexpageids': '1',
|
|
'iiprop': 'size|mediatype',
|
|
'format': 'json',
|
|
'titles': this.filename
|
|
},
|
|
'dataType': 'json',
|
|
'type': 'POST',
|
|
// Wait up to 100 seconds before giving up
|
|
'timeout': 100000,
|
|
'cache': false
|
|
} )
|
|
.done( function ( resp ) {
|
|
var imginfo = resp.query.pages[ resp.query.pageids[0] ];
|
|
|
|
// Resolve with the size parameters
|
|
deferred.resolve( {
|
|
'height': imginfo.imageinfo[0].height,
|
|
'width': imginfo.imageinfo[0].width,
|
|
'mediatype': imginfo.imageinfo[0].mediatype
|
|
} );
|
|
} )
|
|
.fail( function () {
|
|
deferred.resolve( false );
|
|
} );
|
|
}
|
|
return deferred.promise();
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWMediaEditDialog.prototype.setup = function ( data ) {
|
|
var attrs, newDoc, originalSize, resource,
|
|
dimensions = {},
|
|
doc = this.surface.getModel().getDocument();
|
|
|
|
// Parent method
|
|
ve.ui.MWDialog.prototype.setup.call( this, data );
|
|
|
|
// Properties
|
|
this.mediaNode = this.surface.getView().getFocusedNode().getModel();
|
|
this.captionNode = this.mediaNode.getCaptionNode();
|
|
this.store = this.surface.getModel().getDocument().getStore();
|
|
|
|
// Strip the raw filename up to the 'File:' namespage
|
|
resource = this.mediaNode.getAttribute( 'resource' );
|
|
this.filename = resource.substring( resource.indexOf( 'File:' ) );
|
|
|
|
if ( this.captionNode && this.captionNode.getLength() > 0 ) {
|
|
newDoc = doc.cloneFromRange( this.captionNode.getRange() );
|
|
} else {
|
|
newDoc = [
|
|
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
|
|
{ 'type': '/paragraph' },
|
|
{ 'type': 'internalList' },
|
|
{ 'type': '/internalList' }
|
|
];
|
|
}
|
|
|
|
this.captionSurface = new ve.ui.SurfaceWidget(
|
|
newDoc,
|
|
{
|
|
'$': this.$,
|
|
'tools': this.constructor.static.toolbarGroups,
|
|
'commands': this.constructor.static.surfaceCommands,
|
|
'pasteRules': this.constructor.static.pasteRules
|
|
}
|
|
);
|
|
|
|
attrs = this.mediaNode.getAttributes();
|
|
|
|
// Show the spinner
|
|
this.$spinner.show();
|
|
|
|
// Save original size for later calculations
|
|
this.getOriginalDimensions().done( ve.bind( function ( sizeObj ) {
|
|
if ( sizeObj && sizeObj.width && sizeObj.height ) {
|
|
// Set the original dimensions in the widget
|
|
this.sizeWidget.setOriginalDimensions( {
|
|
'width': sizeObj.width,
|
|
'height': sizeObj.height
|
|
} );
|
|
|
|
// Check if we need to limit the size
|
|
if ( sizeObj.mediatype === 'BITMAP' ) {
|
|
// Set the max dimensions
|
|
this.sizeWidget.setMaxDimensions( {
|
|
'width': sizeObj.width,
|
|
'height': sizeObj.height
|
|
} );
|
|
}
|
|
|
|
// Cache the originalSize and mediatype
|
|
originalSize = {
|
|
'height': sizeObj.height,
|
|
'width': sizeObj.width,
|
|
'mediatype': sizeObj.mediatype
|
|
};
|
|
this.store.index( originalSize, this.constructor.static.getSizeHash( this.filename ) );
|
|
} else {
|
|
// Original dimensions couldn't be fetched. Display an error message
|
|
this.sizeErrorLabel.$element.hide();
|
|
}
|
|
|
|
// Set initial size in inputs
|
|
dimensions = {};
|
|
if ( attrs.height !== undefined && Number( attrs.height ) > 0 ) {
|
|
dimensions.height = attrs.height;
|
|
}
|
|
if ( attrs.width !== undefined && Number( attrs.width ) > 0 ) {
|
|
dimensions.width = attrs.width;
|
|
}
|
|
this.sizeWidget.setDimensions( dimensions );
|
|
|
|
this.$spinner.hide();
|
|
}, this ) );
|
|
|
|
// Initialization
|
|
this.captionFieldset.$element.append( this.captionSurface.$element );
|
|
this.captionSurface.initialize();
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ui.MWMediaEditDialog.prototype.teardown = function ( data ) {
|
|
var newDoc, doc, attrs = {},
|
|
surfaceModel = this.surface.getModel();
|
|
|
|
// Data initialization
|
|
data = data || {};
|
|
|
|
if ( data.action === 'apply' ) {
|
|
newDoc = this.captionSurface.getSurface().getModel().getDocument();
|
|
doc = surfaceModel.getDocument();
|
|
if ( !this.captionNode ) {
|
|
// Insert a new caption at the beginning of the image node
|
|
surfaceModel.getFragment()
|
|
.adjustRange( 1 )
|
|
.collapseRangeToStart()
|
|
.insertContent( [ { 'type': 'mwImageCaption' }, { 'type': '/mwImageCaption' } ] );
|
|
this.captionNode = this.mediaNode.getCaptionNode();
|
|
}
|
|
// Replace the contents of the caption
|
|
surfaceModel.change(
|
|
ve.dm.Transaction.newFromRemoval( doc, this.captionNode.getRange(), true )
|
|
);
|
|
surfaceModel.change(
|
|
ve.dm.Transaction.newFromDocumentInsertion( doc, this.captionNode.getRange().start, newDoc )
|
|
);
|
|
|
|
// Change attributes only if the values are valid
|
|
if ( this.sizeWidget.isValid() ) {
|
|
attrs = this.sizeWidget.getDimensions();
|
|
}
|
|
|
|
surfaceModel.change(
|
|
ve.dm.Transaction.newFromAttributeChanges( doc, this.mediaNode.getOffset(), attrs )
|
|
);
|
|
}
|
|
|
|
// Clean size values
|
|
this.sizeWidget.clear();
|
|
|
|
// Cleanup
|
|
this.captionSurface.destroy();
|
|
this.captionSurface = null;
|
|
this.captionNode = null;
|
|
|
|
// Parent method
|
|
ve.ui.MWDialog.prototype.teardown.call( this, data );
|
|
};
|
|
|
|
/* Static methods */
|
|
|
|
/**
|
|
* Get the store hash for the original dimensions of a given filename
|
|
*
|
|
* @param {string} filename Filename
|
|
* @returns {string} Store hash
|
|
*/
|
|
ve.ui.MWMediaEditDialog.static.getSizeHash = function ( filename ) {
|
|
return 'MWOriginalSize:' + filename;
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ui.dialogFactory.register( ve.ui.MWMediaEditDialog );
|