Henning Snater has uploaded a new change for review. https://gerrit.wikimedia.org/r/154388
Change subject: Implemented jQuery.wikibase.descriptionview ...................................................................... Implemented jQuery.wikibase.descriptionview jQuery.wikibase.descriptionview replaces DescriptionEditTool and EditableDescription Change-Id: I3bbc60aac809e5268e38ecca3013ae4fc0b66fde --- M lib/WikibaseLib.hooks.php M lib/resources/Resources.php M lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js A lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js A lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css M lib/resources/jquery.wikibase/toolbar/edittoolbar.js M lib/resources/templates.php M lib/resources/wikibase.css D lib/resources/wikibase.ui.DescriptionEditTool.js D lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js M lib/resources/wikibase.ui.PropertyEditTool.css A lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js D lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js D lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js M repo/includes/EntityView.php M repo/resources/Resources.php M repo/resources/wikibase.ui.entityViewInit.js M repo/resources/wikibase.ui.initTermBox.js 18 files changed, 1,031 insertions(+), 488 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase refs/changes/88/154388/4 diff --git a/lib/WikibaseLib.hooks.php b/lib/WikibaseLib.hooks.php index 65505d9..f7c859a 100644 --- a/lib/WikibaseLib.hooks.php +++ b/lib/WikibaseLib.hooks.php @@ -83,10 +83,8 @@ 'tests/qunit/wikibase.RepoApi/wikibase.RepoApi.tests.js', 'tests/qunit/wikibase.RepoApi/wikibase.RepoApiError.tests.js', - 'tests/qunit/wikibase.ui.DescriptionEditTool.tests.js', 'tests/qunit/wikibase.ui.LabelEditTool.tests.js', 'tests/qunit/wikibase.ui.PropertyEditTool.tests.js', - 'tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js', 'tests/qunit/wikibase.ui.PropertyEditTool.EditableLabel.tests.js', 'tests/qunit/wikibase.ui.PropertyEditTool.EditableValue.tests.js', 'tests/qunit/wikibase.ui.PropertyEditTool.EditableValue.Interface.tests.js', @@ -163,6 +161,15 @@ ), ); + $testModules['qunit']['jquery.wikibase.descriptionview.tests'] = $moduleBase + array( + 'scripts' => array( + 'tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js', + ), + 'dependencies' => array( + 'jquery.wikibase.descriptionview', + ), + ); + $testModules['qunit']['jquery.wikibase.listview.tests'] = $moduleBase + array( 'scripts' => array( 'tests/qunit/jquery.wikibase/jquery.wikibase.listview.tests.js', diff --git a/lib/resources/Resources.php b/lib/resources/Resources.php index 8d3aab4..dc36178 100644 --- a/lib/resources/Resources.php +++ b/lib/resources/Resources.php @@ -299,10 +299,8 @@ 'wikibase.ui.PropertyEditTool.js', 'wikibase.ui.PropertyEditTool.EditableValue.js', 'wikibase.ui.PropertyEditTool.EditableValue.Interface.js', - 'wikibase.ui.PropertyEditTool.EditableDescription.js', 'wikibase.ui.PropertyEditTool.EditableLabel.js', 'wikibase.ui.LabelEditTool.js', - 'wikibase.ui.DescriptionEditTool.js', ), 'styles' => array( 'wikibase.ui.PropertyEditTool.css' @@ -487,6 +485,23 @@ ), ), + 'jquery.wikibase.descriptionview' => $moduleTemplate + array( + 'scripts' => array( + 'jquery.wikibase/jquery.wikibase.descriptionview.js' + ), + 'styles' => array( + 'jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css', + ), + 'dependencies' => array( + 'jquery.inputautoexpand', + 'jquery.ui.TemplatedWidget', + 'jquery.wikibase.edittoolbar', + 'jquery.wikibase.toolbarcontroller', + 'wikibase', + 'wikibase.RepoApiError', + ), + ), + 'jquery.wikibase.sitelinkgroupview' => $moduleTemplate + array( 'scripts' => array( 'jquery.wikibase/jquery.wikibase.sitelinkgroupview.js' diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js b/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js index 866defa..2fa0374 100644 --- a/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js +++ b/lib/resources/jquery.wikibase/jquery.wikibase.aliasesview.js @@ -66,7 +66,8 @@ PARENT.prototype._create.call( this ); - var value = this.options.value; + var self = this, + value = this.options.value; if( value && value.aliases.length @@ -79,7 +80,7 @@ // TODO: Move that code to a sensible place (see jQuery.wikibase.entityview): .on( 'aliasesviewafterstartediting.' + this.widgetName, function( event ) { $( wb ).trigger( 'startItemPageEditMode', [ - event.target, + self.element, { exclusive: false, wbCopyrightWarningGravity: 'sw' @@ -88,7 +89,7 @@ } ) .on( 'aliasesviewafterstopediting.' + this.widgetName, function( event, dropValue ) { $( wb ).trigger( 'stopItemPageEditMode', [ - event.target, + self.element, { save: dropValue !== true } ] ); } ); diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js b/lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js new file mode 100644 index 0000000..07b654b --- /dev/null +++ b/lib/resources/jquery.wikibase/jquery.wikibase.descriptionview.js @@ -0,0 +1,447 @@ +/** + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + */ +( function( $, mw, wb ) { + 'use strict'; + + var PARENT = $.ui.TemplatedWidget; + +/** + * Manages a description. + * @since 0.5 + * @extends jQuery.ui.TemplatedWidget + * + * @option {Object|null} value + * Object representing the widget's value. + * Structure: { language: <{string}>, description: <{string|null}> } + * + * @option {string} [helpMessage] + * Default: mw.msg( 'wikibase-description-input-help-message' ) + * + * @options {string} entityId + * + * @option {wikibase.RepoApi} api + * + * @option {wikibase.store.EntityStore} entityStore + */ +$.widget( 'wikibase.descriptionview', PARENT, { + /** + * @see jQuery.ui.TemplatedWidget.options + */ + options: { + template: 'wikibase-descriptionview', + templateParams: [ + '', // additional class + '', // text + '' // toolbar + ], + templateShortCuts: { + '$text': '.wikibase-descriptionview-text' + }, + value: null, + helpMessage: mw.msg( 'wikibase-description-input-help-message' ), + entityId: null, + api: null + }, + + /** + * @type {boolean} + */ + _isInEditMode: false, + + /** + * @see jQuery.ui.TemplatedWidget._create + * + * @throws {Error} if required parameters are not specified properly. + */ + _create: function() { + if( !this.options.entityId || !this.options.api ) { + throw new Error( 'Required parameter(s) missing' ); + } + + this.options.value = this._checkValue( this.options.value ); + + PARENT.prototype._create.call( this ); + + var self = this, + value = this.options.value; + + if( value && value.description !== '' && this.$text.text() === '' ) { + this._draw(); + } + + this.element + // TODO: Move that code to a sensible place (see jQuery.wikibase.entityview): + .on( + 'descriptionviewafterstartediting.' + this.widgetName + + ' eachchange.' + this.widgetName, + function( event ) { + if( !self.value().description ) { + // Since the widget shall not be in view mode when there is no value, triggering + // the event without a proper value is only done when creating the widget. Disabling + // other edit buttons shall be avoided. + // TODO: Move logic to a sensible place. + self.element.addClass( 'wb-empty' ); + return; + } + + self.element.removeClass( 'wb-empty' ); + + $( wb ).trigger( 'startItemPageEditMode', [ + self.element, + { + exclusive: false, + wbCopyrightWarningGravity: 'sw' + } + ] ); + } ) + .on( + 'descriptionviewafterstopediting.' + this.widgetName + + ' eachchange.' + this.widgetName, + function( event, dropValue ) { + if( + event.type !== 'eachchange' + || !self.options.value.description && !self.value().description + ) { + $( wb ).trigger( 'stopItemPageEditMode', [ + self.element, + { save: dropValue !== true } + ] ); + } + } ); + }, + + /** + * @see jQuery.ui.TemplatedWidget.destroy + */ + destroy: function() { + if( this._isInEditMode ) { + var self = this; + + this.element.one( this.widgetEventPrefix + 'afterstopediting', function( event ) { + PARENT.prototype.destroy.call( self ); + } ); + + this.cancelEditing(); + } else { + PARENT.prototype.destroy.call( this ); + } + }, + + /** + * Main draw routine. + */ + _draw: function() { + if( !this._isInEditMode ) { + this.element.removeClass( 'wb-edit' ); + this.$text.text( this.options.value.description ); + return; + } + + // TODO: Inject correct placeholder via options + var self = this, + languageName = wb.getLanguageNameByCode( this.options.value.language ), + placeholder = mw.msg( 'wikibase-description-edit-placeholder' ); + + if( languageName ) { + placeholder = mw.msg( + 'wikibase-description-edit-placeholder-language-aware', + languageName + ); + } + + var $input = $( '<input/>' ) + .attr( 'placeholder', placeholder ) + .on( 'eachchange.' + this.widgetName, function( event ) { + self._trigger( 'change' ); + } ); + + if( this.options.value.description ) { + $input.val( this.options.value.description ); + } + + this.element.addClass( 'wb-edit' ); + this.$text.empty().append( $input ); + }, + + /** + * Starts the widget's edit mode. + */ + startEditing: function() { + if( this._isInEditMode ) { + return; + } + + this._isInEditMode = true; + this._draw(); + + this._trigger( 'afterstartediting' ); + }, + + /** + * Stops the widget's edit mode. + * + * @param {boolean} dropValue + */ + stopEditing: function( dropValue ) { + var self = this; + + if( !this._isInEditMode || ( !this.isValid() || this.isInitialValue() ) && !dropValue ) { + return; + } + + if( dropValue ) { + this._afterStopEditing( dropValue ); + return; + } + + this.disable(); + + this._trigger( 'stopediting', null, [dropValue] ); + + // TODO: Performing API interaction should be managed in parent component (probably + // entityview) + this._save() + .done( function( response ) { + wb.getRevisionStore().setDescriptionRevision( response.entity.lastrevid ); + self.enable(); + self._afterStopEditing( dropValue ); + } ) + .fail( function( errorCode, details ) { + // TODO: API should return an Error object + var error = wb.RepoApiError.newFromApiResponse( errorCode, details, 'save' ); + self.setError( error ); + self.enable(); + } ); + }, + + /** + * @return {jQuery.Promise} + */ + _save: function() { + return this.options.api.setDescription( + this.options.entityId, + wb.getRevisionStore().getDescriptionRevision(), + this.value().description || '', + this.options.value.language + ); + }, + + /** + * Cancels the widget's edit mode. + */ + cancelEditing: function() { + this.stopEditing( true ); + }, + + /** + * Callback tearing down edit mode. + * + * @param {boolean} dropValue + */ + _afterStopEditing: function( dropValue ) { + if( !dropValue ) { + this.options.value = this.value(); + } else if( !this.options.value.description ) { + this.$text.children( 'input' ).val( '' ); + this._trigger( 'change' ); + } + + this._isInEditMode = false; + this._draw(); + + this._trigger( 'afterstopediting', null, [dropValue] ); + }, + + /** + * @return {boolean} + */ + isValid: function() { + // Function is required by edittoolbar definition. + return true; + }, + + /** + * @return {boolean} + */ + isInitialValue: function() { + var initialValue = this.options.value, + currentValue = this.value(); + + return currentValue.language === initialValue.language + && currentValue.description === initialValue.description; + }, + + /** + * Toggles error state. + * + * @param {Error} error + */ + setError: function( error ) { + if( error ) { + this.element.addClass( 'wb-error' ); + this._trigger( 'toggleerror', null, [error] ); + } else { + this.element.removeClass( 'wb-error' ); + this._trigger( 'toggleerror' ); + } + }, + + /** + * @see jQuery.ui.TemplatedWidget._setOption + */ + _setOption: function( key, value ) { + if( key === 'value' ) { + value = this._checkValue( value ); + } + return PARENT.prototype._setOption.call( this, key, value ); + }, + + /** + * @param {*} value + * @return {Object} + * + * @throws {Error} if value is not defined properly. + */ + _checkValue: function( value ) { + if( !$.isPlainObject( value ) ) { + throw new Error( 'Value needs to be an object' ); + } else if( !value.language ) { + throw new Error( 'Value needs language to be specified' ); + } + + if( !value.description ) { + value.description = null; + } + + return value; + }, + + /** + * Gets/Sets the widget's value. + * + * @param {Object} [value] + * @return {Object|undefined} + */ + value: function( value ) { + if( value !== undefined ) { + this.option( 'value', value ); + return; + } + + if( !this._isInEditMode ) { + return this.option( 'value' ); + } + + var text = $.trim( this.$text.children( 'input' ).val() ); + + return { + language: this.options.value.language, + description: text !== '' ? text : null + }; + }, + + /** + * Puts Keyboard focus on the widget. + */ + focus: function() { + if( this._isInEditMode ) { + this.$text.children( 'input' ).focus(); + } + }, + + /** + * @see jQuery.ui.TemplatedWidget.disable + */ + disable: function() { + if( this._isInEditMode ) { + this.$text.children( 'input' ).prop( 'disabled', true ); + } + + return PARENT.prototype.disable.call( this ); + }, + + /** + * @see jQuery.ui.TemplatedWidget.enable + */ + enable: function() { + if( this._isInEditMode ) { + this.$text.children( 'input' ).prop( 'disabled', false ); + } + + return PARENT.prototype.enable.call( this ); + } + +} ); + +$.wikibase.toolbarcontroller.definition( 'edittoolbar', { + id: 'descriptionview', + events: { + descriptionviewcreate: function( event, toolbarcontroller ) { + var $descriptionview = $( event.target ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + $descriptionview.edittoolbar( { + $container: $descriptionview.find( '.wikibase-descriptionview-container' ), + interactionWidgetName: $.wikibase.descriptionview.prototype.widgetName, + enableRemove: false + } ); + + $descriptionview.on( 'keyup', function( event ) { + if( event.keyCode === $.ui.keyCode.ESCAPE ) { + descriptionview.stopEditing( true ); + } else if( event.keyCode === $.ui.keyCode.ENTER ) { + descriptionview.stopEditing( false ); + } + } ); + + if( !descriptionview.value().description ) { + descriptionview.startEditing(); + } + + $descriptionview + .off( 'descriptionviewafterstopediting.edittoolbar' ) + .on( 'descriptionviewafterstopediting', function( event ) { + var edittoolbar = $( event.target ).data( 'edittoolbar' ); + if( descriptionview.value().description ) { + edittoolbar.toolbar.editGroup.toNonEditMode(); + } else { + descriptionview.startEditing(); + } + + edittoolbar.enable(); + edittoolbar.toggleActionMessage( function() { + edittoolbar.toolbar.editGroup.getButton( 'edit' ).focus(); + } ); + } ); + }, + 'descriptionviewchange descriptionviewafterstartediting': function( event ) { + var $descriptionview = $( event.target ), + descriptionview = $descriptionview.data( 'descriptionview' ), + toolbar = $descriptionview.data( 'edittoolbar' ).toolbar, + $btnSave = toolbar.editGroup.getButton( 'save' ), + btnSave = $btnSave.data( 'toolbarbutton' ), + enable = descriptionview.isValid() && !descriptionview.isInitialValue(), + $btnCancel = toolbar.editGroup.getButton( 'cancel' ), + btnCancel = $btnCancel.data( 'toolbarbutton' ), + currentDescription = descriptionview.value().description, + disableCancel = !currentDescription && descriptionview.isInitialValue(); + + btnSave[enable ? 'enable' : 'disable'](); + btnCancel[disableCancel ? 'disable' : 'enable'](); + }, + toolbareditgroupedit: function( event, toolbarcontroller ) { + var $descriptionview = $( event.target ).closest( ':wikibase-edittoolbar' ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + if( !descriptionview ) { + return; + } + + descriptionview.focus(); + } + } +} ); + +}( jQuery, mediaWiki, wikibase ) ); diff --git a/lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css b/lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css new file mode 100644 index 0000000..5ad3b4d --- /dev/null +++ b/lib/resources/jquery.wikibase/themes/default/jquery.wikibase.descriptionview.css @@ -0,0 +1,53 @@ +/** + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + */ + +.wikibase-descriptionview { + float: left; + margin-top: -2px; /* move to top decreasing space between label and description */ + padding-left: 10px; + width: 100%; +} + +div.wikibase-descriptionview.wb-edit { + background-color: #D6F3FF; +} +div.wikibase-descriptionview.wb-empty { + background-color: inherit; +} + +.wikibase-descriptionview .wikibase-descriptionview-container { + position: relative; +} + +.wikibase-descriptionview .wb-editsection { + line-height: 2; + position: absolute; + right: 10px; + top: 0.1em; +} + +.wikibase-descriptionview.wb-edit .wb-editsection { + right: 7px; +} + +.wikibase-descriptionview.wb-edit { + padding-left: 7px; +} + +.wikibase-descriptionview .wikibase-descriptionview-text { + display: block; + padding: 0.1em 19em 0.1em 0; + line-height: 2; /* force height to be able to align toolbar */ +} + +.wikibase-descriptionview .wikibase-descriptionview-text > div { + display: inline; +} + +.wikibase-descriptionview input { + width: 100%; + padding: 0 2px; + font-size: 1em; /* prevent IE from automatically resizing the font within the input box */ +} diff --git a/lib/resources/jquery.wikibase/toolbar/edittoolbar.js b/lib/resources/jquery.wikibase/toolbar/edittoolbar.js index c7c80b2..6d3df82 100644 --- a/lib/resources/jquery.wikibase/toolbar/edittoolbar.js +++ b/lib/resources/jquery.wikibase/toolbar/edittoolbar.js @@ -197,7 +197,7 @@ self.toggleActionMessage( { message: 'wikibase-save-inprogress' } ); } } ) - .on( prefix + 'afterstopediting', function( event ) { + .on( prefix + 'afterstopediting.' + this.widgetName, function( event ) { editGroup.toNonEditMode(); self.enable(); self.toggleActionMessage( function() { diff --git a/lib/resources/templates.php b/lib/resources/templates.php index 72ff2dd..813b00c 100644 --- a/lib/resources/templates.php +++ b/lib/resources/templates.php @@ -169,11 +169,13 @@ <h1 id="wb-firstHeading-$1" class="wb-firstHeading wb-value-row">$2</h1> HTML; - $templates['wb-description'] = + $templates['wikibase-descriptionview'] = <<<HTML -<div class="wb-property-container wb-value-row wb-description" dir="auto"> - <div class="wb-property-container-key" title="description"></div> - $1 +<div class="wikibase-descriptionview $1" dir="auto"> + <div class="wikibase-descriptionview-container"> + <span class="wikibase-descriptionview-text">$2</span> + <!-- wikibase-toolbar -->$3 + </div> </div> HTML; diff --git a/lib/resources/wikibase.css b/lib/resources/wikibase.css index 7425325..a6c905b 100644 --- a/lib/resources/wikibase.css +++ b/lib/resources/wikibase.css @@ -162,8 +162,7 @@ /********** LABEL & DESCRIPTION **********/ -.wb-ui-labeledittool .wb-value-container, -.wb-ui-descriptionedittool .wb-value-container { +.wb-ui-labeledittool .wb-value-container { padding-right: 19em; /* width of toolbar + white space border */ } @@ -240,29 +239,6 @@ /********** /LABEL **********/ -/********** DESCRIPTION **********/ - -.wb-description { - margin-top: -2px; /* move to top decreasing space between label and description */ -} - -.wb-description .wb-value-container { - padding-left: 10px; - padding-top: 0.1em; - padding-bottom: 0.1em; -} - -.wb-description .wb-value { - line-height: 2; /* force height to be able to align toolbar */ -} - -.wb-description .wb-editsection { - top: 0.1em; /* see padding-top of editablevalue container */ - line-height: 2; -} - -/********** /DESCRIPTION **********/ - /********** /LABEL & DESCRIPTION **********/ @@ -317,13 +293,11 @@ background: #F8F8F8; } -table.wb-terms td.wb-terms-label, -table.wb-terms td.wb-terms-description { +table.wb-terms td.wb-terms-label { padding: 10px; } -table.wb-terms .wb-edit td.wb-terms-label, -table.wb-terms .wb-edit td.wb-terms-description { +table.wb-terms .wb-edit td.wb-terms-label { padding: 8px 8px 9px 7px; } @@ -354,6 +328,26 @@ width: 100%; } +.wb-terms .wikibase-descriptionview .wikibase-descriptionview-text { + padding-right: 0; +} + +.wb-terms .wikibase-descriptionview { + display: block; + float: none; + margin-top: 0; + padding: 10px; + width: auto; +} + +.wb-terms .wikibase-descriptionview.wb-edit { + padding: 9px 8px 9px 7px; +} + +.wb-terms .wb-error { + background-color: #FFDFC9; +} + /********** /TERMS **********/ diff --git a/lib/resources/wikibase.ui.DescriptionEditTool.js b/lib/resources/wikibase.ui.DescriptionEditTool.js deleted file mode 100644 index 0fec694..0000000 --- a/lib/resources/wikibase.ui.DescriptionEditTool.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @licence GNU GPL v2+ - * @author Daniel Werner < daniel.werner at wikimedia.de > - */ -( function( mw, wb, util, $ ) { -'use strict'; -var PARENT = wb.ui.PropertyEditTool; - -/** - * Module for 'Wikibase' extensions user interface functionality for editing the description of an item. - * - * @constructor - * @see wb.ui.PropertyEditTool - * @since 0.1 - */ -wb.ui.DescriptionEditTool = util.inherit( PARENT, { - /** - * @see wb.ui.SECONDARY_UI_CLASSES - */ - SECONDARY_UI_CLASSES: PARENT.prototype.SECONDARY_UI_CLASSES + ' wb-ui-descriptionedittool', - - /** - * @see wb.ui.PropertyEditTool._init() - */ - _init: function( subject, options ) { - // setting default options - options = $.extend( {}, PARENT.prototype._options, { - /** - * @see wikibase.ui.PropertyEditTool.allowsMultipleValues - */ - allowsMultipleValues: false - }, options ); - PARENT.prototype._init.call( this, subject, options ); - }, - - /** - * @see wb.ui.PropertyEditTool.getEditableValuePrototype - */ - getEditableValuePrototype: function() { - return wb.ui.PropertyEditTool.EditableDescription; - } - -} ); - -} )( mediaWiki, wikibase, util, jQuery ); diff --git a/lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js b/lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js deleted file mode 100644 index 3c1242a..0000000 --- a/lib/resources/wikibase.ui.PropertyEditTool.EditableDescription.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @licence GNU GPL v2+ - * @author Daniel Werner - * @author Tobias Gritschacher - */ -( function( mw, wb, util, $ ) { -'use strict'; - -var PARENT = wb.ui.PropertyEditTool.EditableValue; - -/** - * Serves the input interface for an item description, extends EditableValue. - * @constructor - * @extends wb.ui.PropertyEditTool.EditableValue - * @since 0.1 - */ -var SELF = wb.ui.PropertyEditTool.EditableDescription = util.inherit( PARENT, { - /** - * @see wikibase.ui.PropertyEditTool.EditableValue.API_VALUE_KEY - */ - API_VALUE_KEY: 'descriptions', - - /** - * @see wb.ui.PropertyEditTool.EditableValue._options - */ - _options: $.extend( {}, PARENT.prototype._options, { - inputHelpMessageKey: 'wikibase-description-input-help-message' - } ), - - /** - * @see wikibase.ui.PropertyEditTool.EditableValue._setRevisionIdFromApiResponse - */ - _setRevisionIdFromApiResponse: function( response ) { - wb.getRevisionStore().setDescriptionRevision( response.lastrevid ); - return true; - }, - - /** - * Calling the corresponding method in the wikibase.RepoApi - * - * @return {jQuery.Promise} - */ - queryApi: function() { - return this._api.setDescription( - mw.config.get( 'wbEntityId' ), - wb.getRevisionStore().getDescriptionRevision(), - this.getValue().toString(), - this.getValueLanguageContext() - ); - } -} ); - -/** - * @see wb.ui.PropertyEditTool.EditableValue.newFromDom - */ -SELF.newFromDom = function( subject, options, toolbar ) { - var ev = wb.ui.PropertyEditTool.EditableValue, - $subject = $( subject ), - $interfaceParent = $subject.children( '.wb-value' ).first(), - languageName, placeHolderMsg, simpleInterface; - - options = options || {}; - options.valueLanguageContext = - options.valueLanguageContext || ev.getValueLanguageContextFromDom( $interfaceParent ); - - languageName = wb.getLanguageNameByCode( options.valueLanguageContext ); - - if ( languageName ) { - placeHolderMsg = mw.msg( - 'wikibase-description-edit-placeholder-language-aware', - languageName - ); - } else { - placeHolderMsg = mw.msg( 'wikibase-description-edit-placeholder' ); - } - - simpleInterface = new ev.Interface( $interfaceParent, { - 'inputPlaceholder': placeHolderMsg, - 'autoExpand': false - } ); - - return new SELF( $subject, options, simpleInterface, toolbar ); -}; - -}( mediaWiki, wikibase, util, jQuery ) ); diff --git a/lib/resources/wikibase.ui.PropertyEditTool.css b/lib/resources/wikibase.ui.PropertyEditTool.css index 81f9820..d335f53 100644 --- a/lib/resources/wikibase.ui.PropertyEditTool.css +++ b/lib/resources/wikibase.ui.PropertyEditTool.css @@ -78,25 +78,6 @@ /***** /LABEL *****/ -/***** DESCRIPTION *****/ - -.wb-ui-descriptionedittool .wb-ui-propertyedittool-editablevalue-ineditmode { - margin-right: .3em; - padding-left: 7px; -} - -.wb-ui-descriptionedittool .wb-ui-propertyedittool-editablevalue-ineditmode .wb-value { - padding: 0; -} - -.wb-ui-descriptionedittool input { - width: 100%; - padding: 0 2px; - font-size: 1em; /* prevent IE from automatically resizing the font within the input box */ -} - -/***** /DESCRIPTION *****/ - /********** TAGADATA **********/ ul.tagadata { diff --git a/lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js b/lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js new file mode 100644 index 0000000..dd1bfcb --- /dev/null +++ b/lib/tests/qunit/jquery.wikibase/jquery.wikibase.descriptionview.tests.js @@ -0,0 +1,191 @@ +/** + * @licence GNU GPL v2+ + * @author H. Snater < mediaw...@snater.com > + */ + +( function( $, jQuery, QUnit ) { +'use strict'; + +/** + * @param {Object} [options] + * @param {jQuery} [$node] + * @return {jQuery} + */ +var createDescriptionview = function( options, $node ) { + options = $.extend( { + entityId: 'i am an entity id', + api: 'i am an api', + value: { + language: 'en', + description: 'test description' + } + }, options || {} ); + + $node = $node || $( '<div/>' ).appendTo( 'body' ); + + var $descriptionview = $node + .addClass( 'test_descriptionview' ) + .descriptionview( options ); + + $descriptionview.data( 'descriptionview' )._save = function() { + return $.Deferred().resolve( { + entity: { + lastrevid: 'i am a revision id' + } + } ).promise(); + }; + + return $descriptionview; +}; + +QUnit.module( 'jquery.wikibase.descriptionview', QUnit.newMwEnvironment( { + teardown: function() { + $( '.test_descriptionview' ).each( function() { + var $descriptionview = $( this ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + if( descriptionview ) { + descriptionview.destroy(); + } + + $descriptionview.remove(); + } ); + } +} ) ); + +QUnit.test( 'Create & destroy', function( assert ) { + assert.throws( + function() { + createDescriptionview( { value: null } ); + }, + 'Throwing error when trying to initialize widget without a value.' + ); + + var $descriptionview = createDescriptionview(), + descriptionview = $descriptionview.data( 'descriptionview' ); + + assert.ok( + descriptionview !== 'undefined', + 'Created widget.' + ); + + descriptionview.destroy(); + + assert.ok( + $descriptionview.data( 'descriptionview' ) === undefined, + 'Destroyed widget.' + ); +} ); + +QUnit.test( 'startEditing() & stopEditing()', 5, function( assert ) { + var $descriptionview = createDescriptionview(), + descriptionview = $descriptionview.data( 'descriptionview' ); + + $descriptionview + .on( 'descriptionviewafterstartediting', function( event ) { + assert.ok( + true, + 'Started edit mode.' + ); + } ) + .on( 'descriptionviewafterstopediting', function( event, dropValue ) { + assert.ok( + true, + 'Stopped edit mode.' + ); + } ); + + descriptionview.startEditing(); + + assert.ok( + descriptionview.$text.find( 'input' ).length === 1, + 'Generated input element.' + ); + + descriptionview.startEditing(); // should not trigger event + descriptionview.stopEditing( true ); + descriptionview.stopEditing( true ); // should not trigger event + descriptionview.stopEditing(); // should not trigger event + descriptionview.startEditing(); + + descriptionview.$text.find( 'input' ).val( '' ); + + descriptionview.stopEditing(); +} ); + +QUnit.test( 'isInitialValue()', function( assert ) { + var $descriptionview = createDescriptionview(), + descriptionview = $descriptionview.data( 'descriptionview' ); + + descriptionview.startEditing(); + + assert.ok( + descriptionview.isInitialValue(), + 'Verified isInitialValue() returning true.' + ); + + descriptionview.$text.find( 'input' ).val( 'changed' ); + + assert.ok( + !descriptionview.isInitialValue(), + 'Verified isInitialValue() returning false after changing value.' + ); + + descriptionview.$text.find( 'input' ).val( 'test description' ); + + assert.ok( + descriptionview.isInitialValue(), + 'Verified isInitialValue() returning true after resetting to initial value.' + ); +} ); + +QUnit.test( 'setError()', function( assert ) { + var $descriptionview = createDescriptionview(), + descriptionview = $descriptionview.data( 'descriptionview' ); + + $descriptionview + .on( 'descriptionviewtoggleerror', function( event, error ) { + assert.ok( + true, + 'Triggered "toggleerror" event.' + ); + } ); + + descriptionview.setError(); +} ); + +QUnit.test( 'value()', function( assert ) { + var $descriptionview = createDescriptionview(), + descriptionview = $descriptionview.data( 'descriptionview' ); + + assert.throws( + function() { + descriptionview.value( null ); + }, + 'Trying to set no value fails.' + ); + + descriptionview.value( { + language: 'de', + description: 'changed description' + } ); + + assert.ok( + descriptionview.value().language === 'de' + && descriptionview.value().description === 'changed description', + 'Set new value.' + ); + + descriptionview.value( { + language: 'en', + description: null + } ); + + assert.ok( + descriptionview.value().language === 'en' + && descriptionview.value().description === null, + 'Set another value.' + ); +} ); + +}( jQuery, wikibase, QUnit ) ); diff --git a/lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js b/lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js deleted file mode 100644 index 72b9414..0000000 --- a/lib/tests/qunit/wikibase.ui.DescriptionEditTool.tests.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * QUnit tests description edit tool - * @see https://www.mediawiki.org/wiki/Extension:Wikibase - * - * @since 0.1 - * - * @licence GNU GPL v2+ - * @author H. Snater <mediaw...@snater.com> - */ - -( function( mw, wb, $, QUnit, undefined ) { - 'use strict'; - - QUnit.module( 'wikibase.ui.DescriptionEditTool', QUnit.newWbEnvironment( { - setup: function() { - this.parentNode = $( '<div/>' ); - this.text = 'Text'; - this.node = $( '<div/>' ).append( $( '<div/>', { - text: this.text, - 'class': 'wb-property-container-value' - } ) ); - this.parentNode.append( this.node ); - this.subject = new wb.ui.DescriptionEditTool( this.parentNode, { api: {} } ); - }, - teardown: function() {} - } ) ); - - QUnit.test( 'basic check', function( assert ) { - - assert.ok( - this.subject instanceof wb.ui.DescriptionEditTool, - 'instantiated DescriptionEditTool' - ); - - assert.equal( - this.subject.getEditableValuePrototype(), - wb.ui.PropertyEditTool.EditableDescription, - 'retrieved prototype' - ); - - assert.equal( - this.subject.getOption( 'allowsMultipleValues' ), - false, - 'does not allow multiple values' - ); - - this.subject.destroy(); - - assert.equal( - this.node.children().length + this.node.children().first().children().length, - 1, - 'cleaned DOM' - ); - - assert.equal( - this.node.text(), - this.text, - 'plain text remains' - ); - - } ); - -}( mediaWiki, wikibase, jQuery, QUnit ) ); diff --git a/lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js b/lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js deleted file mode 100644 index aa8aa6f..0000000 --- a/lib/tests/qunit/wikibase.ui.PropertyEditTool.EditableDescription.tests.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * QUnit tests for editable description component - * @see https://www.mediawiki.org/wiki/Extension:Wikibase - * - * @since 0.1 - * - * @licence GNU GPL v2+ - * @author H. Snater <mediaw...@snater.com> - * @author Marius Hoch < h...@online.de > - */ - -( function( mw, wb, $, QUnit, undefined ) { - 'use strict'; - - function setup( options ) { - options = $.extend( { api: {} }, options || {} ); - - var $node = $( '<div><div class="wb-value"/></div>' ); - $( '<div/>', { id: 'parent' } ).append( $node ); - - var propertyEditTool = new wb.ui.PropertyEditTool( $node ), - subject = wb.ui.PropertyEditTool.EditableDescription.newFromDom( $node, options ), - toolbar = propertyEditTool._buildSingleValueToolbar(); - - subject.setToolbar( toolbar ); - - return subject; - } - - QUnit.module( 'wikibase.ui.PropertyEditTool.EditableDescription', QUnit.newWbEnvironment( { - setup: function() { - this.subject = setup(); - }, - teardown: function() {} - } ) ); - - QUnit.test( 'basic', function( assert ) { - - assert.ok( - this.subject instanceof wb.ui.PropertyEditTool.EditableDescription, - 'instantiated editable description' - ); - - assert.equal( - this.subject._interfaces.length, - 1, - 'initialized single interface' - ); - - assert.ok( - this.subject.getInputHelpMessage() !== '', - 'help message not empty' - ); - - this.subject.destroy(); - - assert.equal( - this.subject._toolbar, - null, - 'destroyed toolbar' - ); - - assert.equal( - this.subject._instances, - null, - 'destroyed instances' - ); - - } ); - - QUnit.test( 'placeholder', function( assert ) { - var oldGetLanguageNameByCode = wb.getLanguageNameByCode; - - wb.getLanguageNameByCode = function( code ) { - if ( code === 'de' ) { - return 'Deutsch'; - } else { - return ''; - } - }; - - var withLanguage = setup( { valueLanguageContext: 'de' } ), - withoutLanguage = setup( { valueLanguageContext: 'ru' } ); - - assert.equal( - withLanguage._interfaces[0]._options.inputPlaceholder, - mw.msg( - 'wikibase-description-edit-placeholder-language-aware', - 'Deutsch' - ) - ); - - assert.equal( - withoutLanguage._interfaces[0]._options.inputPlaceholder, - mw.msg( 'wikibase-description-edit-placeholder' ) - ); - - wb.getLanguageNameByCode = oldGetLanguageNameByCode; - } ); - -}( mediaWiki, wikibase, jQuery, QUnit ) ); diff --git a/repo/includes/EntityView.php b/repo/includes/EntityView.php index b4b5ef9..8ea7402 100644 --- a/repo/includes/EntityView.php +++ b/repo/includes/EntityView.php @@ -443,15 +443,19 @@ if ( $entityId !== null && $editable ) { $idString = $entityId->getSerialization(); - $editSection .= $this->getHtmlForEditSection( 'SetDescription', array( $idString, $languageCode ) ); + $editSection .= $this->getHtmlForEditSection( + 'SetDescription', + array( $idString, $languageCode ) + ); } - $html = wfTemplate( 'wb-description', - wfTemplate( 'wb-property', - $description === false ? 'wb-value-empty' : '', - htmlspecialchars( $description === false ? wfMessage( 'wikibase-description-empty' )->text() : $description ), - $editSection - ) + $html = wfTemplate( 'wikibase-descriptionview', + $description === false ? 'wb-empty' : '', + htmlspecialchars( $description === false + ? wfMessage( 'wikibase-description-empty' )->text() + : $description + ), + $editSection ); wfProfileOut( __METHOD__ ); diff --git a/repo/resources/Resources.php b/repo/resources/Resources.php index e06381c..5fe5b10 100644 --- a/repo/resources/Resources.php +++ b/repo/resources/Resources.php @@ -28,6 +28,7 @@ 'mediawiki.user', 'wikibase.ui.PropertyEditTool', 'jquery.wikibase.aliasesview', + 'jquery.wikibase.descriptionview', 'jquery.wikibase.entityview', 'jquery.wikibase.toolbarcontroller', 'jquery.wikibase.wbtooltip', @@ -81,7 +82,9 @@ 'wikibase.ui.initTermBox.js', ), 'dependencies' => array( + 'jquery.wikibase.edittoolbar', 'jquery.wikibase.toolbar', + 'jquery.wikibase.toolbarcontroller', 'jquery.wikibase.toolbareditgroup', 'mediawiki.Title', 'wikibase', diff --git a/repo/resources/wikibase.ui.entityViewInit.js b/repo/resources/wikibase.ui.entityViewInit.js index d282f33..81dec62 100644 --- a/repo/resources/wikibase.ui.entityViewInit.js +++ b/repo/resources/wikibase.ui.entityViewInit.js @@ -53,12 +53,6 @@ $( wb ).on( 'stopItemPageEditMode', fn ); } - // add an edit tool for all properties in the data view: - $( '.wb-property-container:has( > .wb-property-container-key[title=description] )' ).each( function() { - // TODO: Make this nicer when we have implemented the data model - new wb.ui.DescriptionEditTool( this, { api: repoApi } ); - } ); - registerEditRestrictionHandlers(); if( mw.config.get( 'wbEntity' ) !== null ) { @@ -248,6 +242,26 @@ wb.compileEntityStoreFromMwConfig( entityStore ); // TODO: Integrate into entityview + $( '.wikibase-descriptionview' ) + .toolbarcontroller( { + edittoolbar: ['descriptionview'] + } ) + .descriptionview( { + value: { + language: mw.config.get( 'wgUserLanguage' ), + description: $( '.wikibase-descriptionview' ).hasClass( 'wb-empty' ) + ? null + // FIXME: entity object should not contain fallback strings + : entity.getDescription( mw.config.get( 'wgUserLanguage' ) ) + }, + helpMessage: mw.msg( + 'wikibase-description-input-help-message', + wb.getLanguageNameByCode( mw.config.get( 'wgUserLanguage' ) ) + ), + entityId: entity.getId(), + api: repoApi + } ); + $( '.wikibase-aliasesview' ) .toolbarcontroller( { edittoolbar: ['aliasesview'] @@ -303,9 +317,9 @@ // it to a sensible place. $( wb ) .on( 'startItemPageEditMode', function( event, target, options ) { - $( ':wikibase-aliasesview, :wikibase-sitelinklistview' ) + $( ':wikibase-descriptionview, :wikibase-aliasesview, :wikibase-sitelinklistview' ) + .not( target ) .find( ':wikibase-toolbar' ) - .not( $( target ).find( ':wikibase-toolbar' ) ) .each( function() { $( this ).data( 'toolbar' ).disable(); } ); @@ -314,6 +328,17 @@ $( ':wikibase-aliasesview' ).find( ':wikibase-toolbar' ).each( function() { $( this ).data( 'toolbar' ).enable(); } ); + $( ':wikibase-descriptionview' ).each( function() { + var $descriptionview = $( this ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + if( descriptionview.value().description ) { + $descriptionview.find( ':wikibase-toolbar' ).each( function() { + $( this ).data( 'toolbar' ).enable(); + } ); + } + } ); + $( ':wikibase-sitelinklistview' ).each( function() { var $sitelinklistview = $( this ), sitelinklistview = $sitelinklistview.data( 'sitelinklistview' ); diff --git a/repo/resources/wikibase.ui.initTermBox.js b/repo/resources/wikibase.ui.initTermBox.js index ae71b1a..57438e3 100644 --- a/repo/resources/wikibase.ui.initTermBox.js +++ b/repo/resources/wikibase.ui.initTermBox.js @@ -4,54 +4,56 @@ * @author: H. Snater < mediaw...@snater.com > */ ( function( $, mw, wb ) { - 'use strict'; +'use strict'; - /** - * Term box initialization. - * The term box displays label and description in languages other than the user language. - * @since 0.5 - * - * @param {wikibase.datamodel.Entity} entity - * @param {wikibase.RepoApi} api - */ - wb.ui.initTermBox = function( entity, api ) { - mw.hook( 'wikibase.domready' ).add( function() { - var termsValueTools = [], - $termBoxRows = $( 'tr.wb-terms-label, tr.wb-terms-description' ), - userSpecifiedLanguages = mw.config.get( 'wbUserSpecifiedLanguages' ), - hasSpecifiedLanguages = userSpecifiedLanguages && userSpecifiedLanguages.length, - isUlsDefined = mw.uls !== undefined - && $.uls !== undefined - && $.uls.data !== undefined; +/** + * Term box initialization. + * The term box displays label and description in languages other than the user language. + * @since 0.5 + * + * @param {wikibase.datamodel.Entity} entity + * @param {wikibase.RepoApi} api + */ +wb.ui.initTermBox = function( entity, api ) { + mw.hook( 'wikibase.domready' ).add( function() { + var termsValueTools = [], + $termBoxRows = $( 'tr.wb-terms-label, tr.wb-terms-description' ), + userSpecifiedLanguages = mw.config.get( 'wbUserSpecifiedLanguages' ), + hasSpecifiedLanguages = userSpecifiedLanguages && userSpecifiedLanguages.length, + isUlsDefined = mw.uls !== undefined + && $.uls !== undefined + && $.uls.data !== undefined; - // Skip if having no extra languages is what the user wants - if( !$termBoxRows.length && !hasSpecifiedLanguages && isUlsDefined ) { - // No term box present; Ask ULS to provide languages and generate plain HTML - var languageCodes = mw.uls.getFrequentLanguageList(), - title = new mw.Title( - mw.config.get( 'wgTitle' ), - mw.config.get( 'wgNamespaceNumber' ) - ); + $( '.wb-terms' ).toolbarcontroller( { + edittoolbar: ['terms-descriptionview'] + } ); - if( !languageCodes.length ) { - return; - } - - var $sectionHeading = addTermBoxSection(); - $sectionHeading.after( - renderTermBox( title, entity, languageCodes.slice( 1, 4 ) ) + // Skip if having no extra languages is what the user wants + if( !$termBoxRows.length && !hasSpecifiedLanguages && isUlsDefined ) { + // No term box present; Ask ULS to provide languages and generate plain HTML + var languageCodes = mw.uls.getFrequentLanguageList(), + title = new mw.Title( + mw.config.get( 'wgTitle' ), + mw.config.get( 'wgNamespaceNumber' ) ); - $termBoxRows = $( 'tr.wb-terms-label, tr.wb-terms-description' ); + if( !languageCodes.length ) { + return; } - $termBoxRows.each( function() { - var $termsRow = $( this ), - editTool = wb.ui.PropertyEditTool[ - $termsRow.hasClass( 'wb-terms-label' ) - ? 'EditableLabel' - : 'EditableDescription' - ], + var $sectionHeading = addTermBoxSection(); + $sectionHeading.after( + renderTermBox( title, entity, languageCodes.slice( 1, 4 ) ) + ); + + $termBoxRows = $( 'tr.wb-terms-label, tr.wb-terms-description' ); + } + + $termBoxRows.each( function() { + var $termsRow = $( this ); + + if( $termsRow.hasClass( 'wb-terms-label' ) ) { + var editTool = wb.ui.PropertyEditTool.EditableLabel, $toolbar = mw.template( 'wikibase-toolbar', '', '' ).toolbar(), toolbar = $toolbar.data( 'toolbar' ), $editGroup = mw.template( 'wikibase-toolbareditgroup', '', '' ) @@ -65,100 +67,212 @@ termsValueTools.push( editTool.newFromDom( $termsRow, { api: api }, toolbar ) ); + + return true; + } + + var languageCode; + + // TODO: Find more sane way to figure out language code. + $.each( $termsRow.attr( 'class' ).split( ' ' ), function( i, cssClass ) { + if( + cssClass.indexOf( 'wb-terms-' ) === 0 + && cssClass.indexOf( 'wb-terms-description' ) === -1 + ) { + languageCode = cssClass.replace( /wb-terms-/, '' ); + return false; + } } ); - $( wb ) - .on( 'startItemPageEditMode', function( event, origin ) { - // Disable language terms table's editable value or mark it as the active one if it - // is the one being edited by the user and therefore the origin of the event - $.each( termsValueTools, function( i, termValueTool ) { - if( - !( origin instanceof wb.ui.PropertyEditTool.EditableValue ) - || origin.getSubject() !== termValueTool.getSubject() - ) { - termValueTool.disable(); - } else if( origin && origin.getSubject() === termValueTool.getSubject() ) { - $( 'table.wb-terms' ).addClass( 'wb-edit' ); - } - } ); - } ) - .on( 'stopItemPageEditMode', function( event, origin ) { - $( 'table.wb-terms' ).removeClass( 'wb-edit' ); - $.each( termsValueTools, function( i, termValueTool ) { - termValueTool.enable(); - } ); + $termsRow.children( 'td' ).first().descriptionview( { + value: { + language: languageCode, + description: entity.getDescription( languageCode ) + }, + helpMessage: mw.msg( + 'wikibase-description-input-help-message', + wb.getLanguageNameByCode( languageCode ) + ), + entityId: entity.getId(), + api: api } ); } ); - }; - /** - * @return {jQuery} - */ - function addTermBoxSection() { - var $sectionHeading = mw.template( 'wb-terms-heading', mw.msg( 'wikibase-terms' ) ), - $toc = $( '#toc' ), - $precedingNode; - - if( $toc.length ) { - $toc - .children( 'ul' ).prepend( - $( '<li>' ) - .addClass( 'toclevel-1' ) - .append( - $( '<a>' ) - .attr( 'href', '#wb-terms' ) - .text( mw.msg( 'wikibase-terms' ) ) - ) - ) - .find( 'li' ).each( function( i, li ) { - $( li ) - .removeClass( 'tocsection-' + i ) - .addClass( 'tocsection-' + ( i + 1 ) ); + $( wb ) + .on( 'startItemPageEditMode', function( event, origin ) { + // Disable language terms table's editable value or mark it as the active one if it + // is the one being edited by the user and therefore the origin of the event + $.each( termsValueTools, function( i, termValueTool ) { + if( + !( origin instanceof wb.ui.PropertyEditTool.EditableValue ) + || origin.getSubject() !== termValueTool.getSubject() + ) { + termValueTool.disable(); + } else if( origin && origin.getSubject() === termValueTool.getSubject() ) { + $( 'table.wb-terms' ).addClass( 'wb-edit' ); + } } ); - $precedingNode = $toc; - } else { - $precedingNode = $( '.wb-aliases' ); - } + $termBoxRows.find( ':wikibase-descriptionview' ) + .not( origin ) + .each( function() { + $( this ).data( 'edittoolbar' ).toolbar.disable(); + } ); + } ) + .on( 'stopItemPageEditMode', function( event, origin ) { + $( 'table.wb-terms' ).removeClass( 'wb-edit' ); + $.each( termsValueTools, function( i, termValueTool ) { + termValueTool.enable(); + } ); - $precedingNode.after( $sectionHeading ); + $termBoxRows.find( ':wikibase-descriptionview' ).each( function() { + var descriptionview = $( this ).data( 'descriptionview' ); - return $sectionHeading; + if( descriptionview.value().description ) { + var toolbar = $( this ).data( 'edittoolbar' ).toolbar, + btnEdit = toolbar.editGroup.getButton( 'edit' ).data( 'toolbarbutton' ); + + $( this ).data( 'edittoolbar' ).toolbar.enable(); + + // FIXME: Get rid of StatableObject making things complicated + btnEdit.setState( btnEdit.STATE.ENABLED ); + } + } ); + } ); + + } ); +}; + +/** + * @return {jQuery} + */ +function addTermBoxSection() { + var $sectionHeading = mw.template( 'wb-terms-heading', mw.msg( 'wikibase-terms' ) ), + $toc = $( '#toc' ), + $precedingNode; + + if( $toc.length ) { + $toc + .children( 'ul' ).prepend( + $( '<li>' ) + .addClass( 'toclevel-1' ) + .append( + $( '<a>' ) + .attr( 'href', '#wb-terms' ) + .text( mw.msg( 'wikibase-terms' ) ) + ) + ) + .find( 'li' ).each( function( i, li ) { + $( li ) + .removeClass( 'tocsection-' + i ) + .addClass( 'tocsection-' + ( i + 1 ) ); + } ); + + $precedingNode = $toc; + } else { + $precedingNode = $( '.wb-aliases' ); } - /** - * @param {mediaWiki.Title} title - * @param {wikibase.datamodel.Entity} entity - * @param {string[]} languageCodes - * @return {jQuery|undefined} - */ - function renderTermBox( title, entity, languageCodes ) { - if( languageCodes === undefined ) { - return; - } - var labels = entity.getLabels(), - descriptions = entity.getDescriptions(), - $tbody = $( '<tbody>' ); + $precedingNode.after( $sectionHeading ); - for( var i = 0; i < languageCodes.length; i++ ) { - var languageCode = languageCodes[i]; + return $sectionHeading; +} - $tbody.append( mw.template( 'wb-term', - languageCode, - $.uls.data.getAutonym( languageCode ), - labels.hasOwnProperty( languageCode ) ? labels[languageCode] : '', - descriptions.hasOwnProperty( languageCode ) ? descriptions[languageCode] : '', - '', - '', - '', - '', - title.getUrl( { setlang: languageCode } ) - ) ); - } +/** + * @param {mediaWiki.Title} title + * @param {wikibase.datamodel.Entity} entity + * @param {string[]} languageCodes + * @return {jQuery|undefined} + */ +function renderTermBox( title, entity, languageCodes ) { + if( languageCodes === undefined ) { + return; + } + var labels = entity.getLabels(), + descriptions = entity.getDescriptions(), + $tbody = $( '<tbody>' ); - return mw.template( 'wb-terms-table', $tbody ); + for( var i = 0; i < languageCodes.length; i++ ) { + var languageCode = languageCodes[i]; + + $tbody.append( mw.template( 'wb-term', + languageCode, + $.uls.data.getAutonym( languageCode ), + labels.hasOwnProperty( languageCode ) ? labels[languageCode] : '', + descriptions.hasOwnProperty( languageCode ) ? descriptions[languageCode] : '', + '', + '', + '', + '', + title.getUrl( { setlang: languageCode } ) + ) ); } + return mw.template( 'wb-terms-table', $tbody ); +} + +// TODO: Merge with native descriptionview toolbar definiton +$.wikibase.toolbarcontroller.definition( 'edittoolbar', { + id: 'terms-descriptionview', + selector: '.wb-terms-description', + events: { + descriptionviewcreate: function( event, toolbarcontroller ) { + var $descriptionview = $( event.target ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + $descriptionview.edittoolbar( { + $container: $descriptionview.next(), + interactionWidgetName: $.wikibase.descriptionview.prototype.widgetName, + enableRemove: false + } ); + + $descriptionview.on( 'keyup', function( event ) { + if( event.keyCode === $.ui.keyCode.ESCAPE ) { + descriptionview.stopEditing( true ); + } else if( event.keyCode === $.ui.keyCode.ENTER ) { + descriptionview.stopEditing( false ); + } + } ); + + if( !descriptionview.value().description ) { + descriptionview.startEditing(); + } + }, + 'descriptionviewchange descriptionviewafterstartediting': function( event ) { + var $descriptionview = $( event.target ), + descriptionview = $descriptionview.data( 'descriptionview' ), + toolbar = $descriptionview.data( 'edittoolbar' ).toolbar, + $btnSave = toolbar.editGroup.getButton( 'save' ), + btnSave = $btnSave.data( 'toolbarbutton' ), + enable = descriptionview.isValid() && !descriptionview.isInitialValue(), + $btnCancel = toolbar.editGroup.getButton( 'cancel' ), + btnCancel = $btnCancel.data( 'toolbarbutton' ), + currentDescription = descriptionview.value().description, + disableCancel = !currentDescription && descriptionview.isInitialValue(); + + btnSave[enable ? 'enable' : 'disable'](); + btnCancel[disableCancel ? 'disable' : 'enable'](); + }, + descriptionviewafterstopediting: function( event, dropValue ) { + var $descriptionview = $( event.target ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + if( !descriptionview.value().description ) { + descriptionview.startEditing(); + } + }, + toolbareditgroupedit: function( event, toolbarcontroller ) { + var $descriptionview = $( event.target ).closest( ':wikibase-edittoolbar' ), + descriptionview = $descriptionview.data( 'descriptionview' ); + + if( !descriptionview ) { + return; + } + + descriptionview.focus(); + } + } +} ); } )( jQuery, mediaWiki, wikibase ); -- To view, visit https://gerrit.wikimedia.org/r/154388 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I3bbc60aac809e5268e38ecca3013ae4fc0b66fde Gerrit-PatchSet: 4 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Henning Snater <henning.sna...@wikimedia.de> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits