RCFilters UI: Highlight behavior
Let there be highlight! and there were highlights And RCFilters separated the highlight from the darkness And it defined highlights as five colors The lights are called yellow and green, and the darks red and blue And there were colors and there were circles; one highlight. This is the commit that adds highlight support for filters both in the backend and the UI. The backend tags results based on which filter they fit and the front end paints those results according to the color chosen by the user. Highlights can be toggled off and on. Also added circle indicators to the capsule items and each line of results to indicate whether the line has more than one color affecting it. Bug: T149467 Bug: T156164 Change-Id: I341c3f7c224271a18d455b9e5f5457ec43de802d
This commit is contained in:
parent
6d2198bdcb
commit
09e441003b
31 changed files with 1381 additions and 84 deletions
|
|
@ -158,19 +158,43 @@ class ChangesList extends ContextSource {
|
|||
protected function getHTMLClasses( $rc, $watched ) {
|
||||
$classes = [];
|
||||
$logType = $rc->mAttribs['rc_log_type'];
|
||||
$prefix = 'mw-changeslist-';
|
||||
|
||||
if ( $logType ) {
|
||||
$classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
|
||||
$classes[] = Sanitizer::escapeClass( $prefix . 'log-' . $logType );
|
||||
} else {
|
||||
$classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
|
||||
$classes[] = Sanitizer::escapeClass( $prefix . 'ns' .
|
||||
$rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
|
||||
}
|
||||
|
||||
// Indicate watched status on the line to allow for more
|
||||
// comprehensive styling.
|
||||
$classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched
|
||||
? 'mw-changeslist-line-watched'
|
||||
: 'mw-changeslist-line-not-watched';
|
||||
? $prefix . 'line-watched'
|
||||
: $prefix . 'line-not-watched';
|
||||
|
||||
$classes = array_merge( $classes, $this->getHTMLClassesForFilters( $rc ) );
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
protected function getHTMLClassesForFilters( $rc ) {
|
||||
$classes = [];
|
||||
$prefix = 'mw-changeslist-';
|
||||
|
||||
$classes[] = $prefix . ( $rc->getAttribute( 'rc_bot' ) ? 'bot' : 'human' );
|
||||
$classes[] = $prefix . ( $rc->getAttribute( 'rc_user' ) ? 'liu' : 'anon' );
|
||||
$classes[] = $prefix . ( $rc->getAttribute( 'rc_minor' ) ? 'minor' : 'major' );
|
||||
$classes[] = $prefix .
|
||||
( $rc->getAttribute( 'rc_patrolled' ) ? 'patrolled' : 'unpatrolled' );
|
||||
$classes[] = $prefix .
|
||||
( $this->getUser()->equals( $rc->getPerformer() ) ? 'self' : 'others' );
|
||||
$classes[] = $prefix . 'src-' . str_replace( '.', '-', $rc->getAttribute( 'rc_source' ) );
|
||||
|
||||
$performer = $rc->getPerformer();
|
||||
if ( $performer && $performer->isLoggedIn() ) {
|
||||
$classes[] = $prefix . 'user-' . $performer->getExperienceLevel();
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ class EnhancedChangesList extends ChangesList {
|
|||
&& $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
|
||||
) {
|
||||
$tableClasses[] = 'mw-changeslist-line-watched';
|
||||
$tableClasses = array_merge( $tableClasses, $this->getHTMLClassesForFilters( $block[0] ) );
|
||||
} else {
|
||||
$tableClasses[] = 'mw-changeslist-line-not-watched';
|
||||
}
|
||||
|
|
@ -358,16 +359,17 @@ class EnhancedChangesList extends ChangesList {
|
|||
protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) {
|
||||
$RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
|
||||
|
||||
$classes = [ 'mw-enhanced-rc' ];
|
||||
$type = $rcObj->mAttribs['rc_type'];
|
||||
$data = [];
|
||||
$lineParams = [];
|
||||
|
||||
$classes = [ 'mw-enhanced-rc' ];
|
||||
if ( $rcObj->watched
|
||||
&& $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
|
||||
) {
|
||||
$classes = [ 'mw-enhanced-watched' ];
|
||||
$classes[] = [ 'mw-enhanced-watched' ];
|
||||
}
|
||||
$classes = array_merge( $classes, $this->getHTMLClassesForFilters( $rcObj ) );
|
||||
|
||||
$separator = ' <span class="mw-changeslist-separator">. .</span> ';
|
||||
|
||||
|
|
|
|||
|
|
@ -3770,6 +3770,42 @@ class User implements IDBAccessObject {
|
|||
// user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute experienced level based on edit count and registration date.
|
||||
*
|
||||
* @return string 'newcomer', 'learner', or 'experienced'
|
||||
*/
|
||||
public function getExperienceLevel() {
|
||||
global $wgLearnerEdits,
|
||||
$wgExperiencedUserEdits,
|
||||
$wgLearnerMemberSince,
|
||||
$wgExperiencedUserMemberSince;
|
||||
|
||||
if ( $this->isAnon() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$editCount = $this->getEditCount();
|
||||
$registration = $this->getRegistration();
|
||||
$now = time();
|
||||
$learnerRegistration = wfTimestamp( TS_MW, $now - $wgLearnerMemberSince * 86400 );
|
||||
$experiencedRegistration = wfTimestamp( TS_MW, $now - $wgExperiencedUserMemberSince * 86400 );
|
||||
|
||||
if (
|
||||
$editCount < $wgLearnerEdits ||
|
||||
$registration > $learnerRegistration
|
||||
) {
|
||||
return 'newcomer';
|
||||
} elseif (
|
||||
$editCount > $wgExperiencedUserEdits &&
|
||||
$registration <= $experiencedRegistration
|
||||
) {
|
||||
return 'experienced';
|
||||
} else {
|
||||
return 'learner';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie on the user's client. Wrapper for
|
||||
* WebResponse::setCookie
|
||||
|
|
|
|||
|
|
@ -1378,6 +1378,8 @@
|
|||
"rcfilters-invalid-filter": "Invalid filter",
|
||||
"rcfilters-empty-filter": "No active filters. All contributions are shown.",
|
||||
"rcfilters-filterlist-title": "Filters",
|
||||
"rcfilters-highlightbutton-title": "Highlight results",
|
||||
"rcfilters-highlightmenu-title": "Select a color",
|
||||
"rcfilters-filterlist-noresults": "No filters found",
|
||||
"rcfilters-filtergroup-registration": "User registration",
|
||||
"rcfilters-filter-registered-label": "Registered",
|
||||
|
|
|
|||
|
|
@ -1564,6 +1564,8 @@
|
|||
"rcfilters-invalid-filter": "A label for an invalid filter.",
|
||||
"rcfilters-empty-filter": "Placeholder for the filter list when no filters were chosen.",
|
||||
"rcfilters-filterlist-title": "Title for the filters list.\n{{Identical|Filter}}",
|
||||
"rcfilters-highlightbutton-title": "Title for the highlight button used to toggle the highlight feature on and off.",
|
||||
"rcfilters-highlightmenu-title": "Title for the highlight menu used to select the highlight color for an individual filter.",
|
||||
"rcfilters-filterlist-noresults": "Message showing no results found for searching a filter.",
|
||||
"rcfilters-filtergroup-registration": "Title for the filter group for editor registration type.",
|
||||
"rcfilters-filter-registered-label": "Label for the filter for showing edits made by logged-in users.\n{{Identical|Registered}}",
|
||||
|
|
|
|||
|
|
@ -1776,9 +1776,15 @@ return [
|
|||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemHighlightButton.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.HighlightColorPickerWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/mw.rcfilters.HighlightColors.js',
|
||||
'resources/src/mediawiki.rcfilters/mw.rcfilters.init.js',
|
||||
],
|
||||
'styles' => [
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.mixins.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.variables.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.Overlay.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.CapsuleItemWidget.less',
|
||||
|
|
@ -1786,6 +1792,9 @@ return [
|
|||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FiltersListWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.HighlightColorPickerWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemHighlightButton.less',
|
||||
],
|
||||
'messages' => [
|
||||
'rcfilters-activefilters',
|
||||
|
|
@ -1832,12 +1841,15 @@ return [
|
|||
'rcfilters-filter-categorization-description',
|
||||
'rcfilters-filter-logactions-label',
|
||||
'rcfilters-filter-logactions-description',
|
||||
'rcfilters-highlightbutton-title',
|
||||
'rcfilters-highlightmenu-title',
|
||||
'recentchanges-noresult',
|
||||
],
|
||||
'dependencies' => [
|
||||
'oojs-ui',
|
||||
'mediawiki.rcfilters.filters.dm',
|
||||
'oojs-ui.styles.icons-moderation'
|
||||
'oojs-ui.styles.icons-moderation',
|
||||
'oojs-ui.styles.icons-editing-core',
|
||||
],
|
||||
],
|
||||
'mediawiki.special' => [
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* @cfg {boolean} [selected] The item is selected
|
||||
* @cfg {string[]} [subset] Defining the names of filters that are a subset of this filter
|
||||
* @cfg {string[]} [conflictsWith] Defining the names of filters that conflict with this item
|
||||
* @cfg {string} [cssClass] The class identifying the results that match this filter
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( name, groupModel, config ) {
|
||||
config = config || {};
|
||||
|
|
@ -40,6 +41,11 @@
|
|||
this.included = false;
|
||||
this.conflicted = false;
|
||||
this.fullyCovered = false;
|
||||
|
||||
// Highlight
|
||||
this.cssClass = config.cssClass;
|
||||
this.highlightColor = null;
|
||||
this.highlightEnabled = false;
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
|
@ -281,4 +287,81 @@
|
|||
this.emit( 'update' );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the highlight color
|
||||
*
|
||||
* @param {string|null} highlightColor
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.setHighlightColor = function ( highlightColor ) {
|
||||
if ( this.highlightColor !== highlightColor ) {
|
||||
this.highlightColor = highlightColor;
|
||||
this.emit( 'update' );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the highlight color
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.clearHighlightColor = function () {
|
||||
this.setHighlightColor( null );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the highlight color, or null if none is configured
|
||||
*
|
||||
* @return {string|null}
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.getHighlightColor = function () {
|
||||
return this.highlightColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the CSS class that matches changes that fit this filter
|
||||
* or null if none is configured
|
||||
*
|
||||
* @return {string|null}
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.getCssClass = function () {
|
||||
return this.cssClass;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the highlight feature on and off for this filter.
|
||||
* It only works if highlight is supported for this filter.
|
||||
*
|
||||
* @param {boolean} enable Highlight should be enabled
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.toggleHighlight = function ( enable ) {
|
||||
enable = enable === undefined ? !this.highlightEnabled : enable;
|
||||
|
||||
if ( !this.isHighlightSupported() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( enable === this.highlightEnabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlightEnabled = enable;
|
||||
this.emit( 'update' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the highlight feature is currently enabled for this filter
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.isHighlightEnabled = function () {
|
||||
return !!this.highlightEnabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the highlight feature is supported for this filter
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
mw.rcfilters.dm.FilterItem.prototype.isHighlightSupported = function () {
|
||||
return !!this.getCssClass();
|
||||
};
|
||||
}( mediaWiki ) );
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
this.groups = {};
|
||||
this.defaultParams = {};
|
||||
this.defaultFiltersEmpty = null;
|
||||
this.highlightEnabled = false;
|
||||
|
||||
// Events
|
||||
this.aggregate( { update: 'filterItemUpdate' } );
|
||||
|
|
@ -41,6 +42,13 @@
|
|||
* Filter item has changed
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event highlightChange
|
||||
* @param {boolean} Highlight feature is enabled
|
||||
*
|
||||
* Highlight feature has been toggled enabled or disabled
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
|
|
@ -191,7 +199,8 @@
|
|||
group: group,
|
||||
label: data.filters[ i ].label,
|
||||
description: data.filters[ i ].description,
|
||||
subset: data.filters[ i ].subset
|
||||
subset: data.filters[ i ].subset,
|
||||
cssClass: data.filters[ i ].class
|
||||
} );
|
||||
|
||||
// For convenience, we should store each filter's "supersets" -- these are
|
||||
|
|
@ -402,6 +411,21 @@
|
|||
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.
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.getHighlightParameters = function () {
|
||||
var result = { highlight: this.isHighlightEnabled() };
|
||||
|
||||
this.getItems().forEach( function ( filterItem ) {
|
||||
result[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor();
|
||||
} );
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize value group of a string_option groups type
|
||||
* Remove duplicates and make sure to only use valid
|
||||
|
|
@ -448,10 +472,15 @@
|
|||
* @return {boolean} Current filters are all empty
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.areCurrentFiltersEmpty = function () {
|
||||
var currFilters = this.getSelectedState();
|
||||
var model = this;
|
||||
|
||||
return Object.keys( currFilters ).every( function ( filterName ) {
|
||||
return !currFilters[ filterName ];
|
||||
// Check if there are either any selected items or any items
|
||||
// that have highlight enabled
|
||||
return !this.getItems().some( function ( filterItem ) {
|
||||
return (
|
||||
filterItem.isSelected() ||
|
||||
( model.isHighlightEnabled() && filterItem.getHighlightColor() )
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
|
|
@ -659,4 +688,72 @@
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get items that are highlighted
|
||||
*
|
||||
* @return {mw.rcfilters.dm.FilterItem[]} Highlighted items
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.getHighlightedItems = function () {
|
||||
return this.getItems().filter( function ( filterItem ) {
|
||||
return filterItem.isHighlightSupported() &&
|
||||
filterItem.getHighlightColor();
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the highlight feature on and off.
|
||||
* Propagate the change to filter items.
|
||||
*
|
||||
* @param {boolean} enable Highlight should be enabled
|
||||
* @fires highlightChange
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.toggleHighlight = function ( enable ) {
|
||||
enable = enable === undefined ? !this.highlightEnabled : enable;
|
||||
|
||||
if ( this.highlightEnabled !== enable ) {
|
||||
this.highlightEnabled = enable;
|
||||
|
||||
this.getItems().forEach( function ( filterItem ) {
|
||||
filterItem.toggleHighlight( this.highlightEnabled );
|
||||
}.bind( this ) );
|
||||
|
||||
this.emit( 'highlightChange', this.highlightEnabled );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the highlight feature is enabled
|
||||
* @return {boolean}
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.isHighlightEnabled = function () {
|
||||
return this.highlightEnabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set highlight color for a specific filter item
|
||||
*
|
||||
* @param {string} filterName Name of the filter item
|
||||
* @param {string} color Selected color
|
||||
*/
|
||||
mw.rcfilters.dm.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
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.clearHighlightColor = function ( filterName ) {
|
||||
this.getItemByName( filterName ).clearHighlightColor();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear highlight for all filter items
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.clearAllHighlightColors = function () {
|
||||
this.getItems().forEach( function ( filterItem ) {
|
||||
filterItem.clearHighlightColor();
|
||||
} );
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@
|
|||
)
|
||||
);
|
||||
|
||||
// Initialize highlights
|
||||
this.filtersModel.toggleHighlight( !!uri.query.highlight );
|
||||
this.filtersModel.getItems().forEach( function ( filterItem ) {
|
||||
var color = uri.query[ filterItem.getName() + '_color' ];
|
||||
if ( !color ) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterItem.setHighlightColor( color );
|
||||
} );
|
||||
|
||||
// Check all filter interactions
|
||||
this.filtersModel.reassessFilterInteractions();
|
||||
};
|
||||
|
|
@ -57,6 +68,7 @@
|
|||
*/
|
||||
mw.rcfilters.Controller.prototype.emptyFilters = function () {
|
||||
this.filtersModel.emptyAllFilters();
|
||||
this.filtersModel.clearAllHighlightColors();
|
||||
this.updateURL();
|
||||
this.updateChangesList();
|
||||
};
|
||||
|
|
@ -68,23 +80,37 @@
|
|||
* @param {boolean} isSelected Filter selected state
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.updateFilter = function ( filterName, isSelected ) {
|
||||
var obj = {};
|
||||
var obj = {},
|
||||
filterItem = this.filtersModel.getItemByName( filterName );
|
||||
|
||||
obj[ filterName ] = isSelected;
|
||||
if ( filterItem.isSelected() !== isSelected ) {
|
||||
obj[ filterName ] = isSelected;
|
||||
this.filtersModel.updateFilters( obj );
|
||||
|
||||
this.filtersModel.updateFilters( obj );
|
||||
this.updateURL();
|
||||
this.updateChangesList();
|
||||
this.updateURL();
|
||||
this.updateChangesList();
|
||||
|
||||
// Check filter interactions
|
||||
this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
|
||||
// Check filter interactions
|
||||
this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the URL of the page to reflect current filters
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.updateURL = function () {
|
||||
var uri = new mw.Uri();
|
||||
var uri = this.getUpdatedUri();
|
||||
window.history.pushState( { tag: 'rcfilters' }, document.title, uri.toString() );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an updated mw.Uri object based on the model state
|
||||
*
|
||||
* @return {mw.Uri} Updated Uri
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.getUpdatedUri = function () {
|
||||
var uri = new mw.Uri(),
|
||||
highlightParams = this.filtersModel.getHighlightParameters();
|
||||
|
||||
// Add to existing queries in URL
|
||||
// TODO: Clean up the list of filters; perhaps 'falsy' filters
|
||||
|
|
@ -92,17 +118,25 @@
|
|||
// and see if current state of a specific filter is needed?
|
||||
uri.extend( this.filtersModel.getParametersFromFilters() );
|
||||
|
||||
// Update the URL itself
|
||||
window.history.pushState( { tag: 'rcfilters' }, document.title, uri.toString() );
|
||||
// highlight params
|
||||
Object.keys( highlightParams ).forEach( function ( paramName ) {
|
||||
if ( highlightParams[ paramName ] ) {
|
||||
uri.query[ paramName ] = highlightParams[ paramName ];
|
||||
} else {
|
||||
delete uri.query[ paramName ];
|
||||
}
|
||||
} );
|
||||
|
||||
return uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch the list of changes from the server for the current filters
|
||||
*
|
||||
* @returns {jQuery.Promise} Promise object that will resolve with the changes list
|
||||
* @return {jQuery.Promise} Promise object that will resolve with the changes list
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.fetchChangesList = function () {
|
||||
var uri = new mw.Uri(),
|
||||
var uri = this.getUpdatedUri(),
|
||||
requestId = ++this.requestCounter,
|
||||
latestRequest = function () {
|
||||
return requestId === this.requestCounter;
|
||||
|
|
@ -130,4 +164,33 @@
|
|||
}
|
||||
}.bind( this ) );
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the highlight feature on and off
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.toggleHighlight = function () {
|
||||
this.filtersModel.toggleHighlight();
|
||||
this.updateURL();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the highlight color for a filter item
|
||||
*
|
||||
* @param {string} filterName Name of the filter item
|
||||
* @param {string} color Selected color
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
|
||||
this.filtersModel.setHighlightColor( filterName, color );
|
||||
this.updateURL();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear highlight for a filter item
|
||||
*
|
||||
* @param {string} filterName Name of the filter item
|
||||
*/
|
||||
mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
|
||||
this.filtersModel.clearHighlightColor( filterName );
|
||||
this.updateURL();
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
( function ( mw ) {
|
||||
/**
|
||||
* Supported highlight colors.
|
||||
* Warning: These are also hardcoded in "styles/mw.rcfilters.variables.less"
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
mw.rcfilters.HighlightColors = [ 'c1', 'c2', 'c3', 'c4', 'c5' ];
|
||||
}( mediaWiki ) );
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
// eslint-disable-next-line no-new
|
||||
new mw.rcfilters.ui.ChangesListWrapperWidget(
|
||||
changesListModel, $( '.mw-changeslist, .mw-changeslist-empty' ) );
|
||||
filtersModel, changesListModel, $( '.mw-changeslist, .mw-changeslist-empty' ) );
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new mw.rcfilters.ui.FormWrapperWidget(
|
||||
|
|
@ -34,12 +34,14 @@
|
|||
{
|
||||
name: 'hideliu',
|
||||
label: mw.msg( 'rcfilters-filter-registered-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-registered-description' )
|
||||
description: mw.msg( 'rcfilters-filter-registered-description' ),
|
||||
'class': 'mw-changeslist-liu'
|
||||
},
|
||||
{
|
||||
name: 'hideanons',
|
||||
label: mw.msg( 'rcfilters-filter-unregistered-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-unregistered-description' )
|
||||
description: mw.msg( 'rcfilters-filter-unregistered-description' ),
|
||||
'class': 'mw-changeslist-anon'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -58,19 +60,22 @@
|
|||
name: 'newcomer',
|
||||
label: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-description' ),
|
||||
conflicts: [ 'hideanons' ]
|
||||
conflicts: [ 'hideanons' ],
|
||||
'class': 'mw-changeslist-user-newcomer'
|
||||
},
|
||||
{
|
||||
name: 'learner',
|
||||
label: mw.msg( 'rcfilters-filter-userExpLevel-learner-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-userExpLevel-learner-description' ),
|
||||
conflicts: [ 'hideanons' ]
|
||||
conflicts: [ 'hideanons' ],
|
||||
'class': 'mw-changeslist-user-learner'
|
||||
},
|
||||
{
|
||||
name: 'experienced',
|
||||
label: mw.msg( 'rcfilters-filter-userExpLevel-experienced-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-userExpLevel-experienced-description' ),
|
||||
conflicts: [ 'hideanons' ]
|
||||
conflicts: [ 'hideanons' ],
|
||||
'class': 'mw-changeslist-user-experienced'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -90,12 +95,14 @@
|
|||
{
|
||||
name: 'hidemyself',
|
||||
label: mw.msg( 'rcfilters-filter-editsbyself-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-editsbyself-description' )
|
||||
description: mw.msg( 'rcfilters-filter-editsbyself-description' ),
|
||||
'class': 'mw-changeslist-self'
|
||||
},
|
||||
{
|
||||
name: 'hidebyothers',
|
||||
label: mw.msg( 'rcfilters-filter-editsbyother-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-editsbyother-description' )
|
||||
description: mw.msg( 'rcfilters-filter-editsbyother-description' ),
|
||||
'class': 'mw-changeslist-others'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -108,13 +115,15 @@
|
|||
name: 'hidebots',
|
||||
label: mw.msg( 'rcfilters-filter-bots-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-bots-description' ),
|
||||
'default': true
|
||||
'default': true,
|
||||
'class': 'mw-changeslist-bot'
|
||||
},
|
||||
{
|
||||
name: 'hidehumans',
|
||||
label: mw.msg( 'rcfilters-filter-humans-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-humans-description' ),
|
||||
'default': false
|
||||
'default': false,
|
||||
'class': 'mw-changeslist-human'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -126,12 +135,14 @@
|
|||
{
|
||||
name: 'hideminor',
|
||||
label: mw.msg( 'rcfilters-filter-minor-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-minor-description' )
|
||||
description: mw.msg( 'rcfilters-filter-minor-description' ),
|
||||
'class': 'mw-changeslist-minor'
|
||||
},
|
||||
{
|
||||
name: 'hidemajor',
|
||||
label: mw.msg( 'rcfilters-filter-major-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-major-description' )
|
||||
description: mw.msg( 'rcfilters-filter-major-description' ),
|
||||
'class': 'mw-changeslist-major'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -144,25 +155,30 @@
|
|||
name: 'hidepageedits',
|
||||
label: mw.msg( 'rcfilters-filter-pageedits-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-pageedits-description' ),
|
||||
'default': false
|
||||
'default': false,
|
||||
'class': 'mw-changeslist-src-mw-edit'
|
||||
|
||||
},
|
||||
{
|
||||
name: 'hidenewpages',
|
||||
label: mw.msg( 'rcfilters-filter-newpages-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-newpages-description' ),
|
||||
'default': false
|
||||
'default': false,
|
||||
'class': 'mw-changeslist-src-mw-new'
|
||||
},
|
||||
{
|
||||
name: 'hidecategorization',
|
||||
label: mw.msg( 'rcfilters-filter-categorization-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-categorization-description' ),
|
||||
'default': true
|
||||
'default': true,
|
||||
'class': 'mw-changeslist-src-mw-categorize'
|
||||
},
|
||||
{
|
||||
name: 'hidelog',
|
||||
label: mw.msg( 'rcfilters-filter-logactions-label' ),
|
||||
description: mw.msg( 'rcfilters-filter-logactions-description' ),
|
||||
'default': false
|
||||
'default': false,
|
||||
'class': 'mw-changeslist-src-mw-log'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
@import "mediawiki.mixins";
|
||||
@import "mw.rcfilters.variables";
|
||||
|
||||
// This is a general mixin for a color circle
|
||||
.mw-rcfilters-mixin-circle( @color: white, @diameter: 2em, @padding: 0.5em, @border: false ) {
|
||||
border-radius: 50%;
|
||||
min-width: @diameter;
|
||||
width: @diameter;
|
||||
min-height: @diameter;
|
||||
height: @diameter;
|
||||
margin: @padding;
|
||||
.box-sizing( border-box );
|
||||
|
||||
background-color: @color;
|
||||
|
||||
& when (@border = true) {
|
||||
border: 1px solid #565656;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the circle that appears next to the results
|
||||
// Its visibility is directly dependent on whether there is
|
||||
// a color class on its parent element
|
||||
.result-circle( @colorName: 'none' ) {
|
||||
&-@{colorName} {
|
||||
.mw-rcfilters-mixin-circle( ~"@{highlight-@{colorName}}", @result-circle-diameter, 0 );
|
||||
display: none;
|
||||
|
||||
.mw-rcfilters-highlight-color-@{colorName} & {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This mixin produces color mixes for two, three and four colors
|
||||
.highlight-color-mix( @color1, @color2, @color3: false, @color4: false ) {
|
||||
@highlight-color-class-var: ~".mw-rcfilters-highlight-color-@{color1}.mw-rcfilters-highlight-color-@{color2}";
|
||||
|
||||
// The nature of these variables and them being inside
|
||||
// a 'tint' and 'average' LESS functions is such where
|
||||
// the parsing is failing if it is done inside those functions.
|
||||
// Instead, we first construct their LESS variable names,
|
||||
// and then we call them inside those functions by calling @@var
|
||||
@c1var: ~"highlight-@{color1}";
|
||||
@c2var: ~"highlight-@{color2}";
|
||||
|
||||
// Two colors
|
||||
@{highlight-color-class-var} when ( @color3 = false ) and ( @color4 = false ) and not ( @color1 = false ), ( @color2 = false ) {
|
||||
background-color: tint( average( @@c1var, @@c2var ), 50% );
|
||||
}
|
||||
// Three colors
|
||||
@{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3} when ( @color4 = false ) and not ( @color3 = false ) {
|
||||
@c3var: ~"highlight-@{color3}";
|
||||
background-color: tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% );
|
||||
}
|
||||
|
||||
// Four colors
|
||||
@{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3}.mw-rcfilters-highlight-color-@{color4} when not ( @color4 = false ) {
|
||||
@c3var: ~"highlight-@{color3}";
|
||||
@c4var: ~"highlight-@{color4}";
|
||||
background-color: tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
@import "mw.rcfilters.mixins";
|
||||
|
||||
.mw-rcfilters-ui-capsuleItemWidget {
|
||||
&-popup {
|
||||
padding: 1em;
|
||||
|
|
@ -8,7 +10,44 @@
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.oo-ui-labelElement-label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&-muted {
|
||||
opacity: 0.5;
|
||||
// Muted state
|
||||
// We want everything muted except the circle
|
||||
background-color: rgba( 255, 255, 255, @muted-opacity );
|
||||
|
||||
.oo-ui-labelElement-label,
|
||||
.oo-ui-buttonWidget {
|
||||
opacity: @muted-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
&-highlight {
|
||||
display: none;
|
||||
padding-right: 0.5em;
|
||||
|
||||
&-highlighted {
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
&[data-color="c1"] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c1, 0.7em, ~"0 0.5em 0 0" );
|
||||
}
|
||||
&[data-color="c2"] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c2, 0.7em, ~"0 0.5em 0 0" );
|
||||
}
|
||||
&[data-color="c3"] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c3, 0.7em, ~"0 0.5em 0 0" );
|
||||
}
|
||||
&[data-color="c4"] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c4, 0.7em, ~"0 0.5em 0 0" );
|
||||
}
|
||||
&[data-color="c5"] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c5, 0.7em, ~"0 0.5em 0 0" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
@import 'mw.rcfilters.mixins';
|
||||
|
||||
.mw-rcfilters-ui-changesListWrapperWidget {
|
||||
&-highlighted {
|
||||
ul {
|
||||
list-style: none;
|
||||
// Each li's margin-left should be the width of the highlights
|
||||
// element + the margin
|
||||
margin-left: ~"calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * 5 + @{result-circle-general-margin} )";
|
||||
}
|
||||
}
|
||||
|
||||
// Correction for Enhanced RC
|
||||
// This is outside the scope of the 'highlights' wrapper
|
||||
table.mw-enhanced-rc {
|
||||
margin-left: ~"calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * 5 + @{result-circle-general-margin} )";
|
||||
|
||||
td:last-child {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-highlights {
|
||||
display: none;
|
||||
padding: 0 @result-circle-general-margin 0 0;
|
||||
text-align: right;
|
||||
// The width is 5 circles times their diameter + individual margin
|
||||
// and then plus the general margin
|
||||
width: ~"calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * 5 )";
|
||||
// And we want to shift the entire block to the left of the li
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
.mw-rcfilters-ui-changesListWrapperWidget-highlighted & {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div {
|
||||
.box-sizing( border-box );
|
||||
margin-right: @result-circle-margin;
|
||||
vertical-align: middle;
|
||||
// This is to make the dots appear at the center of the
|
||||
// text itself; it's a horrendous hack and blame JamesF for it.
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
&-color {
|
||||
|
||||
&-none {
|
||||
.mw-rcfilters-mixin-circle( @highlight-none, @result-circle-diameter, 0, true );
|
||||
display: inline-block;
|
||||
|
||||
.mw-rcfilters-highlight-color-c1 &,
|
||||
.mw-rcfilters-highlight-color-c2 &,
|
||||
.mw-rcfilters-highlight-color-c3 &,
|
||||
.mw-rcfilters-highlight-color-c4 &,
|
||||
.mw-rcfilters-highlight-color-c5 & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.result-circle( c1 );
|
||||
.result-circle( c2 );
|
||||
.result-circle( c3 );
|
||||
.result-circle( c4 );
|
||||
.result-circle( c5 );
|
||||
}
|
||||
}
|
||||
|
||||
// One color
|
||||
.mw-rcfilters-highlight-color-c1 {
|
||||
background-color: tint( @highlight-c1, 70% );
|
||||
}
|
||||
|
||||
.mw-rcfilters-highlight-color-c2 {
|
||||
background-color: tint( @highlight-c2, 70% );
|
||||
}
|
||||
|
||||
.mw-rcfilters-highlight-color-c3 {
|
||||
background-color: tint( @highlight-c3, 70% );
|
||||
}
|
||||
|
||||
.mw-rcfilters-highlight-color-c4 {
|
||||
background-color: tint( @highlight-c4, 70% );
|
||||
}
|
||||
|
||||
.mw-rcfilters-highlight-color-c5 {
|
||||
background-color: tint( @highlight-c5, 70% );
|
||||
}
|
||||
|
||||
// Two colors
|
||||
.highlight-color-mix( c1, c2 );
|
||||
.highlight-color-mix( c1, c3 );
|
||||
.highlight-color-mix( c1, c4 );
|
||||
.highlight-color-mix( c1, c5 );
|
||||
.highlight-color-mix( c2, c3 );
|
||||
.highlight-color-mix( c2, c4 );
|
||||
.highlight-color-mix( c2, c5 );
|
||||
.highlight-color-mix( c3, c4 );
|
||||
.highlight-color-mix( c3, c5 );
|
||||
.highlight-color-mix( c4, c5 );
|
||||
|
||||
// Three colors
|
||||
.highlight-color-mix( c1, c2, c3 );
|
||||
.highlight-color-mix( c1, c2, c5 );
|
||||
.highlight-color-mix( c1, c2, c4 );
|
||||
.highlight-color-mix( c1, c3, c4 );
|
||||
.highlight-color-mix( c1, c3, c5 );
|
||||
.highlight-color-mix( c1, c4, c5 );
|
||||
.highlight-color-mix( c2, c3, c4 );
|
||||
.highlight-color-mix( c2, c3, c5 );
|
||||
.highlight-color-mix( c2, c4, c5 );
|
||||
.highlight-color-mix( c3, c4, c5 );
|
||||
|
||||
// Four colors
|
||||
.highlight-color-mix( c1, c2, c3, c4 );
|
||||
.highlight-color-mix( c1, c2, c3, c5 );
|
||||
.highlight-color-mix( c1, c2, c4, c5 );
|
||||
.highlight-color-mix( c1, c3, c4, c5 );
|
||||
.highlight-color-mix( c2, c3, c4, c5 );
|
||||
|
||||
// Five colors:
|
||||
.mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
|
||||
background-color: tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
@import "mw.rcfilters.mixins";
|
||||
|
||||
.mw-rcfilters-ui-filterItemHighlightButton {
|
||||
|
||||
&-circle {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
background-image: none;
|
||||
margin-right: 0.2em;
|
||||
|
||||
&-color {
|
||||
&-c1 {
|
||||
// These values duplicate the sizing of the icon
|
||||
// width/height 1.875em
|
||||
.mw-rcfilters-mixin-circle( @highlight-c1, 1.875em, 0 );
|
||||
}
|
||||
&-c2 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c2, 1.875em, 0 );
|
||||
}
|
||||
&-c3 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c3, 1.875em, 0 );
|
||||
}
|
||||
&-c4 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c4, 1.875em, 0 );
|
||||
}
|
||||
&-c5 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c5, 1.875em, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,37 @@
|
|||
@import "mediawiki.mixins";
|
||||
|
||||
.mw-rcfilters-ui-filterItemWidget {
|
||||
padding-left: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
.box-sizing( border-box );
|
||||
|
||||
&-label {
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
color: #222;
|
||||
.mw-rcfilters-ui-table {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
&-filterCheckbox {
|
||||
&-label {
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
color: #222;
|
||||
}
|
||||
&-desc {
|
||||
color: #464a4f;
|
||||
}
|
||||
}
|
||||
&-desc {
|
||||
color: #464a4f;
|
||||
|
||||
.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
|
||||
// Override margin-top and -bottom rules from FieldLayout
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
&-muted {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
|
||||
// Override margin-top and -bottom rules from FieldLayout
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
&-muted {
|
||||
opacity: 0.5;
|
||||
&-highlightButton {
|
||||
width: 4em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
color: #54595d;
|
||||
border-bottom: 1px solid #c8ccd1;
|
||||
background: #f8f9fa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-noresults {
|
||||
|
|
@ -13,4 +14,8 @@
|
|||
// TODO: Unify colors with official design palette
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&-hightlightButton {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
@import "mw.rcfilters.mixins";
|
||||
|
||||
.mw-rcfilters-ui-highlightColorPickerWidget {
|
||||
&-label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
&-buttonSelect {
|
||||
&-color {
|
||||
.oo-ui-iconElement-icon {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
&-none {
|
||||
.mw-rcfilters-mixin-circle( @highlight-none, 2em, 0.5em, true );
|
||||
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected {
|
||||
background-color: @highlight-none;
|
||||
}
|
||||
}
|
||||
&-c1 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c1 );
|
||||
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected {
|
||||
background-color: @highlight-c1;
|
||||
}
|
||||
}
|
||||
&-c2 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c2 );
|
||||
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected {
|
||||
background-color: @highlight-c2;
|
||||
}
|
||||
}
|
||||
&-c3 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c3 );
|
||||
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected {
|
||||
background-color: @highlight-c3;
|
||||
}
|
||||
}
|
||||
&-c4 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c4 );
|
||||
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected {
|
||||
background-color: @highlight-c4;
|
||||
}
|
||||
}
|
||||
&-c5 {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c5 );
|
||||
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-pressed,
|
||||
&.oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected {
|
||||
background-color: @highlight-c5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
.mw-rcfilters-ui {
|
||||
&-table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-row {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
&-cell {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Highlight color definitions
|
||||
@highlight-none: #fff;
|
||||
@highlight-c1: #36c;
|
||||
@highlight-c2: #00af89;
|
||||
@highlight-c3: #fc3;
|
||||
@highlight-c4: #ff6d22;
|
||||
@highlight-c5: #d33;
|
||||
|
||||
// Muted state
|
||||
@muted-opacity: 0.5;
|
||||
|
||||
// Result list circle indicators
|
||||
// Defined and used in mw.rcfilters.ui.ChangesListWrapperWidget.less
|
||||
@result-circle-margin: 0.1em;
|
||||
@result-circle-general-margin: 0.5em;
|
||||
// In these small sizes, 'em' appears
|
||||
// squished and inconsistent.
|
||||
// Pixels are better for this use case:
|
||||
@result-circle-diameter: 5px;
|
||||
|
|
@ -45,6 +45,9 @@
|
|||
// Set initial text for the popup - the description
|
||||
descLabelWidget.setLabel( this.model.getDescription() );
|
||||
|
||||
this.$highlight = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-capsuleItemWidget-highlight' );
|
||||
|
||||
// Events
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
|
||||
|
|
@ -53,12 +56,14 @@
|
|||
// Initialization
|
||||
this.$overlay.append( this.popup.$element );
|
||||
this.$element
|
||||
.prepend( this.$highlight )
|
||||
.attr( 'aria-haspopup', 'true' )
|
||||
.addClass( 'mw-rcfilters-ui-capsuleItemWidget' )
|
||||
.on( 'mouseover', this.onHover.bind( this, true ) )
|
||||
.on( 'mouseout', this.onHover.bind( this, false ) );
|
||||
|
||||
this.setCurrentMuteState();
|
||||
this.setHighlightColor();
|
||||
};
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.CapsuleItemWidget, OO.ui.CapsuleItemWidget );
|
||||
|
|
@ -69,6 +74,19 @@
|
|||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onModelUpdate = function () {
|
||||
this.setCurrentMuteState();
|
||||
|
||||
this.setHighlightColor();
|
||||
};
|
||||
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.setHighlightColor = function () {
|
||||
var selectedColor = this.model.isHighlightEnabled() ? this.model.getHighlightColor() : null;
|
||||
|
||||
this.$highlight
|
||||
.attr( 'data-color', selectedColor )
|
||||
.toggleClass(
|
||||
'mw-rcfilters-ui-capsuleItemWidget-highlight-highlighted',
|
||||
!!selectedColor
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -78,6 +96,7 @@
|
|||
this.$element
|
||||
.toggleClass(
|
||||
'mw-rcfilters-ui-capsuleItemWidget-muted',
|
||||
!this.model.isSelected() ||
|
||||
this.model.isIncluded() ||
|
||||
this.model.isConflicted() ||
|
||||
this.model.isFullyCovered()
|
||||
|
|
@ -106,6 +125,7 @@
|
|||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onCapsuleRemovedByUser = function () {
|
||||
this.controller.updateFilter( this.model.getName(), false );
|
||||
this.controller.clearHighlightColor( this.model.getName() );
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,27 +6,43 @@
|
|||
* @mixins OO.ui.mixin.PendingElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.dm.ChangesListViewModel} model View model
|
||||
* @param {mw.rcfilters.dm.FiltersViewModel} filtersViewModel View model
|
||||
* @param {mw.rcfilters.dm.ChangesListViewModel} changesListViewModel View model
|
||||
* @param {jQuery} $changesListRoot Root element of the changes list to attach to
|
||||
* @param {Object} config Configuration object
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget = function MwRcfiltersUiChangesListWrapperWidget( model, $changesListRoot, config ) {
|
||||
config = config || {};
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget = function MwRcfiltersUiChangesListWrapperWidget(
|
||||
filtersViewModel,
|
||||
changesListViewModel,
|
||||
$changesListRoot,
|
||||
config
|
||||
) {
|
||||
config = $.extend( {}, config, {
|
||||
$element: $changesListRoot
|
||||
} );
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.parent.call( this, $.extend( {}, config, {
|
||||
$element: $changesListRoot
|
||||
} ) );
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.parent.call( this, config );
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.PendingElement.call( this, config );
|
||||
|
||||
this.model = model;
|
||||
this.filtersViewModel = filtersViewModel;
|
||||
this.changesListViewModel = changesListViewModel;
|
||||
|
||||
// Events
|
||||
this.model.connect( this, {
|
||||
this.filtersViewModel.connect( this, {
|
||||
itemUpdate: 'onItemUpdate',
|
||||
highlightChange: 'onHighlightChange'
|
||||
} );
|
||||
this.changesListViewModel.connect( this, {
|
||||
invalidate: 'onModelInvalidate',
|
||||
update: 'onModelUpdate'
|
||||
} );
|
||||
|
||||
this.$element.addClass( 'mw-rcfilters-ui-changesListWrapperWidget' );
|
||||
|
||||
// Set up highlight containers
|
||||
this.setupHighlightContainers( this.$element );
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
|
@ -35,26 +51,130 @@
|
|||
OO.mixinClass( mw.rcfilters.ui.ChangesListWrapperWidget, OO.ui.mixin.PendingElement );
|
||||
|
||||
/**
|
||||
* Respond to model invalidate
|
||||
* Respond to the highlight feature being toggled on and off
|
||||
*
|
||||
* @param {boolean} highlightEnabled
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onHighlightChange = function ( highlightEnabled ) {
|
||||
if ( highlightEnabled ) {
|
||||
this.applyHighlight();
|
||||
} else {
|
||||
this.clearHighlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to a filter item model update
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onItemUpdate = function () {
|
||||
if ( this.filtersViewModel.isHighlightEnabled() ) {
|
||||
this.clearHighlight();
|
||||
this.applyHighlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to changes list model invalidate
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onModelInvalidate = function () {
|
||||
this.pushPending();
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to model update
|
||||
* Respond to changes list model update
|
||||
*
|
||||
* @param {jQuery|string} changesListContent The content of the updated changes list
|
||||
* @param {jQuery|string} $changesListContent The content of the updated changes list
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onModelUpdate = function ( changesListContent ) {
|
||||
var isEmpty = changesListContent === 'NO_RESULTS';
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onModelUpdate = function ( $changesListContent ) {
|
||||
var isEmpty = $changesListContent === 'NO_RESULTS';
|
||||
|
||||
this.$element.toggleClass( 'mw-changeslist', !isEmpty );
|
||||
this.$element.toggleClass( 'mw-changeslist-empty', isEmpty );
|
||||
this.$element.empty().append(
|
||||
isEmpty ?
|
||||
document.createTextNode( mw.message( 'recentchanges-noresult' ).text() ) :
|
||||
changesListContent
|
||||
);
|
||||
if ( isEmpty ) {
|
||||
this.$changesListContent = null;
|
||||
this.$element.empty().append(
|
||||
document.createTextNode( mw.message( 'recentchanges-noresult' ).text() )
|
||||
);
|
||||
} else {
|
||||
this.$changesListContent = $changesListContent;
|
||||
this.$element.empty().append( this.$changesListContent );
|
||||
// Set up highlight containers
|
||||
this.setupHighlightContainers( this.$element );
|
||||
|
||||
// Apply highlight
|
||||
this.applyHighlight();
|
||||
|
||||
// Make sure enhanced RC re-initializes correctly
|
||||
mw.hook( 'wikipage.content' ).fire( this.$changesListContent );
|
||||
}
|
||||
this.popPending();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up the highlight containers with all color circle indicators.
|
||||
*
|
||||
* @param {jQuery|string} $content The content of the updated changes list
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.setupHighlightContainers = function ( $content ) {
|
||||
var $highlights = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-color-none' )
|
||||
.prop( 'data-color', 'none' )
|
||||
);
|
||||
|
||||
mw.rcfilters.HighlightColors.forEach( function ( color ) {
|
||||
$highlights.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlights-color-' + color )
|
||||
.prop( 'data-color', color )
|
||||
);
|
||||
} );
|
||||
|
||||
if ( Number( mw.user.options.get( 'usenewrc' ) ) ) {
|
||||
// Enhanced RC
|
||||
$content.find( 'td.mw-enhanced-rc' )
|
||||
.parent()
|
||||
.prepend(
|
||||
$( '<td>' )
|
||||
.append( $highlights.clone() )
|
||||
);
|
||||
} else {
|
||||
// Regular RC
|
||||
$content.find( 'ul.special li' )
|
||||
.prepend( $highlights.clone() );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply color classes based on filters highlight configuration
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.applyHighlight = function () {
|
||||
if ( !this.filtersViewModel.isHighlightEnabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.filtersViewModel.getHighlightedItems().forEach( function ( filterItem ) {
|
||||
// Add highlight class to all highlighted list items
|
||||
this.$element.find( '.' + filterItem.getCssClass() )
|
||||
.addClass( 'mw-rcfilters-highlight-color-' + filterItem.getHighlightColor() );
|
||||
}.bind( this ) );
|
||||
|
||||
// Turn on highlights
|
||||
this.$element.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all color classes
|
||||
*/
|
||||
mw.rcfilters.ui.ChangesListWrapperWidget.prototype.clearHighlight = function () {
|
||||
// Remove highlight classes
|
||||
mw.rcfilters.HighlightColors.forEach( function ( color ) {
|
||||
this.$element.find( '.mw-rcfilters-highlight-color-' + color ).removeClass( 'mw-rcfilters-highlight-color-' + color );
|
||||
}.bind( this ) );
|
||||
|
||||
// Turn off highlights
|
||||
this.$element.removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
|
||||
};
|
||||
}( mediaWiki ) );
|
||||
|
|
|
|||
|
|
@ -13,14 +13,15 @@
|
|||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( controller, model, filterInput, config ) {
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.call( this, $.extend( {
|
||||
$autoCloseIgnore: filterInput.$element
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.call( this, $.extend( true, {
|
||||
popup: { $autoCloseIgnore: filterInput.$element.add( this.$overlay ) }
|
||||
}, config ) );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
this.filterInput = filterInput;
|
||||
|
||||
|
|
@ -44,7 +45,10 @@
|
|||
|
||||
// Events
|
||||
this.resetButton.connect( this, { click: 'onResetButtonClick' } );
|
||||
this.model.connect( this, { itemUpdate: 'onModelItemUpdate' } );
|
||||
this.model.connect( this, {
|
||||
itemUpdate: 'onModelItemUpdate',
|
||||
highlightChange: 'onModelHighlightChange'
|
||||
} );
|
||||
// Add the filterInput as trigger
|
||||
this.filterInput.$input
|
||||
.on( 'focus', this.focus.bind( this ) );
|
||||
|
|
@ -101,7 +105,14 @@
|
|||
* @param {mw.rcfilters.dm.FilterItem} item Filter item model
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
|
||||
if ( item.isSelected() ) {
|
||||
if (
|
||||
item.isSelected() ||
|
||||
(
|
||||
this.model.isHighlightEnabled() &&
|
||||
item.isHighlightSupported() &&
|
||||
item.getHighlightColor()
|
||||
)
|
||||
) {
|
||||
this.addItemByName( item.getName() );
|
||||
} else {
|
||||
this.removeItemByName( item.getName() );
|
||||
|
|
@ -111,6 +122,29 @@
|
|||
this.reevaluateResetRestoreState();
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to highlightChange event
|
||||
*
|
||||
* @param {boolean} isHighlightEnabled Highlight is enabled
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelHighlightChange = function ( isHighlightEnabled ) {
|
||||
var highlightedItems = this.model.getHighlightedItems();
|
||||
|
||||
if ( isHighlightEnabled ) {
|
||||
// Add capsule widgets
|
||||
highlightedItems.forEach( function ( filterItem ) {
|
||||
this.addItemByName( filterItem.getName() );
|
||||
}.bind( this ) );
|
||||
} else {
|
||||
// Remove capsule widgets if they're not selected
|
||||
highlightedItems.forEach( function ( filterItem ) {
|
||||
if ( !filterItem.isSelected() ) {
|
||||
this.removeItemByName( filterItem.getName() );
|
||||
}
|
||||
}.bind( this ) );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to click event on the reset button
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
$label: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-title' )
|
||||
} ) );
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Populate
|
||||
this.populateFromModel();
|
||||
|
|
@ -68,7 +69,8 @@
|
|||
filterItem,
|
||||
{
|
||||
label: filterItem.getLabel(),
|
||||
description: filterItem.getDescription()
|
||||
description: filterItem.getDescription(),
|
||||
$overlay: widget.$overlay
|
||||
}
|
||||
);
|
||||
} )
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* A button to configure highlight for a filter item
|
||||
*
|
||||
* @extends OO.ui.PopupButtonWidget
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller RCFilters controller
|
||||
* @param {mw.rcfilters.dm.FilterItem} model Filter item model
|
||||
* @param {Object} [config] Configuration object
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemHighlightButton = function MwRcfiltersUiFilterItemHighlightButton( controller, model, config ) {
|
||||
config = config || {};
|
||||
|
||||
this.colorPickerWidget = new mw.rcfilters.ui.HighlightColorPickerWidget( controller, model );
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterItemHighlightButton.parent.call( this, $.extend( {}, config, {
|
||||
icon: 'edit',
|
||||
indicator: 'down',
|
||||
popup: {
|
||||
anchor: false,
|
||||
padded: true,
|
||||
align: 'backwards',
|
||||
width: 290,
|
||||
$content: this.colorPickerWidget.$element
|
||||
}
|
||||
} ) );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
|
||||
// Event
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
this.colorPickerWidget.connect( this, { chooseColor: 'onChooseColor' } );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterItemHighlightButton' );
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterItemHighlightButton, OO.ui.PopupButtonWidget );
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to item model update event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemHighlightButton.prototype.onModelUpdate = function () {
|
||||
var currentColor = this.model.getHighlightColor(),
|
||||
widget = this;
|
||||
|
||||
this.$icon.toggleClass(
|
||||
'mw-rcfilters-ui-filterItemHighlightButton-circle',
|
||||
currentColor !== null
|
||||
);
|
||||
|
||||
mw.rcfilters.HighlightColors.forEach( function ( c ) {
|
||||
widget.$icon
|
||||
.toggleClass(
|
||||
'mw-rcfilters-ui-filterItemHighlightButton-circle-color-' + c,
|
||||
c === currentColor
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
mw.rcfilters.ui.FilterItemHighlightButton.prototype.onChooseColor = function () {
|
||||
this.popup.toggle( false );
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
@ -40,6 +40,15 @@
|
|||
);
|
||||
}
|
||||
|
||||
this.highlightButton = new mw.rcfilters.ui.FilterItemHighlightButton(
|
||||
this.controller,
|
||||
this.model,
|
||||
{
|
||||
$overlay: config.$overlay || this.$element
|
||||
}
|
||||
);
|
||||
this.highlightButton.toggle( this.model.isHighlightEnabled() );
|
||||
|
||||
layout = new OO.ui.FieldLayout( this.checkboxWidget, {
|
||||
label: $label,
|
||||
align: 'inline'
|
||||
|
|
@ -53,7 +62,20 @@
|
|||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterItemWidget' )
|
||||
.append(
|
||||
layout.$element
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-table' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-row' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterItemWidget-filterCheckbox' )
|
||||
.append( layout.$element ),
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterItemWidget-highlightButton' )
|
||||
.append( this.highlightButton.$element )
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -106,7 +128,10 @@
|
|||
!this.model.isSelected()
|
||||
)
|
||||
);
|
||||
|
||||
this.highlightButton.toggle( this.model.isHighlightEnabled() );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the name of this filter
|
||||
*
|
||||
|
|
@ -115,5 +140,4 @@
|
|||
mw.rcfilters.ui.FilterItemWidget.prototype.getName = function () {
|
||||
return this.model.getName();
|
||||
};
|
||||
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
this.controller,
|
||||
this.model,
|
||||
{
|
||||
label: mw.msg( 'rcfilters-filterlist-title' )
|
||||
label: mw.msg( 'rcfilters-filterlist-title' ),
|
||||
$overlay: this.$overlay
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@
|
|||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
this.highlightButton = new OO.ui.ButtonWidget( {
|
||||
label: mw.message( 'rcfilters-highlightbutton-title' ).text(),
|
||||
classes: [ 'mw-rcfilters-ui-filtersListWidget-hightlightButton' ]
|
||||
} );
|
||||
|
||||
this.$label.append( this.highlightButton.$element );
|
||||
|
||||
this.noResultsLabel = new OO.ui.LabelWidget( {
|
||||
label: mw.msg( 'rcfilters-filterlist-noresults' ),
|
||||
|
|
@ -32,8 +40,10 @@
|
|||
} );
|
||||
|
||||
// Events
|
||||
this.highlightButton.connect( this, { click: 'onHighlightButtonClick' } );
|
||||
this.model.connect( this, {
|
||||
initialize: 'onModelInitialize'
|
||||
initialize: 'onModelInitialize',
|
||||
highlightChange: 'onHighlightChange'
|
||||
} );
|
||||
|
||||
// Initialize
|
||||
|
|
@ -69,12 +79,26 @@
|
|||
Object.keys( this.model.getFilterGroups() ).map( function ( groupName ) {
|
||||
return new mw.rcfilters.ui.FilterGroupWidget(
|
||||
widget.controller,
|
||||
widget.model.getGroup( groupName )
|
||||
widget.model.getGroup( groupName ),
|
||||
{
|
||||
$overlay: widget.$overlay
|
||||
}
|
||||
);
|
||||
} )
|
||||
);
|
||||
};
|
||||
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.onHighlightChange = function ( highlightEnabled ) {
|
||||
this.highlightButton.setActive( highlightEnabled );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to highlight button click
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.onHighlightButtonClick = function () {
|
||||
this.controller.toggleHighlight();
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch between showing the 'no results' message for filtering results or the result list.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* A widget representing a filter item highlight color picker
|
||||
*
|
||||
* @extends OO.ui.Widget
|
||||
* @mixins OO.ui.mixin.LabelElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller RCFilters controller
|
||||
* @param {mw.rcfilters.dm.FilterItem} model Filter item model
|
||||
* @param {Object} [config] Configuration object
|
||||
*/
|
||||
mw.rcfilters.ui.HighlightColorPickerWidget = function MwRcfiltersUiHighlightColorPickerWidget( controller, model, config ) {
|
||||
var colors = [ 'none' ].concat( mw.rcfilters.HighlightColors );
|
||||
config = config || {};
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.HighlightColorPickerWidget.parent.call( this, config );
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, {
|
||||
label: mw.message( 'rcfilters-highlightmenu-title' ).text()
|
||||
} ) );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
|
||||
this.currentSelection = '';
|
||||
this.buttonSelect = new OO.ui.ButtonSelectWidget( {
|
||||
items: colors.map( function ( color ) {
|
||||
return new OO.ui.ButtonOptionWidget( {
|
||||
icon: color === 'none' ? 'check' : null,
|
||||
data: color,
|
||||
classes: [
|
||||
'mw-rcfilters-ui-highlightColorPickerWidget-buttonSelect-color',
|
||||
'mw-rcfilters-ui-highlightColorPickerWidget-buttonSelect-color-' + color
|
||||
],
|
||||
framed: false
|
||||
} );
|
||||
} ),
|
||||
classes: 'mw-rcfilters-ui-highlightColorPickerWidget-buttonSelect'
|
||||
} );
|
||||
this.selectColor( 'none' );
|
||||
|
||||
// Event
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
this.buttonSelect.connect( this, { choose: 'onChooseColor' } );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-highlightColorPickerWidget' )
|
||||
.append(
|
||||
this.$label
|
||||
.addClass( 'mw-rcfilters-ui-highlightColorPickerWidget-label' ),
|
||||
this.buttonSelect.$element
|
||||
);
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.HighlightColorPickerWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.rcfilters.ui.HighlightColorPickerWidget, OO.ui.mixin.LabelElement );
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event chooseColor
|
||||
* @param {string} The chosen color
|
||||
*
|
||||
* A color has been chosen
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to item model update event
|
||||
*/
|
||||
mw.rcfilters.ui.HighlightColorPickerWidget.prototype.onModelUpdate = function () {
|
||||
this.selectColor( this.model.getHighlightColor() || 'none' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the color for this widget
|
||||
*
|
||||
* @param {string} color Selected color
|
||||
*/
|
||||
mw.rcfilters.ui.HighlightColorPickerWidget.prototype.selectColor = function ( color ) {
|
||||
var previousItem = this.buttonSelect.getItemFromData( this.currentSelection ),
|
||||
selectedItem = this.buttonSelect.getItemFromData( color );
|
||||
|
||||
if ( this.currentSelection !== color ) {
|
||||
this.currentSelection = color;
|
||||
|
||||
this.buttonSelect.selectItem( selectedItem );
|
||||
if ( previousItem ) {
|
||||
previousItem.setIcon( null );
|
||||
}
|
||||
|
||||
if ( selectedItem ) {
|
||||
selectedItem.setIcon( 'check' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mw.rcfilters.ui.HighlightColorPickerWidget.prototype.onChooseColor = function ( button ) {
|
||||
var color = button.data;
|
||||
if ( color === 'none' ) {
|
||||
this.controller.clearHighlightColor( this.model.getName() );
|
||||
} else {
|
||||
this.controller.setHighlightColor( this.model.getName(), color );
|
||||
}
|
||||
this.emit( 'chooseColor', color );
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
@ -884,4 +884,53 @@ class UserTest extends MediaWikiTestCase {
|
|||
$noRateLimitUser->expects( $this->any() )->method( 'getRights' )->willReturn( [ 'noratelimit' ] );
|
||||
$this->assertFalse( $noRateLimitUser->isPingLimitable() );
|
||||
}
|
||||
|
||||
public function provideExperienceLevel() {
|
||||
return [
|
||||
[ 2, 2, 'newcomer' ],
|
||||
[ 12, 3, 'newcomer' ],
|
||||
[ 8, 5, 'newcomer' ],
|
||||
[ 15, 10, 'learner' ],
|
||||
[ 450, 20, 'learner' ],
|
||||
[ 460, 33, 'learner' ],
|
||||
[ 525, 28, 'learner' ],
|
||||
[ 538, 33, 'experienced' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideExperienceLevel
|
||||
*/
|
||||
public function testExperienceLevel( $editCount, $memberSince, $expLevel ) {
|
||||
$this->setMwGlobals( [
|
||||
'wgLearnerEdits' => 10,
|
||||
'wgLearnerMemberSince' => 4,
|
||||
'wgExperiencedUserEdits' => 500,
|
||||
'wgExperiencedUserMemberSince' => 30,
|
||||
] );
|
||||
|
||||
$db = wfGetDB( DB_MASTER );
|
||||
|
||||
$data = new stdClass();
|
||||
$data->user_id = 1;
|
||||
$data->user_name = 'name';
|
||||
$data->user_real_name = 'Real Name';
|
||||
$data->user_touched = 1;
|
||||
$data->user_token = 'token';
|
||||
$data->user_email = 'a@a.a';
|
||||
$data->user_email_authenticated = null;
|
||||
$data->user_email_token = 'token';
|
||||
$data->user_email_token_expires = null;
|
||||
$data->user_editcount = $editCount;
|
||||
$data->user_registration = $db->timestamp( time() - $memberSince * 86400 );
|
||||
$user = User::newFromRow( $data );
|
||||
|
||||
$this->assertEquals( $expLevel, $user->getExperienceLevel() );
|
||||
}
|
||||
|
||||
public function testExperienceLevelAnon() {
|
||||
$user = User::newFromName( '10.11.12.13', false );
|
||||
|
||||
$this->assertFalse( $user->getExperienceLevel() );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1157,4 +1157,116 @@
|
|||
'Selecting a non-conflicting filter from a conflicting group removes the conflict'
|
||||
);
|
||||
} );
|
||||
|
||||
QUnit.test( 'Filter highlights', function ( assert ) {
|
||||
var definition = {
|
||||
group1: {
|
||||
title: 'Group 1',
|
||||
type: 'string_options',
|
||||
filters: [
|
||||
{ name: 'filter1', class: 'class1' },
|
||||
{ name: 'filter2', class: 'class2' },
|
||||
{ name: 'filter3', class: 'class3' },
|
||||
{ name: 'filter4', class: 'class4' },
|
||||
{ name: 'filter5', class: 'class5' },
|
||||
{ name: 'filter6' }
|
||||
]
|
||||
}
|
||||
},
|
||||
model = new mw.rcfilters.dm.FiltersViewModel();
|
||||
|
||||
model.initializeFilters( definition );
|
||||
|
||||
assert.ok(
|
||||
!model.isHighlightEnabled(),
|
||||
'Initially, highlight is disabled.'
|
||||
);
|
||||
|
||||
model.toggleHighlight( true );
|
||||
assert.ok(
|
||||
model.isHighlightEnabled(),
|
||||
'Highlight is enabled on toggle.'
|
||||
);
|
||||
|
||||
model.setHighlightColor( 'filter1', 'color1' );
|
||||
model.setHighlightColor( 'filter2', 'color2' );
|
||||
|
||||
assert.deepEqual(
|
||||
model.getHighlightedItems().map( function ( item ) {
|
||||
return item.getName();
|
||||
} ),
|
||||
[
|
||||
'filter1',
|
||||
'filter2'
|
||||
],
|
||||
'Highlighted items are highlighted.'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
model.getItemByName( 'filter1' ).getHighlightColor(),
|
||||
'color1',
|
||||
'Item highlight color is set.'
|
||||
);
|
||||
|
||||
model.setHighlightColor( 'filter1', 'color1changed' );
|
||||
assert.equal(
|
||||
model.getItemByName( 'filter1' ).getHighlightColor(),
|
||||
'color1changed',
|
||||
'Item highlight color is changed on setHighlightColor.'
|
||||
);
|
||||
|
||||
model.clearHighlightColor( 'filter1' );
|
||||
assert.deepEqual(
|
||||
model.getHighlightedItems().map( function ( item ) {
|
||||
return item.getName();
|
||||
} ),
|
||||
[
|
||||
'filter2'
|
||||
],
|
||||
'Clear highlight from an item results in the item no longer being highlighted.'
|
||||
);
|
||||
|
||||
// Reset
|
||||
model = new mw.rcfilters.dm.FiltersViewModel();
|
||||
model.initializeFilters( definition );
|
||||
|
||||
model.setHighlightColor( 'filter1', 'color1' );
|
||||
model.setHighlightColor( 'filter2', 'color2' );
|
||||
model.setHighlightColor( 'filter3', 'color3' );
|
||||
|
||||
assert.deepEqual(
|
||||
model.getHighlightedItems().map( function ( item ) {
|
||||
return item.getName();
|
||||
} ),
|
||||
[
|
||||
'filter1',
|
||||
'filter2',
|
||||
'filter3'
|
||||
],
|
||||
'Even if highlights are not enabled, the items remember their highlight state'
|
||||
// NOTE: When actually displaying the highlights, the UI checks whether
|
||||
// highlighting is generally active and then goes over the highlighted
|
||||
// items. The item models, however, and the view model in general, still
|
||||
// retains the knowledge about which filters have different colors, so we
|
||||
// can seamlessly return to the colors the user previously chose if they
|
||||
// reapply highlights.
|
||||
);
|
||||
|
||||
// Reset
|
||||
model = new mw.rcfilters.dm.FiltersViewModel();
|
||||
model.initializeFilters( definition );
|
||||
|
||||
model.setHighlightColor( 'filter1', 'color1' );
|
||||
model.setHighlightColor( 'filter6', 'color6' );
|
||||
|
||||
assert.deepEqual(
|
||||
model.getHighlightedItems().map( function ( item ) {
|
||||
return item.getName();
|
||||
} ),
|
||||
[
|
||||
'filter1'
|
||||
],
|
||||
'Items without a specified class identifier are not highlighted.'
|
||||
);
|
||||
} );
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
|||
Loading…
Reference in a new issue