Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/resources/src/mediawiki.rcfilters/dm/FiltersViewModel.js
Ðазад
const FilterGroup = require( './FilterGroup.js' ), FilterItem = require( './FilterItem.js' ), utils = require( '../utils.js' ); /** * View model for the filters selection and display. * * @class mw.rcfilters.dm.FiltersViewModel * @ignore * @mixes OO.EventEmitter * @mixes OO.EmitterList */ const FiltersViewModel = function MwRcfiltersDmFiltersViewModel() { // Mixin constructor OO.EventEmitter.call( this ); OO.EmitterList.call( this ); this.groups = {}; this.defaultParams = {}; this.highlightEnabled = false; this.parameterMap = {}; this.emptyParameterState = null; this.views = {}; this.currentView = 'default'; this.searchQuery = null; // Events this.aggregate( { update: 'filterItemUpdate' } ); this.connect( this, { filterItemUpdate: [ 'emit', 'itemUpdate' ] } ); }; /* Initialization */ OO.initClass( FiltersViewModel ); OO.mixinClass( FiltersViewModel, OO.EventEmitter ); OO.mixinClass( FiltersViewModel, OO.EmitterList ); /* Events */ /** * Filter list is initialized. * * @event initialize * @ignore */ /** * Model has been updated. * * @event update * @ignore */ /** * Filter item has changed. * * @event itemUpdate * @param {mw.rcfilters.dm.FilterItem} item Filter item updated * @ignore */ /** * Highlight feature has been toggled enabled or disabled. * * @event highlightChange * @param {boolean} Highlight feature is enabled * @ignore */ /* Methods */ /** * Re-assess the states of filter items based on the interactions between them * * @param {mw.rcfilters.dm.FilterItem} [item] Changed item. If not given, the * method will go over the state of all items */ FiltersViewModel.prototype.reassessFilterInteractions = function ( item ) { const model = this, iterationItems = item !== undefined ? [ item ] : this.getItems(); iterationItems.forEach( ( checkedItem ) => { const allCheckedItems = checkedItem.getSubset().concat( [ checkedItem.getName() ] ), groupModel = checkedItem.getGroupModel(); // Check for subsets (included filters) plus the item itself: allCheckedItems.forEach( ( filterItemName ) => { const itemInSubset = model.getItemByName( filterItemName ); itemInSubset.toggleIncluded( // If any of itemInSubset's supersets are selected, this item // is included itemInSubset.getSuperset().some( ( supersetName ) => ( model.getItemByName( supersetName ).isSelected() ) ) ); } ); // Update coverage for the changed group if ( groupModel.isFullCoverage() ) { const allSelected = groupModel.areAllSelected(); groupModel.getItems().forEach( ( filterItem ) => { filterItem.toggleFullyCovered( allSelected ); } ); } } ); // Check for conflicts // In this case, we must go over all items, since // conflicts are bidirectional and depend not only on // individual items, but also on the selected states of // the groups they're in. this.getItems().forEach( ( filterItem ) => { let inConflict = false; const filterItemGroup = filterItem.getGroupModel(); // For each item, see if that item is still conflicting // eslint-disable-next-line no-jquery/no-each-util $.each( model.groups, ( groupName, groupModel ) => { if ( filterItem.getGroupName() === groupName ) { // Check inside the group inConflict = groupModel.areAnySelectedInConflictWith( filterItem ); } else { // According to the spec, if two items conflict from two different // groups, the conflict only lasts if the groups **only have selected // items that are conflicting**. If a group has selected items that // are conflicting and non-conflicting, the scope of the result has // expanded enough to completely remove the conflict. // For example, see two groups with conflicts: // userExpLevel: [ // { // name: 'experienced', // conflicts: [ 'unregistered' ] // } // ], // registration: [ // { // name: 'registered', // }, // { // name: 'unregistered', // } // ] // If we select 'experienced', then 'unregistered' is in conflict (and vice versa), // because, inherently, 'experienced' filter only includes registered users, and so // both filters are in conflict with one another. // However, the minute we select 'registered', the scope of our results // has expanded to no longer have a conflict with 'experienced' filter, and // so the conflict is removed. // In our case, we need to check if the entire group conflicts with // the entire item's group, so we follow the above spec inConflict = ( // The foreign group is in conflict with this item groupModel.areAllSelectedInConflictWith( filterItem ) && // Every selected member of the item's own group is also // in conflict with the other group filterItemGroup.findSelectedItems().every( ( otherGroupItem ) => groupModel.areAllSelectedInConflictWith( otherGroupItem ) ) ); } // If we're in conflict, this will return 'false' which // will break the loop. Otherwise, we're not in conflict // and the loop continues return !inConflict; } ); // Toggle the item state filterItem.toggleConflicted( inConflict ); } ); }; /** * Get whether the model has any conflict in its items * * @return {boolean} There is a conflict */ FiltersViewModel.prototype.hasConflict = function () { return this.getItems().some( ( filterItem ) => filterItem.isSelected() && filterItem.isConflicted() ); }; /** * Get the first item with a current conflict * * @ignore * @return {mw.rcfilters.dm.FilterItem|undefined} Conflicted item or undefined when not found */ FiltersViewModel.prototype.getFirstConflictedItem = function () { const items = this.getItems(); for ( let i = 0; i < items.length; i++ ) { const filterItem = items[ i ]; if ( filterItem.isSelected() && filterItem.isConflicted() ) { return filterItem; } } }; /** * Set filters and preserve a group relationship based on * the definition given by an object * * @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: [] * } * ] * } * } */ FiltersViewModel.prototype.initializeFilters = function ( filterGroups, views ) { const model = this, items = [], groupConflictMap = {}, filterConflictMap = {}, /*! * Expand a conflict definition from group name to * the list of all included filters in that group. * We do this so that the direct relationship in the * models are consistently item->items rather than * mixing item->group with item->item. * * @param {Object} obj Conflict definition * @return {Object} Expanded conflict definition */ expandConflictDefinitions = function ( obj ) { const result = {}; // eslint-disable-next-line no-jquery/no-each-util $.each( obj, ( key, conflicts ) => { const adjustedConflicts = {}; conflicts.forEach( ( conflict ) => { let filter; if ( conflict.filter ) { const filterName = model.groups[ conflict.group ].getPrefixedName( conflict.filter ); filter = model.getItemByName( filterName ); // Rename adjustedConflicts[ filterName ] = Object.assign( {}, conflict, { filter: filterName, item: filter } ); } else { // This conflict is for an entire group. Split it up to // represent each filter // Get the relevant group items model.groups[ conflict.group ].getItems().forEach( ( groupItem ) => { // Rebuild the conflict adjustedConflicts[ groupItem.getName() ] = Object.assign( {}, conflict, { filter: groupItem.getName(), item: groupItem } ); } ); } } ); result[ key ] = adjustedConflicts; } ); return result; }; // Reset this.clearItems(); this.groups = {}; this.views = {}; // Clone filterGroups = OO.copy( filterGroups ); // Normalize definition from the server filterGroups.forEach( ( data ) => { let 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 // eslint-disable-next-line mediawiki/msg-doc 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++ ) { // eslint-disable-next-line mediawiki/msg-doc data.filters[ i ].label = data.filters[ i ].label ? mw.msg( data.filters[ i ].label ) : data.filters[ i ].name; // eslint-disable-next-line mediawiki/msg-doc data.filters[ i ].description = data.filters[ i ].description ? mw.msg( data.filters[ i ].description ) : ''; } } ); // Collect views const allViews = $.extend( true, { default: { title: mw.msg( 'rcfilters-filterlist-title' ), groups: filterGroups } }, views ); // Go over all views // eslint-disable-next-line no-jquery/no-each-util $.each( allViews, ( viewName, viewData ) => { // Define the view model.views[ viewName ] = { name: viewData.name, title: viewData.title, trigger: viewData.trigger }; // Go over groups viewData.groups.forEach( ( groupData ) => { const group = groupData.name; if ( !model.groups[ group ] ) { model.groups[ group ] = new FilterGroup( group, $.extend( true, {}, groupData, { view: viewName } ) ); } model.groups[ group ].initializeFilters( groupData.filters, groupData.default ); items.push( ...model.groups[ group ].getItems() ); // Prepare conflicts if ( groupData.conflicts ) { // Group conflicts groupConflictMap[ group ] = groupData.conflicts; } groupData.filters.forEach( ( itemData ) => { const 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 this.addItems( items ); // Expand conflicts const groupConflictResult = expandConflictDefinitions( groupConflictMap ); const filterConflictResult = expandConflictDefinitions( filterConflictMap ); // Set conflicts for groups // eslint-disable-next-line no-jquery/no-each-util $.each( groupConflictResult, ( group, conflicts ) => { model.groups[ group ].setConflicts( conflicts ); } ); // Set conflicts for items // eslint-disable-next-line no-jquery/no-each-util $.each( filterConflictResult, ( filterName, conflicts ) => { const filterItem = model.getItemByName( filterName ); // set conflicts for items in the group filterItem.setConflicts( conflicts ); } ); // Create a map between known parameters and their models // eslint-disable-next-line no-jquery/no-each-util $.each( this.groups, ( group, groupModel ) => { if ( groupModel.getType() === 'send_unselected_if_any' || groupModel.getType() === 'boolean' || groupModel.getType() === 'any_value' ) { // Individual filters groupModel.getItems().forEach( ( filterItem ) => { model.parameterMap[ filterItem.getParamName() ] = filterItem; } ); } else if ( groupModel.getType() === 'string_options' || groupModel.getType() === 'single_option' ) { // Group model.parameterMap[ groupModel.getName() ] = groupModel; } } ); this.setSearch( '' ); this.updateHighlightedState(); // Finish initialization this.emit( 'initialize' ); }; /** * Update filter view model state based on a parameter object * * @param {Object} params Parameters object */ FiltersViewModel.prototype.updateStateFromParams = function ( params ) { // For arbitrary numeric single_option values make sure the values // are normalized to fit within the limits // eslint-disable-next-line no-jquery/no-each-util $.each( this.getFilterGroups(), ( groupName, groupModel ) => { params[ groupName ] = groupModel.normalizeArbitraryValue( params[ groupName ] ); } ); // Update filter values const filtersValue = this.getFiltersFromParameters( params ); Object.keys( filtersValue ).forEach( ( filterName ) => { this.getItemByName( filterName ).setValue( filtersValue[ filterName ] ); } ); // Update highlight state this.getItemsSupportingHighlights().forEach( ( filterItem ) => { const color = params[ filterItem.getName() + '_color' ]; if ( color ) { filterItem.setHighlightColor( color ); } else { filterItem.clearHighlightColor(); } } ); this.updateHighlightedState(); // Check all filter interactions this.reassessFilterInteractions(); }; /** * Get a representation of an empty (falsey) parameter state * * @return {Object} Empty parameter state */ FiltersViewModel.prototype.getEmptyParameterState = function () { if ( !this.emptyParameterState ) { this.emptyParameterState = $.extend( true, {}, this.getParametersFromFilters( {} ), this.getEmptyHighlightParameters() ); } return this.emptyParameterState; }; /** * Get a representation of only the non-falsey parameters * * @param {Object} [parameters] A given parameter state to minimize. If not given the current * state of the system will be used. * @return {Object} Empty parameter state */ FiltersViewModel.prototype.getMinimizedParamRepresentation = function ( parameters ) { const result = {}; parameters = parameters ? $.extend( true, {}, parameters ) : this.getCurrentParameterState(); // Params // eslint-disable-next-line no-jquery/no-each-util $.each( this.getEmptyParameterState(), ( param, value ) => { if ( parameters[ param ] !== undefined && parameters[ param ] !== value ) { result[ param ] = parameters[ param ]; } } ); // Highlights Object.keys( this.getEmptyHighlightParameters() ).forEach( ( param ) => { if ( parameters[ param ] ) { // If a highlight parameter is not undefined and not null // add it to the result result[ param ] = parameters[ param ]; } } ); return result; }; /** * Get a representation of the full parameter list, including all base values * * @return {Object} Full parameter representation */ FiltersViewModel.prototype.getExpandedParamRepresentation = function () { return $.extend( true, {}, this.getEmptyParameterState(), this.getCurrentParameterState() ); }; /** * Get a parameter representation of the current state of the model * * @param {boolean} [removeStickyParams] Remove sticky filters from final result * @return {Object} Parameter representation of the current state of the model */ FiltersViewModel.prototype.getCurrentParameterState = function ( removeStickyParams ) { let state = this.getMinimizedParamRepresentation( $.extend( true, {}, this.getParametersFromFilters( this.getSelectedState() ), this.getHighlightParameters() ) ); if ( removeStickyParams ) { state = this.removeStickyParams( state ); } return state; }; /** * Delete sticky parameters from given object. * * @param {Object} paramState Parameter state * @return {Object} Parameter state without sticky parameters */ FiltersViewModel.prototype.removeStickyParams = function ( paramState ) { this.getStickyParams().forEach( ( paramName ) => { delete paramState[ paramName ]; } ); return paramState; }; /** * Turn the highlight feature on or off */ FiltersViewModel.prototype.updateHighlightedState = function () { this.toggleHighlight( this.getHighlightedItems().length > 0 ); }; /** * Get the object that defines groups by their name. * * @return {Object} Filter groups */ FiltersViewModel.prototype.getFilterGroups = function () { 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 */ FiltersViewModel.prototype.getFilterGroupsByView = function ( view ) { const result = {}; view = view || this.getCurrentView(); // eslint-disable-next-line no-jquery/no-each-util $.each( this.groups, ( groupName, groupModel ) => { if ( groupModel.getView() === view ) { result[ groupName ] = groupModel; } } ); return result; }; /** * Get an array of filters matching the given display group. * * @ignore * @param {string} [view] Requested view. If not given, uses current view * @return {mw.rcfilters.dm.FilterItem} Filter items matching the group */ FiltersViewModel.prototype.getFiltersByView = function ( view ) { const result = []; view = view || this.getCurrentView(); const groups = this.getFilterGroupsByView( view ); // eslint-disable-next-line no-jquery/no-each-util $.each( groups, ( groupName, groupModel ) => { result.push( ...groupModel.getItems() ); } ); return result; }; /** * Get the trigger for the requested view. * * @param {string} view View name * @return {string} View trigger, if exists */ FiltersViewModel.prototype.getViewTrigger = function ( view ) { return ( this.views[ view ] && this.views[ view ].trigger ) || ''; }; /** * Get the value of a specific parameter * * @param {string} name Parameter name * @return {number|string} Parameter value */ FiltersViewModel.prototype.getParamValue = function ( name ) { return this.parameters[ name ]; }; /** * Get the current selected state of the filters * * @param {boolean} [onlySelected] return an object containing only the filters with a value * @return {Object} Filters selected state */ FiltersViewModel.prototype.getSelectedState = function ( onlySelected ) { const items = this.getItems(), result = {}; for ( let i = 0; i < items.length; i++ ) { if ( !onlySelected || items[ i ].getValue() ) { result[ items[ i ].getName() ] = items[ i ].getValue(); } } return result; }; /** * Get the current full state of the filters * * @return {Object} Filters full state */ FiltersViewModel.prototype.getFullState = function () { const items = this.getItems(), result = {}; for ( let i = 0; i < items.length; i++ ) { result[ items[ i ].getName() ] = { selected: items[ i ].isSelected(), conflicted: items[ i ].isConflicted(), included: items[ i ].isIncluded() }; } return result; }; /** * Get an object representing default parameters state * * @return {Object} Default parameter values */ FiltersViewModel.prototype.getDefaultParams = function () { const result = {}; // Get default filter state // eslint-disable-next-line no-jquery/no-each-util $.each( this.groups, ( name, model ) => { if ( !model.isSticky() ) { $.extend( true, result, model.getDefaultParams() ); } } ); return result; }; /** * Get a parameter representation of all sticky parameters * * @return {Object} Sticky parameter values */ FiltersViewModel.prototype.getStickyParams = function () { let result = []; // eslint-disable-next-line no-jquery/no-each-util $.each( this.groups, ( name, model ) => { if ( model.isSticky() ) { if ( model.isPerGroupRequestParameter() ) { result.push( name ); } else { // Each filter is its own param result = result.concat( model.getItems().map( ( filterItem ) => filterItem.getParamName() ) ); } } } ); return result; }; /** * Get a parameter representation of all sticky parameters * * @return {Object} Sticky parameter values */ FiltersViewModel.prototype.getStickyParamsValues = function () { const result = {}; // eslint-disable-next-line no-jquery/no-each-util $.each( this.groups, ( name, model ) => { if ( model.isSticky() ) { $.extend( true, result, model.getParamRepresentation() ); } } ); return result; }; /** * Analyze the groups and their filters and output an object representing * the state of the parameters they represent. * * @param {Object} [filterDefinition] An object defining the filter values, * keyed by filter names. * @return {Object} Parameter state object */ FiltersViewModel.prototype.getParametersFromFilters = function ( filterDefinition ) { let groupItemDefinition; const result = {}, groupItems = this.getFilterGroups(); if ( filterDefinition ) { groupItemDefinition = {}; // Filter definition is "flat", but in effect // each group needs to tell us its result based // on the values in it. We need to split this list // back into groupings so we can "feed" it to the // loop below, and we need to expand it so it includes // all filters (set to false) this.getItems().forEach( ( filterItem ) => { groupItemDefinition[ filterItem.getGroupName() ] = groupItemDefinition[ filterItem.getGroupName() ] || {}; groupItemDefinition[ filterItem.getGroupName() ][ filterItem.getName() ] = filterItem.coerceValue( filterDefinition[ filterItem.getName() ] ); } ); } // eslint-disable-next-line no-jquery/no-each-util $.each( groupItems, ( group, model ) => { Object.assign( result, model.getParamRepresentation( groupItemDefinition ? groupItemDefinition[ group ] : null ) ); } ); return result; }; /** * This is the opposite of the #getParametersFromFilters method; this goes over * the given parameters and translates into a selected/unselected value in the filters. * * @param {Object} params Parameters query object * @return {Object} Filter state object */ FiltersViewModel.prototype.getFiltersFromParameters = function ( params ) { const groupMap = {}, model = this; let result = {}; // Go over the given parameters, break apart to groupings // The resulting object represents the group with its parameter // values. For example: // { // group1: { // param1: "1", // param2: "0", // param3: "1" // }, // group2: "param4|param5" // } // eslint-disable-next-line no-jquery/no-each-util $.each( params, ( paramName, paramValue ) => { const itemOrGroup = model.parameterMap[ paramName ]; if ( itemOrGroup ) { const groupName = itemOrGroup instanceof FilterItem ? itemOrGroup.getGroupName() : itemOrGroup.getName(); groupMap[ groupName ] = groupMap[ groupName ] || {}; groupMap[ groupName ][ paramName ] = paramValue; } } ); // Go over all groups, so we make sure we get the complete output // even if the parameters don't include a certain group // eslint-disable-next-line no-jquery/no-each-util $.each( this.groups, ( groupName, groupModel ) => { result = $.extend( true, {}, result, groupModel.getFilterRepresentation( groupMap[ groupName ] ) ); } ); return result; }; /** * Get the highlight parameters based on current filter configuration * * @return {Object} Object where keys are `<filter name>_color` and values * are the selected highlight colors. */ FiltersViewModel.prototype.getHighlightParameters = function () { const highlightEnabled = this.isHighlightEnabled(), result = {}; this.getItems().forEach( ( filterItem ) => { if ( filterItem.isHighlightSupported() ) { result[ filterItem.getName() + '_color' ] = highlightEnabled && filterItem.isHighlighted() ? filterItem.getHighlightColor() : null; } } ); return result; }; /** * Get an object representing the complete empty state of highlights * * @return {Object} Object containing all the highlight parameters set to their negative value */ FiltersViewModel.prototype.getEmptyHighlightParameters = function () { const result = {}; this.getItems().forEach( ( filterItem ) => { if ( filterItem.isHighlightSupported() ) { result[ filterItem.getName() + '_color' ] = null; } } ); return result; }; /** * Get an array of currently applied highlight colors * * @return {string[]} Currently applied highlight colors */ FiltersViewModel.prototype.getCurrentlyUsedHighlightColors = function () { const result = []; if ( this.isHighlightEnabled() ) { this.getHighlightedItems().forEach( ( filterItem ) => { const color = filterItem.getHighlightColor(); if ( result.indexOf( color ) === -1 ) { result.push( color ); } } ); } return result; }; /** * Sanitize value group of a string_option groups type * Remove duplicates and make sure to only use valid * values. * * @private * @param {string} groupName Group name * @param {string[]} valueArray Array of values * @return {string[]} Array of valid values */ FiltersViewModel.prototype.sanitizeStringOptionGroup = function ( groupName, valueArray ) { const validNames = this.getGroupFilters( groupName ).map( ( filterItem ) => filterItem.getParamName() ); return utils.normalizeParamOptions( valueArray, validNames ); }; /** * Check whether no visible filter is selected. * * Filter groups that are hidden or sticky are not shown in the * active filters area and therefore not included in this check. * * @return {boolean} No visible filter is selected */ FiltersViewModel.prototype.areVisibleFiltersEmpty = function () { // Check if there are either any selected items or any items // that have highlight enabled return !this.getItems().some( ( filterItem ) => { const visible = !filterItem.getGroupModel().isSticky() && !filterItem.getGroupModel().isHidden(), active = ( filterItem.isSelected() || filterItem.isHighlighted() ); return visible && active; } ); }; /** * Check whether the namespace invert state is a valid one. A valid invert state is one * where there are actual namespaces selected. * * This is done to compare states to previous ones that may have had the invert model * selected but effectively had no namespaces, so are not effectively different than * ones where invert is not selected. * * @return {boolean} Invert is effectively selected */ FiltersViewModel.prototype.areNamespacesEffectivelyInverted = function () { return this.getNamespacesInvertModel().isSelected() && this.findSelectedItems().some( ( itemModel ) => itemModel.getGroupModel().getName() === 'namespace' ); }; /** * Check whether the tag invert state is a valid one. A valid invert state is one * where there are actual tags selected. * * This is done to compare states to previous ones that may have had the invert model * selected but effectively had no tags, so are not effectively different than * ones where invert is not selected. * * @return {boolean} Invert is effectively selected */ FiltersViewModel.prototype.areTagsEffectivelyInverted = function () { return this.getTagsInvertModel().isSelected() && this.findSelectedItems().some( ( itemModel ) => itemModel.getGroupModel().getName() === 'tagfilter' ); }; /** * Get the item that matches the given name * * @ignore * @param {string} name Filter name * @return {mw.rcfilters.dm.FilterItem} Filter item */ FiltersViewModel.prototype.getItemByName = function ( name ) { return this.getItems().filter( ( item ) => name === item.getName() )[ 0 ]; }; /** * Set all filters to false or empty/all * This is equivalent to display all. */ FiltersViewModel.prototype.emptyAllFilters = function () { this.getItems().forEach( ( filterItem ) => { if ( !filterItem.getGroupModel().isSticky() ) { this.toggleFilterSelected( filterItem.getName(), false ); } } ); }; /** * Toggle selected state of one item * * @param {string} name Name of the filter item * @param {boolean} [isSelected] Filter selected state */ FiltersViewModel.prototype.toggleFilterSelected = function ( name, isSelected ) { const item = this.getItemByName( name ); if ( item ) { item.toggleSelected( isSelected ); } }; /** * Toggle selected state of items by their names * * @param {Object} filterDef Filter definitions */ FiltersViewModel.prototype.toggleFiltersSelected = function ( filterDef ) { Object.keys( filterDef ).forEach( ( name ) => { this.toggleFilterSelected( name, filterDef[ name ] ); } ); }; /** * Get a group model from its name * * @ignore * @param {string} groupName Group name * @return {mw.rcfilters.dm.FilterGroup} Group model */ FiltersViewModel.prototype.getGroup = function ( groupName ) { return this.groups[ groupName ]; }; /** * Get all filters within a specified group by its name * * @ignore * @param {string} groupName Group name * @return {mw.rcfilters.dm.FilterItem[]} Filters belonging to this group */ FiltersViewModel.prototype.getGroupFilters = function ( groupName ) { return ( this.getGroup( groupName ) && this.getGroup( groupName ).getItems() ) || []; }; /** * Find items whose labels match the given string * * @param {string} query Search string * @param {boolean} [returnFlat] Return a flat array. If false, the result * is an object whose keys are the group names and values are an array of * filters per group. If set to true, returns an array of filters regardless * of their groups. * @return {Object} An object of items to show * arranged by their group names */ FiltersViewModel.prototype.findMatches = function ( query, returnFlat ) { const result = {}, flatResult = [], view = this.getViewByTrigger( query.slice( 0, 1 ) ), items = this.getFiltersByView( view ); // Normalize so we can search strings regardless of case and view query = query.trim().toLowerCase(); if ( view !== 'default' ) { query = query.slice( 1 ); } // Trim again to also intercept cases where the spaces were after the trigger // eg: '# str' query = query.trim(); // Check if the search if actually empty; this can be a problem when // we use prefixes to denote different views const searchIsEmpty = query.length === 0; // item label starting with the query string for ( let i = 0; i < items.length; i++ ) { 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 ] ); } } if ( $.isEmptyObject( result ) ) { // item containing the query string in their label, description, or group title for ( let i = 0; i < items.length; i++ ) { const 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 || ( // 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 ] ); } } } return returnFlat ? flatResult : result; }; /** * Get items that are highlighted * * @ignore * @return {mw.rcfilters.dm.FilterItem[]} Highlighted items */ FiltersViewModel.prototype.getHighlightedItems = function () { return this.getItems().filter( ( filterItem ) => filterItem.isHighlightSupported() && filterItem.getHighlightColor() ); }; /** * Get items that allow highlights even if they're not currently highlighted * * @ignore * @return {mw.rcfilters.dm.FilterItem[]} Items supporting highlights */ FiltersViewModel.prototype.getItemsSupportingHighlights = function () { return this.getItems().filter( ( filterItem ) => filterItem.isHighlightSupported() ); }; /** * Get all selected items * * @ignore * @return {mw.rcfilters.dm.FilterItem[]} Selected items */ FiltersViewModel.prototype.findSelectedItems = function () { let allSelected = []; // eslint-disable-next-line no-jquery/no-each-util $.each( this.getFilterGroups(), ( groupName, groupModel ) => { allSelected = allSelected.concat( groupModel.findSelectedItems() ); } ); return allSelected; }; /** * Get the current view * * @return {string} Current view */ FiltersViewModel.prototype.getCurrentView = function () { return this.currentView; }; /** * Get the label for the current view * * @param {string} viewName View name * @return {string} Label for the current view */ FiltersViewModel.prototype.getViewTitle = function ( viewName ) { viewName = viewName || this.getCurrentView(); return this.views[ viewName ] && this.views[ viewName ].title; }; /** * Get the view that fits the given trigger * * @param {string} trigger Trigger * @return {string} Name of view */ FiltersViewModel.prototype.getViewByTrigger = function ( trigger ) { let result = 'default'; // eslint-disable-next-line no-jquery/no-each-util $.each( this.views, ( name, data ) => { if ( data.trigger === trigger ) { result = name; } } ); return result; }; /** * Return a version of the given string that is without any * view triggers. * * @param {string} str Given string * @return {string} Result */ FiltersViewModel.prototype.removeViewTriggers = function ( str ) { if ( this.getViewFromString( str ) !== 'default' ) { str = str.slice( 1 ); } return str; }; /** * Get the view from the given string by a trigger, if it exists * * @param {string} str Given string * @return {string} View name */ FiltersViewModel.prototype.getViewFromString = function ( str ) { return this.getViewByTrigger( str.slice( 0, 1 ) ); }; /** * Set the current search for the system. * This also dictates what items and groups are visible according * to the search in #findMatches * * @param {string} searchQuery Search query, including triggers * @fires searchChange */ FiltersViewModel.prototype.setSearch = function ( searchQuery ) { let visibleGroups, visibleGroupNames; if ( this.searchQuery !== searchQuery ) { // Check if the view changed this.switchView( this.getViewFromString( searchQuery ) ); visibleGroups = this.findMatches( searchQuery ); visibleGroupNames = Object.keys( visibleGroups ); // Update visibility of items and groups // eslint-disable-next-line no-jquery/no-each-util $.each( this.getFilterGroups(), ( groupName, groupModel ) => { // Check if the group is visible at all groupModel.toggleVisible( visibleGroupNames.indexOf( groupName ) !== -1 ); groupModel.setVisibleItems( visibleGroups[ groupName ] || [] ); } ); this.searchQuery = searchQuery; this.emit( 'searchChange', this.searchQuery ); } }; /** * Get the current search * * @return {string} Current search query */ FiltersViewModel.prototype.getSearch = function () { return this.searchQuery; }; /** * Switch the current view * * @private * @param {string} view View name */ FiltersViewModel.prototype.switchView = function ( view ) { if ( this.views[ view ] && this.currentView !== view ) { this.currentView = view; } }; /** * Toggle the highlight feature on and off. * Propagate the change to filter items. * * @param {boolean} enable Highlight should be enabled * @fires highlightChange */ FiltersViewModel.prototype.toggleHighlight = function ( enable ) { enable = enable === undefined ? !this.highlightEnabled : enable; if ( this.highlightEnabled !== enable ) { this.highlightEnabled = enable; this.emit( 'highlightChange', this.highlightEnabled ); } }; /** * Check if the highlight feature is enabled * * @return {boolean} */ FiltersViewModel.prototype.isHighlightEnabled = function () { return !!this.highlightEnabled; }; /** * Toggle the inverted tags property on and off. * Propagate the change to tag filter items. * * @param {boolean} enable Inverted property is enabled */ FiltersViewModel.prototype.toggleInvertedTags = function ( enable ) { this.toggleFilterSelected( this.getTagsInvertModel().getName(), enable ); }; /** * Toggle the inverted namespaces property on and off. * Propagate the change to namespace filter items. * * @param {boolean} enable Inverted property is enabled */ FiltersViewModel.prototype.toggleInvertedNamespaces = function ( enable ) { this.toggleFilterSelected( this.getNamespacesInvertModel().getName(), enable ); }; /** * Get the model object that represents the 'invert' filter * * @ignore * @param {string} view * @return {mw.rcfilters.dm.FilterItem|null} */ FiltersViewModel.prototype.getInvertModel = function ( view ) { if ( view === 'namespaces' ) { return this.getNamespacesInvertModel(); } if ( view === 'tags' ) { return this.getTagsInvertModel(); } return null; }; /** * Get the model object that represents the 'invert' filter * * @ignore * @return {mw.rcfilters.dm.FilterItem} */ FiltersViewModel.prototype.getNamespacesInvertModel = function () { return this.getGroup( 'invertGroup' ).getItemByParamName( 'invert' ); }; /** * Get the model object that represents the 'invert' filter * * @ignore * @return {mw.rcfilters.dm.FilterItem} */ FiltersViewModel.prototype.getTagsInvertModel = function () { return this.getGroup( 'invertTagsGroup' ).getItemByParamName( 'inverttags' ); }; /** * Set highlight color for a specific filter item * * @param {string} filterName Name of the filter item * @param {string} color Selected color */ FiltersViewModel.prototype.setHighlightColor = function ( filterName, color ) { this.getItemByName( filterName ).setHighlightColor( color ); }; /** * Clear highlight for a specific filter item * * @param {string} filterName Name of the filter item */ FiltersViewModel.prototype.clearHighlightColor = function ( filterName ) { this.getItemByName( filterName ).clearHighlightColor(); }; module.exports = FiltersViewModel;
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка