From 4064701b68bb7dd78dc74e37c24a595f718fdb7e Mon Sep 17 00:00:00 2001 From: petarpetkovic Date: Tue, 7 Nov 2017 17:55:37 +0100 Subject: [PATCH] Add overlay to list of changes - Prevent users from accidentally clicking links on filtered results, when RCFilters dropdown menu is opened, by adding overlay which closes dropdown menu. - Introduce MainWrapperWidget, to factor out some of the logic from the init file. Bug: T177626 Change-Id: Id55702ecbe6b96ee57453d4f86f20bd94a401d7c --- resources/Resources.php | 1 + .../mediawiki.rcfilters/mw.rcfilters.init.js | 124 ++++++++--------- ...rcfilters.ui.ChangesListWrapperWidget.less | 17 +++ ...w.rcfilters.ui.ChangesListWrapperWidget.js | 11 ++ .../ui/mw.rcfilters.ui.FilterWrapperWidget.js | 1 + .../ui/mw.rcfilters.ui.MainWrapperWidget.js | 125 ++++++++++++++++++ 6 files changed, 211 insertions(+), 68 deletions(-) create mode 100644 resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MainWrapperWidget.js diff --git a/resources/Resources.php b/resources/Resources.php index fbc4dbb56d..3d075e29b8 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1792,6 +1792,7 @@ return [ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuHeaderWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MenuSelectWidget.js', + 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MainWrapperWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ViewSwitchWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ValuePickerWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesLimitPopupWidget.js', diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js index 40b8bd2bb3..582d25fa34 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js @@ -8,10 +8,8 @@ * @private */ init: function () { - var $topLinks, - topSection, - $watchlistDetails, - namespaces, + var $topSection, + mainWrapperWidget, conditionalViews = {}, savedQueriesPreferenceName = mw.config.get( 'wgStructuredChangeFiltersSavedQueriesPreferenceName' ), daysPreferenceName = mw.config.get( 'wgStructuredChangeFiltersDaysPreferenceName' ), @@ -19,6 +17,7 @@ filtersModel = new mw.rcfilters.dm.FiltersViewModel(), changesListModel = new mw.rcfilters.dm.ChangesListViewModel(), savedQueriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ), + specialPage = mw.config.get( 'wgCanonicalSpecialPageName' ), controller = new mw.rcfilters.Controller( filtersModel, changesListModel, savedQueriesModel, { @@ -26,23 +25,17 @@ daysPreferenceName: daysPreferenceName, limitPreferenceName: limitPreferenceName } - ), - $overlay = $( '
' ) - .addClass( 'mw-rcfilters-ui-overlay' ), - filtersWidget = new mw.rcfilters.ui.FilterWrapperWidget( - controller, filtersModel, savedQueriesModel, changesListModel, { $overlay: $overlay } ), - savedLinksListWidget = new mw.rcfilters.ui.SavedLinksListWidget( - controller, savedQueriesModel, { $overlay: $overlay } - ), - specialPage = mw.config.get( 'wgCanonicalSpecialPageName' ), - $changesListRoot = $( [ - '.mw-changeslist', - '.mw-changeslist-empty', - '.mw-changeslist-timeout', - '.mw-changeslist-notargetpage' - ].join( ', ' ) ); + ); + + // TODO: The changesListWrapperWidget should be able to initialize + // after the model is ready. - if ( specialPage === 'Recentchangeslinked' ) { + if ( specialPage === 'Recentchanges' ) { + $topSection = $( '.mw-recentchanges-toplinks' ).detach(); + } else if ( specialPage === 'Watchlist' ) { + $( '#contentSub, form#mw-watchlist-resetbutton' ).remove(); + $topSection = $( '.watchlistDetails' ).detach().contents(); + } else if ( specialPage === 'Recentchangeslinked' ) { conditionalViews.recentChangesLinked = { groups: [ { @@ -73,41 +66,39 @@ } ] }; - } - // TODO: The changesListWrapperWidget should be able to initialize - // after the model is ready. - - // eslint-disable-next-line no-new - new mw.rcfilters.ui.ChangesListWrapperWidget( - filtersModel, changesListModel, controller, $changesListRoot ); + mainWrapperWidget = new mw.rcfilters.ui.MainWrapperWidget( + controller, + filtersModel, + savedQueriesModel, + changesListModel, + { + $topSection: $topSection, + $filtersContainer: $( '.rcfilters-container' ), + $changesListContainer: $( [ + '.mw-changeslist', + '.mw-changeslist-empty', + '.mw-changeslist-timeout', + '.mw-changeslist-notargetpage' + ].join( ', ' ) ), + $formContainer: $( 'fieldset.cloptions' ) + } + ); // Remove the -loading class that may have been added on the server side. // If we are in fact going to load a default saved query, this .initialize() // call will do that and add the -loading class right back. $( 'body' ).removeClass( 'mw-rcfilters-ui-loading' ); - // Remove Media namespace - namespaces = mw.config.get( 'wgFormattedNamespaces' ); - delete namespaces[ mw.config.get( 'wgNamespaceIds' ).media ]; - controller.initialize( mw.config.get( 'wgStructuredChangeFilters' ), - namespaces, + // All namespaces without Media namespace + this.getNamespaces( [ 'Media' ] ), mw.config.get( 'wgRCFiltersChangeTags' ), conditionalViews ); - // eslint-disable-next-line no-new - new mw.rcfilters.ui.FormWrapperWidget( - filtersModel, changesListModel, controller, $( 'fieldset.cloptions' ) ); - - $( '.rcfilters-container' ).append( filtersWidget.$element ); - $( 'body' ) - .append( $overlay ) - .addClass( 'mw-rcfilters-ui-initialized' ); - $( 'a.mw-helplink' ).attr( 'href', 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:New_filters_for_edit_review' @@ -115,33 +106,7 @@ controller.replaceUrl(); - if ( specialPage === 'Recentchanges' ) { - $topLinks = $( '.mw-recentchanges-toplinks' ).detach(); - - topSection = new mw.rcfilters.ui.RcTopSectionWidget( - savedLinksListWidget, $topLinks - ); - filtersWidget.setTopSection( topSection.$element ); - } // end Recentchanges - - if ( specialPage === 'Recentchangeslinked' ) { - topSection = new mw.rcfilters.ui.RclTopSectionWidget( - savedLinksListWidget, controller, - filtersModel.getGroup( 'toOrFrom' ).getItemByParamName( 'showlinkedto' ), - filtersModel.getGroup( 'page' ).getItemByParamName( 'target' ) - ); - filtersWidget.setTopSection( topSection.$element ); - } // end Recentchangeslinked - - if ( specialPage === 'Watchlist' ) { - $( '#contentSub, form#mw-watchlist-resetbutton' ).detach(); - $watchlistDetails = $( '.watchlistDetails' ).detach().contents(); - - topSection = new mw.rcfilters.ui.WatchlistTopSectionWidget( - controller, changesListModel, savedLinksListWidget, $watchlistDetails - ); - filtersWidget.setTopSection( topSection.$element ); - } // end Watchlist + mainWrapperWidget.setTopSection( specialPage ); /** * Fired when initialization of the filtering interface for changes list is complete. @@ -150,6 +115,29 @@ * @member mw.hook */ mw.hook( 'structuredChangeFilters.ui.initialized' ).fire(); + }, + + /** + * Get list of namespaces and remove unused ones + * + * @member mw.rcfilters + * @private + * + * @param {Array} unusedNamespaces Names of namespaces to remove + * @return {Array} Filtered array of namespaces + */ + getNamespaces: function ( unusedNamespaces ) { + var i, length, name, id, + namespaceIds = mw.config.get( 'wgNamespaceIds' ), + namespaces = mw.config.get( 'wgFormattedNamespaces' ); + + for ( i = 0, length = unusedNamespaces.length; i < length; i++ ) { + name = unusedNamespaces[ i ]; + id = namespaceIds[ name.toLowerCase() ]; + delete namespaces[ id ]; + } + + return namespaces; } }; diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less index ec311dfcf1..c379e2702a 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less @@ -12,6 +12,7 @@ } .mw-rcfilters-ui-changesListWrapperWidget { + position: relative; &-newChanges { min-height: 34px; @@ -67,6 +68,22 @@ .mw-changeslist-legend { background-color: @background-color-base; border: 1px solid @colorGray12; + + &:not( .mw-enhanced ) { + // We want to keep the legend accessible when results are overlaid + position: relative; + } + } + + .mw-changeslist-overlay { + position: absolute; + display: none; + width: 100%; + height: 100%; + } + + &--overlaid > .mw-changeslist-overlay { + display: block; } // Correction for Enhanced RC diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js index cd0b8ae2fc..e2092dc6df 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js @@ -180,6 +180,8 @@ } + this.$element.prepend( $( '
' ).addClass( 'mw-changeslist-overlay' ) ); + loaderPromise.done( function () { if ( !isInitialDOM && !isEmpty ) { // Make sure enhanced RC re-initializes correctly @@ -190,6 +192,14 @@ } ); }; + /** Toggles overlay class on changes list + * + * @param {boolean} isVisible True if overlay should be visible + */ + mw.rcfilters.ui.ChangesListWrapperWidget.prototype.toggleOverlay = function ( isVisible ) { + this.$element.toggleClass( 'mw-rcfilters-ui-changesListWrapperWidget--overlaid', isVisible ); + }; + /** * Map a reason for having no results to its message key * @@ -206,6 +216,7 @@ }; return reasonMsgKeyMap[ reason ]; }; + /** * Emphasize the elements (or groups) newer than the 'from' parameter * @param {string} from Anything newer than this is considered 'new' 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 a7d2c825e2..3373ee00ca 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js @@ -74,6 +74,7 @@ } // Events + this.filterTagWidget.menu.connect( this, { toggle: [ 'emit', 'menuToggle' ] } ); this.changesListModel.connect( this, { newChangesExist: 'onNewChangesExist' } ); this.showNewChangesLink.connect( this, { click: 'onShowNewChangesClick' } ); this.showNewChangesLink.toggle( false ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MainWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MainWrapperWidget.js new file mode 100644 index 0000000000..e64ffd8e7e --- /dev/null +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MainWrapperWidget.js @@ -0,0 +1,125 @@ +( function ( $, mw ) { + /** + * Wrapper for changes list content + * + * @extends OO.ui.Widget + * + * @constructor + * @param {mw.rcfilters.Controller} controller Controller + * @param {mw.rcfilters.dm.FiltersViewModel} model View model + * @param {mw.rcfilters.dm.SavedQueriesModel} savedQueriesModel Saved queries model + * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel + * @param {Object} config Configuration object + * @cfg {jQuery} $topSection Top section container + * @cfg {jQuery} $filtersContainer + * @cfg {jQuery} $changesListContainer + * @cfg {jQuery} $formContainer + */ + mw.rcfilters.ui.MainWrapperWidget = function MwRcfiltersUiMainWrapperWidget( + controller, model, savedQueriesModel, changesListModel, config + ) { + config = $.extend( {}, config ); + + // Parent + mw.rcfilters.ui.MainWrapperWidget.parent.call( this, config ); + + this.controller = controller; + this.model = model; + this.changesListModel = changesListModel; + this.$topSection = config.$topSection; + this.$filtersContainer = config.$filtersContainer; + this.$changesListContainer = config.$changesListContainer; + this.$formContainer = config.$formContainer; + this.$overlay = $( '
' ).addClass( 'mw-rcfilters-ui-overlay' ); + + this.savedLinksListWidget = new mw.rcfilters.ui.SavedLinksListWidget( + controller, savedQueriesModel, { $overlay: this.$overlay } + ); + + this.filtersWidget = new mw.rcfilters.ui.FilterWrapperWidget( + controller, + model, + savedQueriesModel, + changesListModel, + { + $overlay: this.$overlay + } + ); + + this.changesListWidget = new mw.rcfilters.ui.ChangesListWrapperWidget( + model, changesListModel, controller, this.$changesListContainer ); + + /* Events */ + + // Toggle changes list overlay when filters menu opens/closes. We use overlay on changes list + // to prevent users from accidentally clicking on links in results, while menu is opened. + // Overlay on changes list is not the same as this.$overlay + this.filtersWidget.connect( this, { menuToggle: this.onFilterMenuToggle.bind( this ) } ); + + // Initialize + this.$filtersContainer.append( this.filtersWidget.$element ); + $( 'body' ) + .append( this.$overlay ) + .addClass( 'mw-rcfilters-ui-initialized' ); + this.initFormWidget(); + }; + + /* Initialization */ + + OO.inheritClass( mw.rcfilters.ui.MainWrapperWidget, OO.ui.Widget ); + + /* Methods */ + + /** + * Set the content of the top section, depending on the type of special page. + * + * @param {string} specialPage + */ + mw.rcfilters.ui.MainWrapperWidget.prototype.setTopSection = function ( specialPage ) { + var topSection; + + if ( specialPage === 'Recentchanges' ) { + topSection = new mw.rcfilters.ui.RcTopSectionWidget( + this.savedLinksListWidget, this.$topSection + ); + this.filtersWidget.setTopSection( topSection.$element ); + } + + if ( specialPage === 'Recentchangeslinked' ) { + topSection = new mw.rcfilters.ui.RclTopSectionWidget( + this.savedLinksListWidget, this.controller, + this.model.getGroup( 'toOrFrom' ).getItemByParamName( 'showlinkedto' ), + this.model.getGroup( 'page' ).getItemByParamName( 'target' ) + ); + + this.filtersWidget.setTopSection( topSection.$element ); + } + + if ( specialPage === 'Watchlist' ) { + topSection = new mw.rcfilters.ui.WatchlistTopSectionWidget( + this.controller, this.changesListModel, this.savedLinksListWidget, this.$topSection + ); + + this.filtersWidget.setTopSection( topSection.$element ); + } + }; + + /** + * Filter menu toggle event listener + * + * @param {boolean} isVisible + */ + mw.rcfilters.ui.MainWrapperWidget.prototype.onFilterMenuToggle = function ( isVisible ) { + this.changesListWidget.toggleOverlay( isVisible ); + }; + + /** + * Initialize FormWrapperWidget + * + * @return {mw.rcfilters.ui.FormWrapperWidget} Form wrapper widget + */ + mw.rcfilters.ui.MainWrapperWidget.prototype.initFormWidget = function () { + return new mw.rcfilters.ui.FormWrapperWidget( + this.model, this.changesListModel, this.controller, this.$formContainer ); + }; +}( jQuery, mediaWiki ) ); -- 2.20.1