RCFilters: Unify reading filters by views and adjust unit tests
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FiltersViewModel.js
index 53a1170..527b96d 100644 (file)
         * Set filters and preserve a group relationship based on
         * the definition given by an object
         *
-        * @param {Array} filters Filter group definition
-        * @param {Object} [namespaces] Namespace definition
+        * @param {Array} filterGroups Filters definition
+        * @param {Object} [views] Extra views definition
+        *  Expected in the following format:
+        *  {
+        *     namespaces: {
+        *       label: 'namespaces', // Message key
+        *       trigger: ':',
+        *       groups: [
+        *         {
+        *            // Group info
+        *            name: 'namespaces' // Parameter name
+        *            title: 'namespaces' // Message key
+        *            type: 'string_options',
+        *            separator: ';',
+        *            labelPrefixKey: { 'default': 'rcfilters-tag-prefix-namespace', inverted: 'rcfilters-tag-prefix-namespace-inverted' },
+        *            fullCoverage: true
+        *            items: []
+        *         }
+        *       ]
+        *     }
+        *  }
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters, namespaces ) {
-               var filterItem, filterConflictResult, groupConflictResult,
+       mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filterGroups, views ) {
+               var filterConflictResult, groupConflictResult,
+                       allViews = {},
                        model = this,
                        items = [],
-                       namespaceDefinition = [],
                        groupConflictMap = {},
                        filterConflictMap = {},
                        /*!
                this.groups = {};
                this.views = {};
 
-               // Filters
-               this.views.default = { name: 'default', label: mw.msg( 'rcfilters-filterlist-title' ) };
-               filters.forEach( function ( data ) {
-                       var i,
-                               group = data.name;
-
-                       if ( !model.groups[ group ] ) {
-                               model.groups[ group ] = new mw.rcfilters.dm.FilterGroup( group, {
-                                       type: data.type,
-                                       title: data.title ? mw.msg( data.title ) : group,
-                                       separator: data.separator,
-                                       fullCoverage: !!data.fullCoverage,
-                                       whatsThis: {
-                                               body: data.whatsThisBody,
-                                               header: data.whatsThisHeader,
-                                               linkText: data.whatsThisLinkText,
-                                               url: data.whatsThisUrl
-                                       }
-                               } );
-                       }
+               // Clone
+               filterGroups = OO.copy( filterGroups );
+
+               // Normalize definition from the server
+               filterGroups.forEach( function ( data ) {
+                       var i;
+                       // What's this information needs to be normalized
+                       data.whatsThis = {
+                               body: data.whatsThisBody,
+                               header: data.whatsThisHeader,
+                               linkText: data.whatsThisLinkText,
+                               url: data.whatsThisUrl
+                       };
+
+                       // Title is a msg-key
+                       data.title = data.title ? mw.msg( data.title ) : data.name;
 
                        // Filters are given to us with msg-keys, we need
                        // to translate those before we hand them off
                                data.filters[ i ].label = data.filters[ i ].label ? mw.msg( data.filters[ i ].label ) : data.filters[ i ].name;
                                data.filters[ i ].description = data.filters[ i ].description ? mw.msg( data.filters[ i ].description ) : '';
                        }
+               } );
 
-                       model.groups[ group ].initializeFilters( data.filters, data.default );
-                       items = items.concat( model.groups[ group ].getItems() );
-
-                       // Prepare conflicts
-                       if ( data.conflicts ) {
-                               // Group conflicts
-                               groupConflictMap[ group ] = data.conflicts;
+               // Collect views
+               allViews = {
+                       'default': {
+                               label: mw.msg( 'rcfilters-filterlist-title' ),
+                               groups: filterGroups
                        }
+               };
 
-                       for ( i = 0; i < data.filters.length; i++ ) {
-                               // Filter conflicts
-                               if ( data.filters[ i ].conflicts ) {
-                                       filterItem = model.groups[ group ].getItemByParamName( data.filters[ i ].name );
-                                       filterConflictMap[ filterItem.getName() ] = data.filters[ i ].conflicts;
+               if ( views && mw.config.get( 'wgStructuredChangeFiltersEnableExperimentalViews' ) ) {
+                       // If we have extended views, add them in
+                       $.extend( true, allViews, views );
+               }
+
+               // Go over all views
+               $.each( allViews, function ( viewName, viewData ) {
+                       // Define the view
+                       model.views[ viewName ] = {
+                               name: viewData.name,
+                               title: viewData.title,
+                               trigger: viewData.trigger
+                       };
+
+                       // Go over groups
+                       viewData.groups.forEach( function ( groupData ) {
+                               var group = groupData.name;
+
+                               model.groups[ group ] = new mw.rcfilters.dm.FilterGroup(
+                                       group,
+                                       $.extend( true, {}, groupData, { view: viewName } )
+                               );
+
+                               model.groups[ group ].initializeFilters( groupData.filters, groupData.default );
+                               items = items.concat( model.groups[ group ].getItems() );
+
+                               // Prepare conflicts
+                               if ( groupData.conflicts ) {
+                                       // Group conflicts
+                                       groupConflictMap[ group ] = groupData.conflicts;
                                }
-                       }
-               } );
 
-               namespaces = namespaces || {};
-               if (
-                       mw.config.get( 'wgStructuredChangeFiltersEnableExperimentalViews' ) &&
-                       !$.isEmptyObject( namespaces )
-               ) {
-                       // Namespaces group
-                       this.views.namespaces = { name: 'namespaces', label: mw.msg( 'namespaces' ), trigger: ':' };
-                       $.each( namespaces, function ( namespaceID, label ) {
-                               // Build and clean up the definition
-                               namespaceDefinition.push( {
-                                       name: namespaceID,
-                                       label: label || mw.msg( 'blanknamespace' ),
-                                       description: '',
-                                       identifiers: [
-                                               ( namespaceID < 0 || namespaceID % 2 === 0 ) ?
-                                                       'subject' : 'talk'
-                                       ],
-                                       cssClass: 'mw-changeslist-ns-' + namespaceID
+                               groupData.filters.forEach( function ( itemData ) {
+                                       var filterItem = model.groups[ group ].getItemByParamName( itemData.name );
+                                       // Filter conflicts
+                                       if ( itemData.conflicts ) {
+                                               filterConflictMap[ filterItem.getName() ] = itemData.conflicts;
+                                       }
                                } );
                        } );
-
-                       // Add the group
-                       model.groups.namespace = new mw.rcfilters.dm.FilterGroup(
-                               'namespace', // Parameter name is singular
-                               {
-                                       type: 'string_options',
-                                       view: 'namespaces',
-                                       title: 'namespaces', // Message key
-                                       separator: ';',
-                                       labelPrefixKey: { 'default': 'rcfilters-tag-prefix-namespace', inverted: 'rcfilters-tag-prefix-namespace-inverted' },
-                                       fullCoverage: true
-                               }
-                       );
-                       // Add namespace items to group
-                       model.groups.namespace.initializeFilters( namespaceDefinition );
-                       items = items.concat( model.groups.namespace.getItems() );
-               }
+               } );
 
                // Add item references to the model, for lookup
                this.addItems( items );
+
                // Expand conflicts
                groupConflictResult = expandConflictDefinitions( groupConflictMap );
                filterConflictResult = expandConflictDefinitions( filterConflictMap );
                        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 );
                }
 
                for ( i = 0; i < items.length; i++ ) {
                        if (
                                searchIsEmpty ||
-                               items[ i ].getLabel().toLowerCase().indexOf( query ) === 0
+                               items[ i ].getLabel().toLowerCase().indexOf( query ) === 0 ||
+                               (
+                                       // For tags, we want the parameter name to be included in the search
+                                       view === 'tags' &&
+                                       items[ i ].getParamName().toLowerCase().indexOf( query ) > -1
+                               )
                        ) {
                                result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
                                result[ items[ i ].getGroupName() ].push( items[ i ] );
                                        searchIsEmpty ||
                                        items[ i ].getLabel().toLowerCase().indexOf( query ) > -1 ||
                                        items[ i ].getDescription().toLowerCase().indexOf( query ) > -1 ||
-                                       groupTitle.toLowerCase().indexOf( query ) > -1
+                                       groupTitle.toLowerCase().indexOf( query ) > -1 ||
+                                       (
+                                               // For tags, we want the parameter name to be included in the search
+                                               view === 'tags' &&
+                                               items[ i ].getParamName().toLowerCase().indexOf( query ) > -1
+                                       )
                                ) {
                                        result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
                                        result[ items[ i ].getGroupName() ].push( items[ i ] );
         * @return {string} Label for the current view
         */
        mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentViewLabel = function () {
-               return this.views[ this.getCurrentView() ].label;
+               return this.views[ this.getCurrentView() ].title;
+       };
+
+       /**
+        * Get an array of all available view names
+        *
+        * @return {string} Available view names
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getAvailableViews = function () {
+               return Object.keys( this.views );
+       };
+
+       /**
+        * Get the view that fits the given trigger
+        *
+        * @param {string} trigger Trigger
+        * @return {string} Name of view
+        */
+       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;
        };
 
        /**