Henning Snater has uploaded a new change for review. https://gerrit.wikimedia.org/r/78246
Change subject: Using listview widget to group qualifiers ...................................................................... Using listview widget to group qualifiers (bug 52391) Qualifier snaks featuring the same property are grouped in separate snaklistview widgets now. These snaklistview widgets are managed by a listview widget. (The grouping is applied only when the qualifiers are initialized and not directly when a new qualifier snak is added.) Change-Id: I5a195c2d144e4cbab7d23dc785c27b8255ef4646 --- M lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js M lib/resources/jquery.wikibase/jquery.wikibase.claimview.js M lib/resources/jquery.wikibase/jquery.wikibase.listview.js M lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js M lib/resources/jquery.wikibase/jquery.wikibase.statementview.js M lib/resources/wikibase.datamodel/wikibase.Claim.js M lib/resources/wikibase.datamodel/wikibase.SnakList.js M selenium/lib/modules/qualifiers_module.rb 8 files changed, 384 insertions(+), 79 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase refs/changes/46/78246/1 diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js b/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js index b381812..616ca4e 100644 --- a/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js +++ b/lib/resources/jquery.wikibase/jquery.wikibase.claimlistview.js @@ -475,16 +475,26 @@ var $target = $( event.target ), statementview = $target.data( 'statementview' ), enable = statementview.isValid() && !statementview.isInitialValue(), - edittoolbar = $target.data( 'edittoolbar' ); + $btnSave = $target.data( 'edittoolbar' ).toolbar.editGroup.$btnSave, + snaklistviews = ( statementview._qualifiers ) ? statementview._qualifiers.value() : [], + areInitialQualifiers = true; - if ( statementview._qualifiers && ( - !statementview._qualifiers.isValid() - || statementview._qualifiers.isInitialValue() && statementview.isInitialValue() - ) ) { + // Statementview's isValid() validated the qualifiers already. However, the information + // whether all qualifiers (grouped by property) have changed, needs to be gathers + // separately: + if( enable && snaklistviews.length ) { + for( var i = 0; i < snaklistviews.length; i++ ) { + if( !snaklistviews[i].isInitialValue() ) { + areInitialQualifiers = false; + } + } + } + + if( areInitialQualifiers && statementview.isInitialValue() ) { enable = false; } - edittoolbar.toolbar.editGroup.$btnSave.data( 'toolbarbutton' )[ enable ? 'enable' : 'disable' ](); + $btnSave.data( 'toolbarbutton' )[ enable ? 'enable' : 'disable' ](); } }, options: { diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js b/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js index 8e0681b..82aad83 100644 --- a/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js +++ b/lib/resources/jquery.wikibase/jquery.wikibase.claimview.js @@ -101,11 +101,21 @@ _claim: null, /** - * Reference to the listview widget managing the qualifier snaks. Basically, just a short-cut - * for this.$qualifiers.data( 'listview' ) + * Reference to the listview widget managing the qualifier snaklistviews. Basically, just a + * short-cut for this.$qualifiers.data( 'listview' ) * @type {jquery.wikibase.listview} */ _qualifiers: null, + + /** + * Caches the snak list of the qualifiers the claimview has been initialized with. The + * qualifiers are split into groups featuring the same property. Removing one of those groups + * results in losing the reference to those qualifiers. Therefore, _initialQualifiers is used + * to rebuild the list of qualifiers when cancelling and is used to query whether the qualifiers + * represent the initial state. + * @type {wikibase.SnakList} + */ + _initialQualifiers: null, /** * Whether the Claim is currently in edit mode. @@ -136,18 +146,22 @@ } ); // Initialize qualifiers: - // TODO: Allow adding qualifiers when adding a new claim. - if ( this.option( 'value' ) ) { - var $qualifiers = $( '<div/>' ) - .prependTo( this.$qualifiers ) - .snaklistview( { - value: ( this._claim ) ? this._claim.getQualifiers() : null - } ) - .on( 'snaklistviewchange', function( event ) { - self._trigger( 'change' ); - } ); + this._initialQualifiers = ( this._claim ) ? this._claim.getQualifiers() : new wb.SnakList(); - this._qualifiers = $qualifiers.data( 'snaklistview' ); + if( this._claim ) { // TODO: Allow adding qualifiers when adding a new claim. + var qualifiers = this._claim.getQualifiers(); + + // Group qualifiers by property id: + if( qualifiers.length ) { + var propertyIds = qualifiers.getPropertyOrder(), + groupedQualifierSnaks = []; + + for( var i = 0; i < propertyIds.length; i++ ) { + groupedQualifierSnaks.push( qualifiers.getFilteredSnakList( propertyIds[i] ) ); + } + + this._createQualifiersListview( groupedQualifierSnaks ); + } } this._attachEditModeEventHandlers(); @@ -166,15 +180,83 @@ }, /** - * Returns whether the claimview is valid according to its current contents. An Empty value + * Creates the listview widget containing the qualifier snaklistview widgets. Omitting + * groupedQualifierSnaks parameter generates an empty list widget. + * @since 0.4 + * + * @param {wikibase.SnakList} [groupedQualifierSnaks] + */ + _createQualifiersListview: function( groupedQualifierSnaks ) { + var self = this; + + // Using the property id, qualifier snaks are split into groups of snaklistviews. These + // snaklistviews are managed in a listview: + var $qualifiers = $( '<div/>' ) + .prependTo( this.$qualifiers ) + .listview( { + listItemAdapter: new $.wikibase.listview.ListItemAdapter( { + listItemWidget: $.wikibase.snaklistview, + listItemWidgetValueAccessor: 'value', + newItemOptionsFn: function( value ) { + return { + value: value || null + }; + } + } ), + value: groupedQualifierSnaks || null + } ) + .on( 'snaklistviewchange.' + this.widgetName, function( event ) { + self._trigger( 'change' ); + } ) + .on( 'listviewitemremoved.' + this.widgetName, function( event, value, $itemNode ) { + if( event.target === self._qualifiers.element.get( 0 ) ) { + self._trigger( 'change' ); + return; + } + + // Check if last snaklistview of a qualifier listview item has been removed and + // remove the listview item if so: + var $snaklistview = $( event.target ).closest( ':wikibase-snaklistview' ), + snaklistview = $snaklistview.data( 'snaklistview' ); + + if( !snaklistview.value() ) { + self._qualifiers.removeItem( snaklistview.element ); + } + } ); + + this._qualifiers = $qualifiers.data( 'listview' ); + }, + + /** + * Destroys the listview widget containing the qualifier snaklistview widgets. + */ + _destroyQualifiersListView: function() { + if( this._qualifiers ) { + this._qualifiers.destroy(); + this.$qualifiers.empty(); + this._qualifiers = null; + } + }, + + /** + * Returns whether the claimview is valid according to its current contents. An empty value * will be considered not valid (also, an empty value can not be saved). * @since 0.4 * * @return {boolean} */ isValid: function() { - if( this._qualifiers && !this._qualifiers.isValid() ) { - return false; + // Validate qualifiers: + if( this._qualifiers ) { + var snaklistviews = this._qualifiers.value(); + + if( snaklistviews.length ) { + for( var i = 0; i < snaklistviews.length; i++ ) { + if( !snaklistviews[i].isValid() ) { + return false; + } + } + } } try { @@ -182,6 +264,7 @@ } catch( e ) { return false; } + return true; }, @@ -190,11 +273,29 @@ * the claim has been initialized with. * @since 0.4 * - * @returns {boolean} + * @return {boolean} */ isInitialValue: function() { - return this.$mainSnak.data( 'snakview' ).isInitialSnak() - && ( !this._qualifiers || this._qualifiers.isInitialValue() ); + if( this._claim ) { + var snaklistviews = ( this._qualifiers ) ? this._qualifiers.value() : [], + qualifiers = new wb.SnakList(); + + // Generate a SnakList object featuring all current qualifier snaks to be able to + // compare it to the SnakList object the claimview has been initialized with: + if( snaklistviews.length ) { + for( var i = 0; i < snaklistviews.length; i++ ) { + if( snaklistviews[i].value() ) { + qualifiers.add( snaklistviews[i].value() ); + } + } + } + + if( !qualifiers.equals( this._initialQualifiers ) ) { + return false; + } + } + + return this.$mainSnak.data( 'snakview' ).isInitialSnak(); }, /** @@ -214,9 +315,23 @@ natively: function( e ) { this.$mainSnak.data( 'snakview' ).startEditing(); - if ( this._qualifiers ) { - this._qualifiers.startEditing(); + if( !this._qualifiers && this._claim ) { + this._createQualifiersListview(); } + + // Start edit mode of all qualifiers: + if( this._qualifiers ) { + var snaklistviews = this._qualifiers.value(); + if ( snaklistviews.length ) { + for( var i = 0; i < snaklistviews.length; i++ ) { + snaklistviews[i].startEditing(); + } + } + // If there are no snaklistviews, there is no way for the "add qualifier" toolbar + // to be + this._qualifiers.element.trigger( 'qualifiersstartediting' ); + } + this.element.addClass( 'wb-edit' ); this._isInEditMode = true; @@ -258,8 +373,39 @@ this.$mainSnak.data( 'snakview' ).stopEditing( dropValue ); } - if ( this._qualifiers ) { - this._qualifiers.stopEditing( dropValue ); + // Stop edit mode of qualifier snaklistviews: + if( this._qualifiers ) { + var snaklistviews = this._qualifiers.value(); + + if ( snaklistviews.length ) { + for( var i = 0; i < snaklistviews.length; i++ ) { + snaklistviews[i].stopEditing( dropValue ); + + // Remove snaklistview from qualifier listview if no snakviews left in + // that snaklistview: + if( !snaklistviews[i].value() ) { + this._qualifiers.removeItem( snaklistviews[i].element ); + } + } + } + } + + // Destroy listview which will also send out events to erase the "add qualifier" + // toolbar: + this._destroyQualifiersListView(); + + // Refill the qualifier listview with the initial qualifiers: + if( this._initialQualifiers.length > 0 ) { + var qualifierSnakLists = [], + propertyIds = this._initialQualifiers.getPropertyOrder(); + + for( var i = 0; i < propertyIds.length; i++ ) { + qualifierSnakLists.push( + this._initialQualifiers.getFilteredSnakList( propertyIds[i] ) + ); + } + + this._createQualifiersListview( qualifierSnakLists ); } self.enable(); @@ -275,8 +421,14 @@ .done( function( savedClaim, pageInfo ) { self.$mainSnak.data( 'snakview' ).stopEditing( dropValue ); - if ( self._qualifiers ) { - self._qualifiers.stopEditing(); + if( self._qualifiers ) { + var snaklistviews = self._qualifiers.value(); + + if ( snaklistviews.length ) { + for( var i = 0; i < snaklistviews.length; i++ ) { + snaklistviews[i].stopEditing(); + } + } } self.enable(); @@ -341,7 +493,7 @@ defaultHandling( event, dropValue ); } ); - if ( this._qualifiers ) { + if( this._qualifiers && this._qualifiers.value().length ) { this._qualifiers.element.one( 'snaklistviewstopediting', function( event, dropValue ) { defaultHandling( event, dropValue ); } ); @@ -355,7 +507,7 @@ _detachEditModeEventHandlers: function() { this.$mainSnak.off( 'snakviewstopediting' ); - if ( this._qualifiers ) { + if ( this._qualifiers && this._qualifiers.value().length ) { this._qualifiers.element.off( 'snaklistviewstopediting' ); } }, @@ -379,9 +531,17 @@ * @throws {Error} In case the widget's current value is insufficient for building a claim. */ _instantiateClaim: function( guid ) { + var qualifiers = new wb.SnakList(), + snaklistviews = this._qualifiers.value(); + + // Combine qualifiers grouped by property to a single SnakList: + for( var i = 0; i < snaklistviews.length; i++ ) { + qualifiers.add( snaklistviews[i].value() ); + } + return new wb.Claim( this.$mainSnak.data( 'snakview' ).snak(), - ( this._qualifiers ) ? this._qualifiers.value() : null, + qualifiers, guid ); }, @@ -415,12 +575,6 @@ // Update model of represented Claim: self._claim = savedClaim; - - // If the claim was pending (adding a new claim instead of editing an existing one), - // there are no qualifiers set yet. - if ( self._qualifiers ) { - self._qualifiers.value( savedClaim.getQualifiers() ); - } } ); }, @@ -519,13 +673,53 @@ id: 'claim-qualifiers-snak', selector: '.wb-claim-qualifiers', events: { - snaklistviewstartediting: 'create', - snaklistviewafterstopediting: 'destroy', + 'listviewcreate snaklistviewstartediting': function( event ) { + var $target = $( event.target ), + $qualifiers = $target.closest( '.wb-claim-qualifiers' ), + listview = $target.closest( ':wikibase-listview' ).data( 'listview' ); + + if( + event.type === 'listviewcreate' && listview.items().length === 0 + || event.type === 'snaklistviewstartediting' + ) { + $qualifiers.addtoolbar( { + customAction: function( event ) { + listview.enterNewItem(); + listview.value()[listview.value().length - 1].enterNewItem(); + }, + eventPrefix: $.wikibase.snaklistview.prototype.widgetEventPrefix, + addButtonLabel: mw.msg( 'wikibase-addqualifier' ) + } ); + } + }, + 'listviewdestroy snaklistviewafterstopediting': function( event ) { + var $target = $( event.target ), + $qualifiers = $target.closest( '.wb-claim-qualifiers' ); + + if ( $qualifiers.data( 'addtoolbar' ) && ( $target.closest( ':wikibase-listview' ).length === 0 || event.type !== 'listviewdestroy' ) ) { + $qualifiers.data( 'addtoolbar' ).destroy(); + $qualifiers.removeData( 'addtoolbar' ); + $qualifiers + .children( '.' + $.wikibase.addtoolbar.prototype.widgetBaseClass ) + .remove(); + } + }, snaklistviewchange: function( event ) { - var snaklistview = $( event.target ).data( 'snaklistview' ), - addToolbar = $( event.target ).data( 'addtoolbar' ); + var $target = $( event.target ), + snaklistview = $target.data( 'snaklistview' ), + $qualifiers = $target.closest( '.wb-claim-qualifiers' ), + addToolbar = $qualifiers.data( 'addtoolbar' ), + $listview = $target.closest( ':wikibase-listview' ), + snaklistviews = $listview.data( 'listview' ).value(); + if ( addToolbar ) { - addToolbar.toolbar[snaklistview.isValid() ? 'enable' : 'disable'](); + addToolbar.toolbar.enable(); + for( var i = 0; i < snaklistviews.length; i++ ) { + if( !snaklistviews[i].isValid() ) { + addToolbar.toolbar.disable(); + break; + } + } } }, snaklistviewdisable: function( event ) { @@ -562,13 +756,6 @@ addToolbar.toolbar.enable(); } } - }, - options: { - customAction: function( event, $parent ) { - $parent.data( 'snaklistview' ).enterNewItem(); - }, - eventPrefix: $.wikibase.snaklistview.prototype.widgetEventPrefix, - addButtonLabel: mw.msg( 'wikibase-addqualifier' ) } } ); @@ -576,18 +763,18 @@ id: 'claim-qualifiers-snak', selector: '.wb-claim-qualifiers', events: { - 'snakviewstartediting snakviewcreate listviewitemadded listviewitemremoved': function( event ) { + 'snakviewstartediting': function( event ) { var $target = $( event.target ), - listview = $target.closest( '.wb-snaklistview' ).data( 'snaklistview' )._listview; + $snaklistview = $target.closest( '.wb-snaklistview' ), + qualifierPorpertyGroupListview = $snaklistview.data( 'snaklistview' )._listview, + qualifiersListview = $snaklistview.closest( '.wb-listview' ).data( 'listview' ); - if ( event.type.indexOf( 'snakview' ) !== -1 ) { - // Create toolbar for each snakview widget: - $target.removetoolbar( { - action: function( event ) { - listview.removeItem( $target ); - } - } ); - } + // Create toolbar for each snakview widget: + $target.removetoolbar( { + action: function( event ) { + qualifierPorpertyGroupListview.removeItem( $target ); + } + } ); }, snaklistviewafterstopediting: function( event ) { // Destroy the snakview toolbars: diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.listview.js b/lib/resources/jquery.wikibase/jquery.wikibase.listview.js index 918096e..c49dae6 100644 --- a/lib/resources/jquery.wikibase/jquery.wikibase.listview.js +++ b/lib/resources/jquery.wikibase/jquery.wikibase.listview.js @@ -48,6 +48,9 @@ * @event enternewitem: Triggered when initializing the process of adding a new item to the list. * (1) {jQuery.Event} * (2) {jQuery} The DOM node pending to be added permanently to the list. + * + * @event destroy: Triggered then the widget has been destroyed. + * (1) {jQuery.Event} */ $.widget( 'wikibase.listview', PARENT, { widgetBaseClass: 'wb-listview', @@ -97,6 +100,7 @@ destroy: function() { this.element.removeClass( this.widgetBaseClass ); $.Widget.prototype.destroy.call( this ); + this._trigger( 'destroy' ); }, /** @@ -126,6 +130,43 @@ }, /** + * Sets/gets the listview's list item instances. + * + * @param {*[]} [value] + * @return {*[]} + */ + value: function( value ) { + var self = this; + + // Getter: + if( value === undefined ) { + var values = []; + + this.items().each( function( i, node ) { + values.push( self._lia.liInstance( $( node ) ) ); + } ); + + return values; + } + + // Clear listview: + this.items().each( function( i, node ) { + var $node = $( node ); + self._lia.liInstance( $node ).destroy(); + $node.remove(); + } ); + + // Add new values: + for( var i = 0; i < value.length; i++ ) { + var $newLi = $( '<div/>' ); + this.element.append( $newLi ); + this._lia.newListItem( $newLi, value[i] ); + } + + return value; + }, + + /** * Returns all list item nodes. * * @since 0.4 diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js b/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js index 8994df4..4a3fe0f 100644 --- a/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js +++ b/lib/resources/jquery.wikibase/jquery.wikibase.snaklistview.js @@ -307,8 +307,15 @@ throw new Error( 'Value has to be an instance of wikibase.SnakList' ); } + var wasInEditMode = this.isInEditMode(); + this._snakList = snakList; this.createListView(); + + if( wasInEditMode ) { + this.startEditing(); + } + return this._snakList; } // getter: diff --git a/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js b/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js index 062d1fc..140eff3 100644 --- a/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js +++ b/lib/resources/jquery.wikibase/jquery.wikibase.statementview.js @@ -33,7 +33,7 @@ '', // TODO: This toolbar placeholder should be removed from the template. '', // .wb-claim-mainsnak '', // Qualifiers - '', // Rreferences heading + '', // References heading '' // List of references ], templateShortCuts: { @@ -158,9 +158,22 @@ * @return {wb.Statement} */ _instantiateClaim: function( guid ) { + var qualifiers = null; + + // Gather qualifiers split into property groups in one single wb.SnakList object: + if( this._qualifiers ) { + var snaklistviews = this._qualifiers.value(); + + qualifiers = new wb.SnakList(); + + for( var i = 0; i < snaklistviews.length; i++ ) { + qualifiers.add( snaklistviews[i].value() ); + } + } + return new wb.Statement( this.$mainSnak.data( 'snakview' ).snak(), - ( this._qualifiers ) ? this._qualifiers.value() : null, + qualifiers, this.getReferences(), null, // TODO: Rank guid diff --git a/lib/resources/wikibase.datamodel/wikibase.Claim.js b/lib/resources/wikibase.datamodel/wikibase.Claim.js index 94b7199..8883b84 100644 --- a/lib/resources/wikibase.datamodel/wikibase.Claim.js +++ b/lib/resources/wikibase.datamodel/wikibase.Claim.js @@ -84,8 +84,20 @@ * * @return wb.SnakList */ - getQualifiers: function() { - return this._qualifiers; + getQualifiers: function( propertyId ) { + if( !propertyId ) { + return this._qualifiers; + } + + var filteredQualifiers = new wb.SnakList(); + + this._qualifiers.each( function( i, snak ) { + if( snak.getPropertyId() === propertyId ) { + filteredQualifiers.addSnak( snak ); + } + } ); + + return filteredQualifiers; }, /** diff --git a/lib/resources/wikibase.datamodel/wikibase.SnakList.js b/lib/resources/wikibase.datamodel/wikibase.SnakList.js index 97384c1..8b7a45f 100644 --- a/lib/resources/wikibase.datamodel/wikibase.SnakList.js +++ b/lib/resources/wikibase.datamodel/wikibase.SnakList.js @@ -97,6 +97,30 @@ }, /** + * Returns a snak list with the snaks that feature the specified property id. If the property id + * parameter is omitted, a copy of the whole SnakList object is returned. + * @since 0.4 + * + * @param {string} [propertyId] + * @return {wikibase.SnakList} + */ + getFilteredSnakList: function( propertyId ) { + if( !propertyId ) { + return this.newFromJSON( this.toJSON() ); + } + + var filteredQualifiers = new wb.SnakList(); + + this.each( function( i, snak ) { + if( snak.getPropertyId() === propertyId ) { + filteredQualifiers.addSnak( snak ); + } + } ); + + return filteredQualifiers; + }, + + /** * Returns whether the list contains a Snak equal to a given one. * * @since 0.4 @@ -149,6 +173,17 @@ */ each: function( fn ) { $.each.call( null, this._snaks, fn ); + }, + + /** + * Adds the snaks of another snak list to this snak list. + * @since 0.4 + * + * @param {wikibase.SnakList} snakList + */ + add: function( snakList ) { + $.merge( this._snaks, snakList.toArray() ); + this.length += snakList.length; }, /** @@ -206,7 +241,7 @@ /** * Creates a new Snak Object from a given JSON structure. * - * @param {string} json + * @param {Object} json * @param {string[]} [order] List of property ids defining the order of the snaks grouped by * property. * @return {wikibase.SnakList|null} diff --git a/selenium/lib/modules/qualifiers_module.rb b/selenium/lib/modules/qualifiers_module.rb index 9677d1a..db24e9c 100644 --- a/selenium/lib/modules/qualifiers_module.rb +++ b/selenium/lib/modules/qualifiers_module.rb @@ -10,21 +10,21 @@ include PageObject # qualifiers UI elements div(:qualifiersContainer, :class => "wb-claim-qualifiers") - link(:addQualifier, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/span[contains(@class, 'wb-addtoolbar')]/div/span/span/a[not(contains(@class, 'wikibase-toolbarbutton-disabled'))][text()='add qualifier']") - link(:addQualifierDisabled, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/span[contains(@class, 'wb-addtoolbar')]/div/span/span/a[contains(@class, 'wikibase-toolbarbutton-disabled')][text()='add qualifier']") - link(:removeQualifierLine1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][1]/span[contains(@class, 'wb-removetoolbar')]/div/span/span/a[not(contains(@class, 'wikibase-toolbarbutton-disabled'))][text()='remove']") - link(:removeQualifierLine2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][2]/span[contains(@class, 'wb-removetoolbar')]/div/span/span/a[not(contains(@class, 'wikibase-toolbarbutton-disabled'))][text()='remove']") + link(:addQualifier, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]//span[contains(@class, 'wb-addtoolbar')]//a[not(contains(@class, 'wikibase-toolbarbutton-disabled'))][text()='add qualifier']") + link(:addQualifierDisabled, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]//span[contains(@class, 'wb-addtoolbar')]//a[contains(@class, 'wikibase-toolbarbutton-disabled')][text()='add qualifier']") + link(:removeQualifierLine1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][1]//span[contains(@class, 'wb-removetoolbar')]//a[not(contains(@class, 'wikibase-toolbarbutton-disabled'))][text()='remove']") + link(:removeQualifierLine2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][2]//span[contains(@class, 'wb-removetoolbar')]//a[not(contains(@class, 'wikibase-toolbarbutton-disabled'))][text()='remove']") text_area(:qualifierValueInput1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]//textarea[contains(@class, 'valueview-input')]", :index => 0) text_area(:qualifierValueInput2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]//textarea[contains(@class, 'valueview-input')]", :index => 1) - link(:qualifierPropertyLink1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][1]/div[contains(@class, 'wb-snak-property-container')]/div/a") - link(:qualifierPropertyLink2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][2]/div[contains(@class, 'wb-snak-property-container')]/div/a") - div(:qualifierProperty1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][1]/div[contains(@class, 'wb-snak-property-container')]/div") - div(:qualifierProperty2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][2]/div[contains(@class, 'wb-snak-property-container')]/div") - link(:qualifierValueLink1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][1]/div[contains(@class, 'wb-snak-value-container')]/div[contains(@class, 'wb-snak-value')]/div/div/a") - link(:qualifierValueLink2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][2]/div[contains(@class, 'wb-snak-value-container')]/div[contains(@class, 'wb-snak-value')]/div/div/a") - div(:qualifierValue1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][1]/div[contains(@class, 'wb-snak-value-container')]/div[contains(@class, 'wb-snak-value')]/div/div") - div(:qualifierValue2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div[contains(@class, 'wb-snaklistview')]/div[contains(@class, 'wb-snaklistview-listview')]/div[contains(@class, 'wb-snakview')][2]/div[contains(@class, 'wb-snak-value-container')]/div[contains(@class, 'wb-snak-value')]/div/div") + div(:qualifierProperty1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][1]//div[contains(@class, 'wb-snak-property-container')]/div") + div(:qualifierProperty2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][2]//div[contains(@class, 'wb-snak-property-container')]/div") + link(:qualifierPropertyLink1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][1]//div[contains(@class, 'wb-snak-property-container')]//a") + link(:qualifierPropertyLink2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][2]//div[contains(@class, 'wb-snak-property-container')]//a") + link(:qualifierValueLink1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][1]//div[contains(@class, 'wb-snak-value')]//a") + link(:qualifierValueLink2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][2]//div[contains(@class, 'wb-snak-value')]//a") + div(:qualifierValue1, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][1]//div[contains(@class, 'wb-snak-value')]/div/div") + div(:qualifierValue2, :xpath => "//div[contains(@class, 'wb-claim-qualifiers')]/div/div[contains(@class, 'wb-snaklistview')][2]//div[contains(@class, 'wb-snak-value')]/div/div") def wait_for_qualifier_value_box wait_until do -- To view, visit https://gerrit.wikimedia.org/r/78246 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I5a195c2d144e4cbab7d23dc785c27b8255ef4646 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Henning Snater <henning.sna...@wikimedia.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits