From a88695256352d70102cb4cb37a1293ff0c9171c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Inez=20Korczyn=CC=81ski?= Date: Tue, 18 Jun 2013 15:58:10 -0700 Subject: [PATCH] Support for editing captions of block images Bug: 38129 Change-Id: I02207e78f5c28eaccd9bc97a48baa78280192255 --- VisualEditor.i18n.php | 2 + VisualEditor.php | 3 + modules/ve/dm/nodes/ve.dm.MWBlockImageNode.js | 13 ++ .../ve/ui/dialogs/ve.ui.MWMediaEditDialog.js | 157 ++++++++++++++++++ modules/ve/ui/styles/ve.ui.Dialog.css | 19 +++ .../buttons/ve.ui.MWMediaEditButtonTool.js | 38 +++++ modules/ve/ui/ve.ui.Context.js | 2 +- 7 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 modules/ve/ui/dialogs/ve.ui.MWMediaEditDialog.js create mode 100644 modules/ve/ui/tools/buttons/ve.ui.MWMediaEditButtonTool.js diff --git a/VisualEditor.i18n.php b/VisualEditor.i18n.php index b30e65d0f3..699d68ee11 100644 --- a/VisualEditor.i18n.php +++ b/VisualEditor.i18n.php @@ -33,6 +33,7 @@ $messages['en'] = array( 'visualeditor-dialog-media-insert-button' => 'Insert media', 'visualeditor-dialog-media-insert-title' => 'Insert media', 'visualeditor-dialog-media-title' => 'Media settings', + 'visualeditor-dialog-media-content-section' => 'Caption content', 'visualeditor-dialog-meta-categories-data-label' => 'Categories', 'visualeditor-dialog-meta-categories-category' => 'Category', 'visualeditor-dialog-meta-categories-defaultsort-label' => 'Sort this page by default as', @@ -198,6 +199,7 @@ See also: {{Identical|Insert media}}', 'visualeditor-dialog-media-insert-title' => 'Media insert dialog title text. {{Identical|Insert media}}', + 'visualeditor-dialog-media-content-section' => 'Label for the image content sub-section', 'visualeditor-dialog-media-title' => 'Title for the editing dialog to set how a media item is displayed on the page', 'visualeditor-dialog-meta-categories-data-label' => 'Label for the categories sub-section. {{Identical|Category}}', diff --git a/VisualEditor.php b/VisualEditor.php index 7be3edc216..b11687cd82 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -437,6 +437,7 @@ $wgResourceModules += array( 've/ui/dialogs/ve.ui.PagedDialog.js', 've/ui/dialogs/ve.ui.MWMetaDialog.js', 've/ui/dialogs/ve.ui.MWMediaInsertDialog.js', + 've/ui/dialogs/ve.ui.MWMediaEditDialog.js', 've/ui/dialogs/ve.ui.MWTransclusionDialog.js', 've/ui/tools/ve.ui.ButtonTool.js', @@ -453,6 +454,7 @@ $wgResourceModules += array( 've/ui/tools/buttons/ve.ui.LinkButtonTool.js', 've/ui/tools/buttons/ve.ui.MWLinkButtonTool.js', 've/ui/tools/buttons/ve.ui.MWMediaInsertButtonTool.js', + 've/ui/tools/buttons/ve.ui.MWMediaEditButtonTool.js', 've/ui/tools/buttons/ve.ui.BulletButtonTool.js', 've/ui/tools/buttons/ve.ui.NumberButtonTool.js', 've/ui/tools/buttons/ve.ui.IndentButtonTool.js', @@ -618,6 +620,7 @@ $wgResourceModules += array( 'visualeditor-dialog-reference-options-section', 'visualeditor-dialog-reference-title', 'visualeditor-dialogbutton-reference-tooltip', + 'visualeditor-dialog-media-content-section' ), ), 'ext.visualEditor.icons-raster' => $wgVisualEditorResourceTemplate + array( diff --git a/modules/ve/dm/nodes/ve.dm.MWBlockImageNode.js b/modules/ve/dm/nodes/ve.dm.MWBlockImageNode.js index 1aa2d50bfd..2cf69f0dc9 100644 --- a/modules/ve/dm/nodes/ve.dm.MWBlockImageNode.js +++ b/modules/ve/dm/nodes/ve.dm.MWBlockImageNode.js @@ -152,6 +152,19 @@ ve.dm.MWBlockImageNode.static.toDomElements = function ( data, doc, converter ) return [ figure ]; }; +/* Methods */ + +/** + * Get the caption node of the image. + * + * @method + * @return {ve.dm.MWImageCaptionNode|null} Caption node, if present + */ +ve.dm.MWBlockImageNode.prototype.getCaptionNode = function() { + var node = this.children[0]; + return node instanceof ve.dm.MWImageCaptionNode ? node : null; +}; + /* Registration */ ve.dm.modelRegistry.register( ve.dm.MWBlockImageNode ); diff --git a/modules/ve/ui/dialogs/ve.ui.MWMediaEditDialog.js b/modules/ve/ui/dialogs/ve.ui.MWMediaEditDialog.js new file mode 100644 index 0000000000..e45e55095c --- /dev/null +++ b/modules/ve/ui/dialogs/ve.ui.MWMediaEditDialog.js @@ -0,0 +1,157 @@ +/*! + * VisualEditor user interface MWMediaEditDialog class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Document dialog. + * + * @class + * @extends ve.ui.Dialog + * + * @constructor + * @param {ve.ui.Surface} surface + * @param {Object} [config] Config options + */ +ve.ui.MWMediaEditDialog = function VeUiMWMediaEditDialog( surface, config ) { + // Parent constructor + ve.ui.Dialog.call( this, surface, config ); + + // Properties + this.captionNode = null; +}; + +/* Inheritance */ + +ve.inheritClass( ve.ui.MWMediaEditDialog, ve.ui.Dialog ); + +/* Static Properties */ + +ve.ui.MWMediaEditDialog.static.titleMessage = 'visualeditor-dialog-media-title'; + +ve.ui.MWMediaEditDialog.static.icon = 'picture'; + +ve.ui.MWMediaEditDialog.static.modelClasses = [ ve.dm.MWBlockImageNode ]; + +ve.ui.MWMediaEditDialog.static.toolbarTools = [ + { 'items': ['undo', 'redo'] }, + { 'items': ['bold', 'italic', 'mwLink', 'clear'] } +]; + +ve.ui.MWMediaEditDialog.static.surfaceCommands = [ + 'bold', 'italic', 'mwLink', 'undo', 'redo' +]; + +/* Static Initialization */ + +ve.ui.MWMediaEditDialog.static.addLocalStylesheets( [ + 've.ce.Node.css', + 've.ce.Surface.css', + 've.ui.Surface.css', + 've.ui.Context.css', + 've.ui.Tool.css', + 've.ui.Toolbar.css' +] ); + +/* Methods */ + +/** + * Handle frame ready events. + * + * @method + */ +ve.ui.MWMediaEditDialog.prototype.initialize = function () { + // Call parent method + ve.ui.Dialog.prototype.initialize.call( this ); + + // Properties + this.contentFieldset = new ve.ui.FieldsetLayout( { + '$$': this.frame.$$, + 'label': ve.msg( 'visualeditor-dialog-media-content-section' ), + 'icon': 'parameter' + } ); + + // Initialization + this.$body.addClass( 've-ui-mwMediaEditDialog-body' ); + this.$body.append( this.contentFieldset.$ ); +}; + +/** + * Handle frame ready events. + * + * @method + */ +ve.ui.MWMediaEditDialog.prototype.onOpen = function () { + var data, doc = this.surface.getModel().getDocument(); + + // Parent method + ve.ui.Dialog.prototype.onOpen.call( this ); + + // Get caption content + this.captionNode = this.surface.getView().getFocusedNode().getModel().getCaptionNode(); + if ( this.captionNode && this.captionNode.getLength() > 0 ) { + data = doc.getData( this.captionNode.getRange(), true ); + } else { + data = [ + { 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } }, + { 'type': '/paragraph' } + ]; + } + + this.captionSurface = new ve.ui.Surface( + new ve.dm.ElementLinearData( doc.getStore(), data ), { '$$': this.frame.$$ } + ); + this.captionToolbar = new ve.ui.Toolbar( this.captionSurface, { '$$': this.frame.$$ } ); + + this.captionToolbar.$.addClass( 've-ui-mwMediaEditDialog-toolbar' ); + this.contentFieldset.$.append( this.captionToolbar.$, this.captionSurface.$ ); + this.captionToolbar.addTools( this.constructor.static.toolbarTools ); + this.captionSurface.addCommands( this.constructor.static.surfaceCommands ); + this.captionSurface.initialize(); + this.captionSurface.view.documentView.documentNode.$.focus(); +}; + +/** + * Handle frame ready events. + * + * @method + * @param {string} action Action that caused the window to be closed + */ +ve.ui.MWMediaEditDialog.prototype.onClose = function ( action ) { + var data, doc, surfaceModel = this.surface.getModel(); + + // Parent method + ve.ui.Dialog.prototype.onClose.call( this ); + + if ( action === 'apply' ) { + data = this.captionSurface.getModel().getDocument().getData(); + doc = surfaceModel.getDocument(); + if ( this.captionNode ) { + // Replace the contents of the caption + surfaceModel.getFragment( this.captionNode.getRange(), true ).insertContent( data ); + } else { + // Insert a new caption at the beginning of the image node + surfaceModel.getFragment() + .adjustRange( 1 ) + .collapseRangeToStart() + .insertContent( + [ { 'type': 'mwImageCaption' } ] + .concat( data ) + .concat( [ { 'type': '/mwImageCaption' } ] ) + ); + } + } + + // Cleanup + this.captionNode = null; + this.captionSurface.destroy(); + this.captionToolbar.destroy(); +}; + +/* Registration */ + +ve.ui.dialogFactory.register( 'mwMediaEdit', ve.ui.MWMediaEditDialog ); + +ve.ui.viewRegistry.register( 'mwMediaEdit', ve.ui.MWMediaEditDialog ); diff --git a/modules/ve/ui/styles/ve.ui.Dialog.css b/modules/ve/ui/styles/ve.ui.Dialog.css index 367cfd6f83..1cff53d237 100644 --- a/modules/ve/ui/styles/ve.ui.Dialog.css +++ b/modules/ve/ui/styles/ve.ui.Dialog.css @@ -237,3 +237,22 @@ left: 0; right: 0; } + +/* ve.ui.MWMediaEditDialog */ + +.ve-ui-mwMediaEditDialog-body { + padding: 2em; + overflow-y: auto; +} + +.ve-ui-mwMediaEditDialog-toolbar { + font-size: 1.25em; +} + +.ve-ui-mwMediaEditDialog-toolbar .ve-ui-toolbar-bar { + border: solid 1px #ddd; + border-radius: 0.25em; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-width: 0; +} diff --git a/modules/ve/ui/tools/buttons/ve.ui.MWMediaEditButtonTool.js b/modules/ve/ui/tools/buttons/ve.ui.MWMediaEditButtonTool.js new file mode 100644 index 0000000000..049f1a5bb3 --- /dev/null +++ b/modules/ve/ui/tools/buttons/ve.ui.MWMediaEditButtonTool.js @@ -0,0 +1,38 @@ +/*! + * VisualEditor UserInterface MWMediaEditButtonTool class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * MediaEdit button tool. + * + * @class + * @extends ve.ui.DialogButtonTool + * @constructor + * @param {ve.ui.Toolbar} toolbar + * @param {Object} [config] Config options + */ +ve.ui.MWMediaEditButtonTool = function VeUiMWMediaEditButtonTool( toolbar, config ) { + // Parent constructor + ve.ui.DialogButtonTool.call( this, toolbar, config ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.ui.MWMediaEditButtonTool, ve.ui.DialogButtonTool ); + +/* Static Properties */ + +ve.ui.MWMediaEditButtonTool.static.name = 'mwMediaEdit'; + +ve.ui.MWMediaEditButtonTool.static.icon = 'picture'; + +ve.ui.MWMediaEditButtonTool.static.titleMessage = 'visualeditor-dialogbutton-media-tooltip'; + +ve.ui.MWMediaEditButtonTool.static.dialog = 'mwMediaEdit'; + +/* Registration */ + +ve.ui.toolFactory.register( 'mwMediaEdit', ve.ui.MWMediaEditButtonTool ); diff --git a/modules/ve/ui/ve.ui.Context.js b/modules/ve/ui/ve.ui.Context.js index 99ceb77bc3..3e05a1b9b6 100644 --- a/modules/ve/ui/ve.ui.Context.js +++ b/modules/ve/ui/ve.ui.Context.js @@ -209,7 +209,7 @@ ve.ui.Context.prototype.update = function () { } else { // No inspector is open, or the selection has changed, show a menu of available inspectors views = ve.ui.viewRegistry.getViewsForAnnotations( fragment.getAnnotations() ); - nodes = fragment.getLeafNodes(); + nodes = fragment.getCoveredNodes(); for ( i = 0; i < nodes.length; i++ ) { if ( nodes[i].range && nodes[i].range.isCollapsed() ) { nodes.splice( i, 1 );