Mooeypoo has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/356056 )
Change subject: [wip] RCFilters: Add edit tags drop down ...................................................................... [wip] RCFilters: Add edit tags drop down Bug: T159942 Bug: T161650 Change-Id: I7bfa99cd5aeb34b6c7de74c15aac158ee40eac2f --- M languages/i18n/en.json M languages/i18n/qqq.json M resources/Resources.php M resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js M resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js M resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less M resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js M resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js 8 files changed, 198 insertions(+), 79 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/56/356056/1 diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 46d86f5..1631a9f 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -1453,6 +1453,8 @@ "rcfilters-filter-excluded": "Excluded", "rcfilters-tag-prefix-namespace": ":$1", "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1", + "rcfilters-tag-prefix-tags": "#$1", + "rcfilters-view-tags": "Tags", "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).", "rclistfromreset": "Reset date selection", "rclistfrom": "Show new changes starting from $2, $3", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index a9077ea..c5ce883 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -1641,6 +1641,8 @@ "rcfilters-filter-excluded": "Label for a menu item in [[Special:RecentChanges]] noting that the item is being excluded from the results.", "rcfilters-tag-prefix-namespace": "Prefix for the namespace tags in [[Special:RecentChanges]]. Namespace tags use a colon (:) as prefix. Please keep this format.\n\nParameters:\n* $1 - Filter name.", "rcfilters-tag-prefix-namespace-inverted": "Prefix for the namespace inverted tags in [[Special:RecentChanges]]. Namespace tags use a colon (:) as prefix. Please keep this format.\n\nParameters:\n* $1 - Filter name.", + "rcfilters-tag-prefix-tags": "Prefix for the edit tags in [[Special:RecentChanges]]. Namespace tags use a colon (:) as prefix. Please keep this format.\n\nParameters:\n* $1 - Tag display name.", + "rcfilters-view-tags": "Title for the tags view in [[Special:RecentChanges]]", "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL", "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.", "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.", diff --git a/resources/Resources.php b/resources/Resources.php index 6c20c2f..2851fc6 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1831,6 +1831,8 @@ 'rcfilters-filter-excluded', 'rcfilters-tag-prefix-namespace', 'rcfilters-tag-prefix-namespace-inverted', + 'rcfilters-tag-prefix-tags', + 'rcfilters-view-tags', 'blanknamespace', 'namespaces', 'invert', diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js index f60598f..c3ca0ff 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js @@ -201,12 +201,14 @@ * * @param {Array} filters Filter group definition * @param {Object} [namespaces] Namespace definition + * @param {Object[]} [tags] Tag array definition */ - mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters, namespaces ) { + mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters, namespaces, tags ) { var filterItem, filterConflictResult, groupConflictResult, model = this, items = [], namespaceDefinition = [], + tagDefinition = [], groupConflictMap = {}, filterConflictMap = {}, /*! @@ -353,8 +355,43 @@ items = items.concat( model.groups.namespace.getItems() ); } + tags = tags || []; + if ( tags.length ) { + // Define view + this.views.tags = { name: 'tags', label: mw.msg( 'rcfilters-view-tags' ), trigger: '#' }; + + // Build item data + tags.forEach( function ( tagData ) { + tagDefinition.push( { + name: tagData.name, + label: tagData.displayname, + description: tagData.description + } ); + } ); + + // Add the group + model.groups.tags = new mw.rcfilters.dm.FilterGroup( + 'tags', + { + type: 'string_options', + view: 'tags', + title: 'rcfilters-view-tags', // Message key + labelPrefixKey: 'rcfilters-tag-prefix-tags', + separator: ',', + fullCoverage: false + } + ); + + // Add tag items to group + model.groups.tags.initializeFilters( tagDefinition ); + + // Add item references to the model, for lookup + items = items.concat( model.groups.tags.getItems() ); + } + // Add item references to the model, for lookup this.addItems( items ); + // Expand conflicts groupConflictResult = expandConflictDefinitions( groupConflictMap ); filterConflictResult = expandConflictDefinitions( filterConflictMap ); @@ -756,12 +793,12 @@ groupTitle, result = {}, flatResult = [], - view = query.indexOf( this.getViewTrigger( 'namespaces' ) ) === 0 ? 'namespaces' : 'default', + view = this.getViewByTrigger( query.substr( 0, 1 ) ), items = this.getFiltersByView( view ); // Normalize so we can search strings regardless of case and view query = query.toLowerCase(); - if ( view === 'namespaces' ) { + if ( view !== 'default' ) { query = query.substr( 1 ); } @@ -839,6 +876,22 @@ return this.views[ this.getCurrentView() ].label; }; + mw.rcfilters.dm.FiltersViewModel.prototype.getAvailableViews = function () { + return Object.keys( this.views ); + }; + + mw.rcfilters.dm.FiltersViewModel.prototype.getViewByTrigger = function ( trigger ) { + var result = 'default'; + + $.each( this.views, function ( name, data ) { + if ( data.trigger === trigger ) { + result = name; + } + } ); + + return result; + }; + /** * Toggle the highlight feature on and off. * Propagate the change to filter items. diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js index 13dfbaf..45c8075 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js @@ -27,75 +27,80 @@ */ mw.rcfilters.Controller.prototype.initialize = function ( filterStructure, namespaceStructure ) { var parsedSavedQueries, validParameterNames, + controller = this, uri = new mw.Uri(), $changesList = $( '.mw-changeslist' ).first().contents(); // Initialize the model - this.filtersModel.initializeFilters( filterStructure, namespaceStructure ); - - this._buildBaseFilterState(); - this._buildEmptyParameterState(); - validParameterNames = Object.keys( this._getEmptyParameterState() ) - .filter( function ( param ) { - // Remove 'highlight' and 'invert' parameters from this check; - // if it's the only parameter in the URL we still - // want to consider the URL 'empty' for defaults to load - return param !== 'highlight' && param !== 'invert'; - } ); - - try { - parsedSavedQueries = JSON.parse( mw.user.options.get( 'rcfilters-saved-queries' ) || '{}' ); - } catch ( err ) { - parsedSavedQueries = {}; - } - - // The queries are saved in a minimized state, so we need - // to send over the base state so the saved queries model - // can normalize them per each query item - this.savedQueriesModel.initialize( - parsedSavedQueries, - this._getBaseFilterState() - ); - - // Check whether we need to load defaults. - // We do this by checking whether the current URI query - // is a subset of the base state. If it is, we don't load - // defaults. If it isn't, we have no values at all, and - // we need to load defaults. - // Defaults should only be applied on load (if necessary) - // or on request - if ( - Object.keys( uri.query ).some( function ( parameter ) { - return validParameterNames.indexOf( parameter ) > -1; + this._fetchEditTags() + .then( function ( tagList ) { + controller.filtersModel.initializeFilters( filterStructure, namespaceStructure, tagList ); } ) - ) { - // There are parameters in the url, update model state - this.updateStateBasedOnUrl(); - } else { - // No valid parameters are given, load defaults - this._updateModelState( - $.extend( true, - // We're ignoring the highlight and invert parameters, - // if they exist, in the check for "emptiness" but we - // should retain their value when we update the model state - { - highlight: String( Number( uri.query.highlight ) ), - invert: String( Number( uri.query.invert ) ) - }, - this._getDefaultParams() - ) - ); - this.updateChangesList(); - } + .then( function () { + this._buildBaseFilterState(); + this._buildEmptyParameterState(); + validParameterNames = Object.keys( this._getEmptyParameterState() ) + .filter( function ( param ) { + // Remove 'highlight' and 'invert' parameters from this check; + // if it's the only parameter in the URL we still + // want to consider the URL 'empty' for defaults to load + return param !== 'highlight' && param !== 'invert'; + } ); - this.switchView( 'default' ); + try { + parsedSavedQueries = JSON.parse( mw.user.options.get( 'rcfilters-saved-queries' ) || '{}' ); + } catch ( err ) { + parsedSavedQueries = {}; + } - // Update the changes list with the existing data - // so it gets processed - this.changesListModel.update( - $changesList.length ? $changesList : 'NO_RESULTS', - $( 'fieldset.rcoptions' ).first() - ); + // The queries are saved in a minimized state, so we need + // to send over the base state so the saved queries model + // can normalize them per each query item + this.savedQueriesModel.initialize( + parsedSavedQueries, + this._getBaseFilterState() + ); + + // Check whether we need to load defaults. + // We do this by checking whether the current URI query + // is a subset of the base state. If it is, we don't load + // defaults. If it isn't, we have no values at all, and + // we need to load defaults. + // Defaults should only be applied on load (if necessary) + // or on request + if ( + Object.keys( uri.query ).some( function ( parameter ) { + return validParameterNames.indexOf( parameter ) > -1; + } ) + ) { + // There are parameters in the url, update model state + this.updateStateBasedOnUrl(); + } else { + // No valid parameters are given, load defaults + this._updateModelState( + $.extend( true, + // We're ignoring the highlight and invert parameters, + // if they exist, in the check for "emptiness" but we + // should retain their value when we update the model state + { + highlight: String( Number( uri.query.highlight ) ), + invert: String( Number( uri.query.invert ) ) + }, + this._getDefaultParams() + ) + ); + this.updateChangesList(); + } + + this.switchView( 'default' ); + + // Update the changes list with the existing data + // so it gets processed + this.changesListModel.update( + $changesList.length ? $changesList : 'NO_RESULTS', + $( 'fieldset.rcoptions' ).first() + ); + }.bind( this ) ); }; /** @@ -755,6 +760,39 @@ }; /** + * Fetch edit tags + * + * @return {jQuery.Promise} Promise that is resolved with the + * array of edit tag information from the API. + */ + mw.rcfilters.Controller.prototype._fetchEditTags = function () { + var api = new mw.Api( { ajax: { cache: false } } ); + + return api.get( { + action: 'query', + list: 'tags', + tgprop: 'displayname|hitcount|description', + tglimit: 500 + } ) + .then( + function ( result ) { + var tags = OO.getProp( result, 'query', 'tags' ) || []; + + // Clean up the result. Displayname has html in it, and we + // don't want that in our view. + tags.forEach( function ( tagData ) { + tagData.displayname = $( '<span>' ).append( tagData.displayname ).text(); + } ); + + return tags; + }, + function () { + return []; + } + ); + }; + + /** * Track usage of highlight feature * * @param {string} action diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less index 2cd77a8..be611eb 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less @@ -3,7 +3,7 @@ // Make sure this uses the interface direction, not the content direction direction: ltr; - &-namespaceToggle { + &-viewToggleButtons { margin-top: 1em; } } diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js index d83f5f3..d8c79ca 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js @@ -171,6 +171,8 @@ if ( value.indexOf( this.model.getViewTrigger( 'namespaces' ) ) === 0 ) { view = 'namespaces'; + } else if ( value.indexOf( this.model.getViewTrigger( 'tags' ) ) === 0 ) { + view = 'tags'; } this.controller.switchView( view ); @@ -271,15 +273,19 @@ newInputValue = inputValue; switch ( view ) { + case 'tags': case 'namespaces': - if ( inputValue.indexOf( this.model.getViewTrigger( 'namespaces' ) ) !== 0 ) { + if ( inputValue.indexOf( this.model.getViewTrigger( view ) ) !== 0 ) { // Add the prefix to the input - newInputValue = this.model.getViewTrigger( 'namespaces' ) + inputValue; + newInputValue = this.model.getViewTrigger( view ) + inputValue; } break; default: case 'default': - if ( inputValue.indexOf( this.model.getViewTrigger( 'namespaces' ) ) === 0 ) { + if ( + inputValue.indexOf( this.model.getViewTrigger( 'namespaces' ) ) === 0 || + inputValue.indexOf( this.model.getViewTrigger( 'tags' ) ) === 0 + ) { // Remove the prefix newInputValue = inputValue.substr( 1 ); } diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js index ae8266a..4b93ae0 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js @@ -33,15 +33,26 @@ { $overlay: this.$overlay } ); - this.namespaceButton = new OO.ui.ButtonWidget( { - label: mw.msg( 'namespaces' ), - classes: [ 'mw-rcfilters-ui-filterWrapperWidget-namespaceToggle' ] + this.viewToggle = new OO.ui.ButtonGroupWidget( { + classes: [ 'mw-rcfilters-ui-filterWrapperWidget-viewToggleButtons' ], + items: [ + new OO.ui.ButtonWidget( { + data: 'namespaces', + label: mw.msg( 'namespaces' ), + classes: [ 'mw-rcfilters-ui-filterWrapperWidget-viewToggleButtons-namespace' ] + } ), + new OO.ui.ButtonWidget( { + data: 'tags', + label: mw.msg( 'rcfilters-view-tags' ), + classes: [ 'mw-rcfilters-ui-filterWrapperWidget-viewToggleButtons-tags' ] + } ), + ] } ); - this.namespaceButton.setActive( this.model.getCurrentView() === 'namespaces' ); // Events this.model.connect( this, { update: 'onModelUpdate' } ); - this.namespaceButton.connect( this, { click: 'onNamespaceToggleClick' } ); + this.viewToggle.aggregate( { click: 'itemClick' } ); + this.viewToggle.connect( this, { itemClick: 'onViewToggleClick' } ); // Initialize this.$element @@ -62,7 +73,7 @@ this.$element.append( this.filterTagWidget.$element, - this.namespaceButton.$element + this.viewToggle.$element ); }; @@ -77,15 +88,20 @@ * Respond to model update event */ mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelUpdate = function () { - // Synchronize the state of the toggle button with the current view - this.namespaceButton.setActive( this.model.getCurrentView() === 'namespaces' ); + var widget = this; + + // Synchronize the state of the toggle buttons with the current view + this.viewToggle.getItems().forEach( function ( buttonWidget ) { + buttonWidget.setActive( widget.model.getCurrentView() === buttonWidget.getData() ); + } ); }; /** * Respond to namespace toggle button click */ - mw.rcfilters.ui.FilterWrapperWidget.prototype.onNamespaceToggleClick = function () { - this.controller.switchView( 'namespaces' ); + mw.rcfilters.ui.FilterWrapperWidget.prototype.onViewToggleClick = function ( buttonWidget ) { + this.controller.switchView( buttonWidget.getData() ); this.filterTagWidget.focus(); }; + }( mediaWiki ) ); -- To view, visit https://gerrit.wikimedia.org/r/356056 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I7bfa99cd5aeb34b6c7de74c15aac158ee40eac2f Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Mooeypoo <mor...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits