X-Git-Url: https://git.cyclocoop.org/%27.WWW_URL.%27admin/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki.rcfilters%2Fdm%2Fmw.rcfilters.dm.FiltersViewModel.js;h=527b96d7226f13e22417335d7670052fc4110efd;hb=08703ee29f21fa3c8dfa13e2763491845242ebfc;hp=88ce33c19a9287f5af94671e0134276cba4cc9a9;hpb=e1090009434d4908a37c8bc481fe07e96c5f647f;p=lhc%2Fweb%2Fwiklou.git 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 88ce33c19a..527b96d722 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js @@ -16,8 +16,12 @@ this.defaultParams = {}; this.defaultFiltersEmpty = null; this.highlightEnabled = false; + this.invertedNamespaces = false; this.parameterMap = {}; + this.views = {}; + this.currentView = null; + // Events this.aggregate( { update: 'filterItemUpdate' } ); this.connect( this, { filterItemUpdate: [ 'emit', 'itemUpdate' ] } ); @@ -36,6 +40,12 @@ * Filter list is initialized */ + /** + * @event update + * + * Model has been updated + */ + /** * @event itemUpdate * @param {mw.rcfilters.dm.FilterItem} item Filter item updated @@ -50,6 +60,13 @@ * Highlight feature has been toggled enabled or disabled */ + /** + * @event invertChange + * @param {boolean} isInverted Namespace selected is inverted + * + * Namespace selection is inverted or straight forward + */ + /* Methods */ /** @@ -189,10 +206,31 @@ * Set filters and preserve a group relationship based on * the definition given by an object * - * @param {Array} filters Filter group 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 ) { - var filterItem, filterConflictResult, groupConflictResult, + mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filterGroups, views ) { + var filterConflictResult, groupConflictResult, + allViews = {}, model = this, items = [], groupConflictMap = {}, @@ -258,41 +296,81 @@ // Reset this.clearItems(); this.groups = {}; + this.views = {}; + + // 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 + }; - 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: mw.msg( data.title ), - separator: data.separator, - fullCoverage: !!data.fullCoverage, - 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 + for ( i = 0; i < data.filters.length; i++ ) { + 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; } - } + + groupData.filters.forEach( function ( itemData ) { + var filterItem = model.groups[ group ].getItemByParamName( itemData.name ); + // Filter conflicts + if ( itemData.conflicts ) { + filterConflictMap[ filterItem.getName() ] = itemData.conflicts; + } + } ); + } ); } ); // Add item references to the model, for lookup @@ -327,6 +405,8 @@ } } ); + this.currentView = 'default'; + // Finish initialization this.emit( 'initialize' ); }; @@ -349,6 +429,56 @@ return this.groups; }; + /** + * Get the object that defines groups that match a certain view by their name. + * + * @param {string} [view] Requested view. If not given, uses current view + * @return {Object} Filter groups matching a display group + */ + mw.rcfilters.dm.FiltersViewModel.prototype.getFilterGroupsByView = function ( view ) { + var result = {}; + + view = view || this.getCurrentView(); + + $.each( this.groups, function ( groupName, groupModel ) { + if ( groupModel.getView() === view ) { + result[ groupName ] = groupModel; + } + } ); + + return result; + }; + + /** + * Get an array of filters matching the given display group. + * + * @param {string} [view] Requested view. If not given, uses current view + * @return {mw.rcfilters.dm.FilterItem} Filter items matching the group + */ + mw.rcfilters.dm.FiltersViewModel.prototype.getFiltersByView = function ( view ) { + var groups, + result = []; + + view = view || this.getCurrentView(); + + groups = this.getFilterGroupsByView( view ); + + $.each( groups, function ( groupName, groupModel ) { + result = result.concat( groupModel.getItems() ); + } ); + + return result; + }; + + /** + * Get the trigger for the requested view. + * + * @param {string} view View name + * @return {string} View trigger, if exists + */ + mw.rcfilters.dm.FiltersViewModel.prototype.getViewTrigger = function ( view ) { + return this.views[ view ] && this.views[ view ].trigger; + }; /** * Get the value of a specific parameter * @@ -405,8 +535,9 @@ mw.rcfilters.dm.FiltersViewModel.prototype.getDefaultParams = function () { var result = {}; + // Get default filter state $.each( this.groups, function ( name, model ) { - result = $.extend( true, {}, result, model.getDefaultParams() ); + $.extend( true, result, model.getDefaultParams() ); } ); return result; @@ -501,15 +632,41 @@ /** * Get the highlight parameters based on current filter configuration * - * @return {object} Object where keys are "_color" and values + * @return {Object} Object where keys are "_color" and values * are the selected highlight colors. */ mw.rcfilters.dm.FiltersViewModel.prototype.getHighlightParameters = function () { - var result = { highlight: Number( this.isHighlightEnabled() ) }; + var result = {}; this.getItems().forEach( function ( filterItem ) { - result[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor(); + result[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor() || null; } ); + result.highlight = String( Number( this.isHighlightEnabled() ) ); + + return result; + }; + + /** + * Extract the highlight values from given object. Since highlights are + * the same for filter and parameters, it doesn't matter which one is + * given; values will be returned with a full list of the highlights + * with colors or null values. + * + * @param {Object} representation Object containing representation of + * some or all highlight values + * @return {Object} Object where keys are "_color" and values + * are the selected highlight colors. The returned object + * contains all available filters either with a color value + * or with null. + */ + mw.rcfilters.dm.FiltersViewModel.prototype.extractHighlightValues = function ( representation ) { + var result = {}; + + this.getItems().forEach( function ( filterItem ) { + var highlightName = filterItem.getName() + '_color'; + result[ highlightName ] = representation[ highlightName ] || null; + } ); + return result; }; @@ -643,18 +800,34 @@ * arranged by their group names */ mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( query, returnFlat ) { - var i, + var i, searchIsEmpty, groupTitle, result = {}, flatResult = [], - items = this.getItems(); + view = this.getViewByTrigger( query.substr( 0, 1 ) ), + items = this.getFiltersByView( view ); - // Normalize so we can search strings regardless of case + // Normalize so we can search strings regardless of case and view query = query.toLowerCase(); + if ( view !== 'default' ) { + query = query.substr( 1 ); + } + + // Check if the search if actually empty; this can be a problem when + // we use prefixes to denote different views + searchIsEmpty = query.length === 0; // item label starting with the query string for ( i = 0; i < items.length; i++ ) { - if ( items[ i ].getLabel().toLowerCase().indexOf( query ) === 0 ) { + if ( + searchIsEmpty || + 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 ] ); flatResult.push( items[ i ] ); @@ -666,9 +839,15 @@ for ( i = 0; i < items.length; i++ ) { groupTitle = items[ i ].getGroupModel().getTitle(); if ( + 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 ] ); @@ -703,6 +882,64 @@ } ); }; + /** + * Switch the current view + * + * @param {string} view View name + * @fires update + */ + mw.rcfilters.dm.FiltersViewModel.prototype.switchView = function ( view ) { + if ( this.views[ view ] && this.currentView !== view ) { + this.currentView = view; + this.emit( 'update' ); + } + }; + + /** + * Get the current view + * + * @return {string} Current view + */ + mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentView = function () { + return this.currentView; + }; + + /** + * Get the label for the current view + * + * @return {string} Label for the current view + */ + mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentViewLabel = function () { + 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; + }; + /** * Toggle the highlight feature on and off. * Propagate the change to filter items. @@ -732,6 +969,35 @@ return !!this.highlightEnabled; }; + /** + * Toggle the inverted namespaces property on and off. + * Propagate the change to namespace filter items. + * + * @param {boolean} enable Inverted property is enabled + * @fires invertChange + */ + mw.rcfilters.dm.FiltersViewModel.prototype.toggleInvertedNamespaces = function ( enable ) { + enable = enable === undefined ? !this.invertedNamespaces : enable; + + if ( this.invertedNamespaces !== enable ) { + this.invertedNamespaces = enable; + + this.getFiltersByView( 'namespaces' ).forEach( function ( filterItem ) { + filterItem.toggleInverted( this.invertedNamespaces ); + }.bind( this ) ); + + this.emit( 'invertChange', this.invertedNamespaces ); + } + }; + + /** + * Check if the namespaces selection is set to be inverted + * @return {boolean} + */ + mw.rcfilters.dm.FiltersViewModel.prototype.areNamespacesInverted = function () { + return !!this.invertedNamespaces; + }; + /** * Set highlight color for a specific filter item *