RCFilters: Adjust to use MenuTagMultiselectWidget
The new widget in OOUI is more stable and easier to manage, and gives us a few features that we were missing, like arrow behavior in the menu. Depends on OOUI release 0.21.0 Bug: T162829 Bug: T159768 Bug: T162709 Bug: T162917 Change-Id: I42be0691304b1e93b4e9c02eba2e3a724a5ffd67 Depends-On: Ic216769f48e4677da5b7274f491aa08a95aa8076
This commit is contained in:
parent
a9ea3a3ee9
commit
a703e5236b
21 changed files with 931 additions and 1118 deletions
|
|
@ -1751,11 +1751,12 @@ return [
|
|||
'mediawiki.rcfilters.filters.ui' => [
|
||||
'scripts' => [
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CheckboxInputWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CapsuleItemWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuOptionWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuSectionOptionWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagItemWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterMenuHeaderWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.js',
|
||||
'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',
|
||||
|
|
@ -1769,12 +1770,13 @@ return [
|
|||
'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',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterGroupWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FiltersListWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagMultiselectWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuOptionWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuSectionOptionWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagItemWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuHeaderWidget.less',
|
||||
'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterFloatingMenuSelectWidget.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',
|
||||
|
|
|
|||
|
|
@ -734,13 +734,18 @@
|
|||
* 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
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( query ) {
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( query, returnFlat ) {
|
||||
var i,
|
||||
groupTitle,
|
||||
result = {},
|
||||
flatResult = [],
|
||||
items = this.getItems();
|
||||
|
||||
// Normalize so we can search strings regardless of case
|
||||
|
|
@ -751,6 +756,7 @@
|
|||
if ( items[ i ].getLabel().toLowerCase().indexOf( query ) === 0 ) {
|
||||
result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
|
||||
result[ items[ i ].getGroupName() ].push( items[ i ] );
|
||||
flatResult.push( items[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -765,11 +771,12 @@
|
|||
) {
|
||||
result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
|
||||
result[ items[ i ].getGroupName() ].push( items[ i ] );
|
||||
flatResult.push( items[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return returnFlat ? flatResult : result;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
@import 'mediawiki.mixins';
|
||||
|
||||
.mw-rcfilters-ui-filterFloatingMenuSelectWidget {
|
||||
z-index: auto;
|
||||
max-width: 650px;
|
||||
|
||||
&-body {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
&-footer {
|
||||
background-color: #f8f9fa;
|
||||
text-align: right;
|
||||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
.mw-rcfilters-ui-filtersListWidget {
|
||||
@import 'mediawiki.mixins';
|
||||
|
||||
.mw-rcfilters-ui-filterMenuHeaderWidget {
|
||||
&-title {
|
||||
font-size: 1.2em;
|
||||
padding: 0.75em 0.5em;
|
||||
|
|
@ -25,10 +27,4 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&-noresults {
|
||||
padding: 0.5em;
|
||||
// TODO: Unify colors with official design palette
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@import 'mediawiki.mixins';
|
||||
|
||||
.mw-rcfilters-ui-filterItemWidget {
|
||||
.mw-rcfilters-ui-filterMenuOptionWidget {
|
||||
padding: 0 0.5em;
|
||||
.box-sizing( border-box );
|
||||
|
||||
|
|
@ -18,13 +18,13 @@
|
|||
|
||||
&-muted {
|
||||
background-color: #f8f9fa; // Base90 AAA
|
||||
.mw-rcfilters-ui-filterItemWidget-label-title,
|
||||
.mw-rcfilters-ui-filterItemWidget-label-desc {
|
||||
.mw-rcfilters-ui-filterMenuOptionWidget-label-title,
|
||||
.mw-rcfilters-ui-filterMenuOptionWidget-label-desc {
|
||||
color: #54595d; // Base20 AAA
|
||||
}
|
||||
}
|
||||
|
||||
&-selected {
|
||||
&.oo-ui-optionWidget-selected {
|
||||
background-color: #eaf3ff; // Accent90 AAA
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
}
|
||||
&-desc {
|
||||
color: #464a4f;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +1,23 @@
|
|||
@import 'mediawiki.mixins';
|
||||
|
||||
.mw-rcfilters-ui-filterGroupWidget {
|
||||
padding-bottom: 0.5em;
|
||||
.mw-rcfilters-ui-filterMenuSectionOptionWidget {
|
||||
background: #eaecf0;
|
||||
padding-bottom: 0.7em;
|
||||
|
||||
&-header {
|
||||
background: #eaecf0;
|
||||
padding: 0.5em 0.75em;
|
||||
|
||||
&-title {
|
||||
padding: 0 0.75em;
|
||||
// Use a high specificity to override OOUI
|
||||
.oo-ui-optionWidget.oo-ui-labelElement &-title.oo-ui-labelElement-label {
|
||||
// TODO: Unify colors with official design palette
|
||||
color: #555a5d;
|
||||
.box-sizing( border-box );
|
||||
display: inline-block;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&-whatsThisButton {
|
||||
display: inline-block;
|
||||
margin-left: 1.5em;
|
||||
|
||||
&.oo-ui-buttonElement {
|
||||
vertical-align: text-bottom;
|
||||
|
||||
|
|
@ -45,7 +44,6 @@
|
|||
|
||||
&-link {
|
||||
margin: 1em 0;
|
||||
|
||||
}
|
||||
|
||||
.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
|
||||
|
|
@ -55,7 +53,7 @@
|
|||
}
|
||||
|
||||
&-active {
|
||||
.mw-rcfilters-ui-filterGroupWidget-header-title {
|
||||
.mw-rcfilters-ui-filterMenuSectionOptionWidget-header-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
@import 'mw.rcfilters.mixins';
|
||||
|
||||
.mw-rcfilters-ui-capsuleItemWidget {
|
||||
.mw-rcfilters-ui-filterTagItemWidget {
|
||||
background-color: #fff;
|
||||
border-color: #979797;
|
||||
color: #222;
|
||||
|
||||
// Background and color of the capsule widget need a bit
|
||||
// more specificity to override ooui internals
|
||||
&-muted.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
|
||||
&-muted.oo-ui-tagItemWidget.oo-ui-widget-enabled {
|
||||
// Muted state
|
||||
background-color: #eaecf0;
|
||||
border-color: #c8ccd1;
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-conflicted.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
|
||||
&-conflicted.oo-ui-tagItemWidget.oo-ui-widget-enabled {
|
||||
background-color: #fee7e6; // Red90 AAA
|
||||
border-color: #b32424; // Red30 AAA
|
||||
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-selected.oo-ui-capsuleItemWidget.oo-ui-widget-enabled {
|
||||
&-selected.oo-ui-tagItemWidget.oo-ui-widget-enabled {
|
||||
background-color: #eaf3ff;
|
||||
border-color: #36c;
|
||||
}
|
||||
|
|
@ -49,27 +49,39 @@
|
|||
|
||||
&-highlight {
|
||||
display: none;
|
||||
padding-right: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
height: 100%;
|
||||
width: 10px;
|
||||
|
||||
&-highlighted {
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
&[data-color='c1'] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c1, 10px, ~'0 0.5em 0 0' );
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
}
|
||||
&[data-color='c2'] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c2, 10px, ~'0 0.5em 0 0' );
|
||||
|
||||
&[data-color='c1']:before {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c1, 10px, ~'-5px 0.5em 0 0' );
|
||||
}
|
||||
&[data-color='c3'] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c3, 10px, ~'0 0.5em 0 0' );
|
||||
|
||||
&[data-color='c2']:before {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c2, 10px, ~'-5px 0.5em 0 0' );
|
||||
}
|
||||
&[data-color='c4'] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c4, 10px, ~'0 0.5em 0 0' );
|
||||
|
||||
&[data-color='c3']:before {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c3, 10px, ~'-5px 0.5em 0 0' );
|
||||
}
|
||||
&[data-color='c5'] {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c5, 10px, ~'0 0.5em 0 0' );
|
||||
|
||||
&[data-color='c4']:before {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c4, 10px, ~'-5px 0.5em 0 0' );
|
||||
}
|
||||
|
||||
&[data-color='c5']:before {
|
||||
.mw-rcfilters-mixin-circle( @highlight-c5, 10px, ~'-5px 0.5em 0 0' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
.mw-rcfilters-ui-filterCapsuleMultiselectWidget {
|
||||
.mw-rcfilters-ui-filterTagMultiselectWidget {
|
||||
max-width: none;
|
||||
|
||||
&.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
|
||||
&.oo-ui-widget-enabled .oo-ui-tagMultiselectWidget-handle {
|
||||
border: 1px solid #a2a9b1;
|
||||
border-bottom: 0;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 2px 2px 0 0;
|
||||
padding: 0.3em 0.6em 0.6em 0.6em;
|
||||
|
|
@ -24,6 +26,7 @@
|
|||
&-cell-filters {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-cell-reset {
|
||||
text-align: right;
|
||||
padding-left: 0.5em;
|
||||
|
|
@ -3,21 +3,6 @@
|
|||
// Make sure this uses the interface direction, not the content direction
|
||||
direction: ltr;
|
||||
|
||||
&-popup {
|
||||
margin-top: 1px;
|
||||
max-width: 650px;
|
||||
|
||||
.oo-ui-popupWidget-body {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.oo-ui-popupWidget-footer {
|
||||
background-color: #f8f9fa;
|
||||
text-align: right;
|
||||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&-search {
|
||||
max-width: none;
|
||||
margin-top: -1px;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,16 @@
|
|||
mw.rcfilters.ui.CheckboxInputWidget.parent.call( this, config );
|
||||
|
||||
// Event
|
||||
this.$input.on( 'change', this.onUserChange.bind( this ) );
|
||||
this.$input
|
||||
// HACK: This widget just pretends to be a checkbox for visual purposes.
|
||||
// In reality, all actions - setting to true or false, etc - are
|
||||
// decided by the model, and executed by the controller. This means
|
||||
// that we want to let the controller and model make the decision
|
||||
// of whether to check/uncheck this checkboxInputWidget, and for that,
|
||||
// we have to bypass the browser action that checks/unchecks it during
|
||||
// click.
|
||||
.on( 'click', false )
|
||||
.on( 'change', this.onUserChange.bind( this ) );
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
|
@ -32,6 +41,19 @@
|
|||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.CheckboxInputWidget.prototype.onEdit = function () {
|
||||
// Similarly to preventing defaults in 'click' event, we want
|
||||
// to prevent this widget from deciding anything about its own
|
||||
// state; it emits a change event and the model and controller
|
||||
// make a decision about what its select state is.
|
||||
// onEdit has a widget.$input.prop( 'checked' ) inside a setTimeout()
|
||||
// so we really want to prevent that from messing with what
|
||||
// the model decides the state of the widget is.
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to checkbox change by a user and emit 'userChange'.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,342 +0,0 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* Filter-specific CapsuleMultiselectWidget
|
||||
*
|
||||
* @class
|
||||
* @extends OO.ui.CapsuleMultiselectWidget
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller RCFilters controller
|
||||
* @param {mw.rcfilters.dm.FiltersViewModel} model RCFilters view model
|
||||
* @param {OO.ui.InputWidget} filterInput A filter input that focuses the capsule widget
|
||||
* @param {Object} config Configuration object
|
||||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( controller, model, filterInput, config ) {
|
||||
var title = new OO.ui.LabelWidget( {
|
||||
label: mw.msg( 'rcfilters-activefilters' ),
|
||||
classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-wrapper-content-title' ]
|
||||
} ),
|
||||
$contentWrapper = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-wrapper' );
|
||||
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.call( this, $.extend( true, {
|
||||
popup: {
|
||||
$autoCloseIgnore: filterInput.$element.add( this.$overlay ),
|
||||
$floatableContainer: filterInput.$element
|
||||
}
|
||||
}, config ) );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.filterInput = filterInput;
|
||||
this.isSelecting = false;
|
||||
this.selected = null;
|
||||
|
||||
this.resetButton = new OO.ui.ButtonWidget( {
|
||||
framed: false,
|
||||
classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-resetButton' ]
|
||||
} );
|
||||
|
||||
this.emptyFilterMessage = new OO.ui.LabelWidget( {
|
||||
label: mw.msg( 'rcfilters-empty-filter' ),
|
||||
classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-emptyFilters' ]
|
||||
} );
|
||||
this.$content.append( this.emptyFilterMessage.$element );
|
||||
|
||||
// Events
|
||||
this.resetButton.connect( this, { click: 'onResetButtonClick' } );
|
||||
this.resetButton.$element.on( 'mousedown', this.onResetButtonMouseDown.bind( this ) );
|
||||
this.model.connect( this, {
|
||||
itemUpdate: 'onModelItemUpdate',
|
||||
highlightChange: 'onModelHighlightChange'
|
||||
} );
|
||||
this.aggregate( { click: 'capsuleItemClick' } );
|
||||
|
||||
// Add the filterInput as trigger
|
||||
this.filterInput.$input
|
||||
.on( 'focus', this.focus.bind( this ) );
|
||||
|
||||
// Build the content
|
||||
$contentWrapper.append(
|
||||
title.$element,
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-table' )
|
||||
.append(
|
||||
// The filter list and button should appear side by side regardless of how
|
||||
// wide the button is; the button also changes its width depending
|
||||
// on language and its state, so the safest way to present both side
|
||||
// by side is with a table layout
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-row' )
|
||||
.append(
|
||||
this.$content
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell-filters' ),
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell-reset' )
|
||||
.append( this.resetButton.$element )
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Initialize
|
||||
this.$handle.append( $contentWrapper );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget' );
|
||||
|
||||
this.reevaluateResetRestoreState();
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterCapsuleMultiselectWidget, OO.ui.CapsuleMultiselectWidget );
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event remove
|
||||
* @param {string[]} filters Array of names of removed filters
|
||||
*
|
||||
* Filters were removed
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to model itemUpdate event
|
||||
*
|
||||
* @param {mw.rcfilters.dm.FilterItem} item Filter item model
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
|
||||
if (
|
||||
item.isSelected() ||
|
||||
(
|
||||
this.model.isHighlightEnabled() &&
|
||||
item.isHighlightSupported() &&
|
||||
item.getHighlightColor()
|
||||
)
|
||||
) {
|
||||
this.addItemByName( item.getName() );
|
||||
} else {
|
||||
this.removeItemByName( item.getName() );
|
||||
}
|
||||
|
||||
// Re-evaluate reset state
|
||||
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
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onResetButtonClick = function () {
|
||||
if ( this.model.areCurrentFiltersEmpty() ) {
|
||||
// Reset to default filters
|
||||
this.controller.resetToDefaults();
|
||||
} else {
|
||||
// Reset to have no filters
|
||||
this.controller.emptyFilters();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to mouse down event on the reset button to prevent the popup from opening
|
||||
*
|
||||
* @param {jQuery.Event} e Event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onResetButtonMouseDown = function ( e ) {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reevaluate the restore state for the widget between setting to defaults and clearing all filters
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.reevaluateResetRestoreState = function () {
|
||||
var defaultsAreEmpty = this.model.areDefaultFiltersEmpty(),
|
||||
currFiltersAreEmpty = this.model.areCurrentFiltersEmpty(),
|
||||
hideResetButton = currFiltersAreEmpty && defaultsAreEmpty;
|
||||
|
||||
this.resetButton.setIcon(
|
||||
currFiltersAreEmpty ? 'history' : 'trash'
|
||||
);
|
||||
|
||||
this.resetButton.setLabel(
|
||||
currFiltersAreEmpty ? mw.msg( 'rcfilters-restore-default-filters' ) : ''
|
||||
);
|
||||
this.resetButton.setTitle(
|
||||
currFiltersAreEmpty ? null : mw.msg( 'rcfilters-clear-all-filters' )
|
||||
);
|
||||
|
||||
this.resetButton.toggle( !hideResetButton );
|
||||
this.emptyFilterMessage.toggle( currFiltersAreEmpty );
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark an item widget as selected
|
||||
*
|
||||
* @param {mw.rcfilters.ui.CapsuleItemWidget} item Capsule widget
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.select = function ( item ) {
|
||||
if ( this.selected !== item ) {
|
||||
// Unselect previous
|
||||
if ( this.selected ) {
|
||||
this.selected.toggleSelected( false );
|
||||
}
|
||||
|
||||
// Select new one
|
||||
this.selected = item;
|
||||
if ( this.selected ) {
|
||||
item.toggleSelected( true );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset selection and remove selected states from all items
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.resetSelection = function () {
|
||||
if ( this.selected !== null ) {
|
||||
this.selected = null;
|
||||
this.getItems().forEach( function ( capsuleWidget ) {
|
||||
capsuleWidget.toggleSelected( false );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.createItemWidget = function ( data ) {
|
||||
var item = this.model.getItemByName( data );
|
||||
|
||||
if ( !item ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new mw.rcfilters.ui.CapsuleItemWidget(
|
||||
this.controller,
|
||||
item,
|
||||
{ $overlay: this.$overlay }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add items by their filter name
|
||||
*
|
||||
* @param {string} name Filter name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.addItemByName = function ( name ) {
|
||||
var item = this.model.getItemByName( name );
|
||||
|
||||
if ( !item ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the item isn't already added
|
||||
if ( !this.getItemFromData( name ) ) {
|
||||
this.addItems( [ this.createItemWidget( name ) ] );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove items by their filter name
|
||||
*
|
||||
* @param {string} name Filter name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItemByName = function ( name ) {
|
||||
this.removeItemsFromData( [ name ] );
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.focus = function () {
|
||||
// Override this method; we don't want to focus on the popup, and we
|
||||
// don't want to bind the size to the handle.
|
||||
if ( !this.isDisabled() ) {
|
||||
this.popup.toggle( true );
|
||||
this.filterInput.$input.get( 0 ).focus();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onFocusForPopup = function () {
|
||||
// HACK can be removed once I21b8cff4048 is merged in oojs-ui
|
||||
this.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onKeyDown = function () {};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onPopupFocusOut = function () {};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.clearInput = function () {
|
||||
if ( this.filterInput ) {
|
||||
this.filterInput.setValue( '' );
|
||||
}
|
||||
this.menu.toggle( false );
|
||||
this.menu.selectItem();
|
||||
this.menu.highlightItem();
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItems = function ( items ) {
|
||||
// Parent call
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.prototype.removeItems.call( this, items );
|
||||
|
||||
// Destroy the item widget when it is removed
|
||||
// This is done because we re-add items by recreating them, rather than hiding them
|
||||
// and items include popups, that will just continue to be created and appended
|
||||
// unnecessarily.
|
||||
items.forEach( function ( widget ) {
|
||||
widget.destroy();
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Override 'editItem' since it tries to use $input which does
|
||||
* not exist when a popup is available.
|
||||
*/
|
||||
mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.editItem = function () {};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
( function ( mw ) {
|
||||
/**
|
||||
* A floating menu widget for the filter list
|
||||
*
|
||||
* @extends OO.ui.FloatingMenuSelectWidget
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller Controller
|
||||
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
|
||||
* @param {Object} [config] Configuration object
|
||||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
* @cfg {jQuery} [$footer] An optional footer for the menu
|
||||
*/
|
||||
mw.rcfilters.ui.FilterFloatingMenuSelectWidget = function MwRcfiltersUiFilterFloatingMenuSelectWidget( controller, model, config ) {
|
||||
var header;
|
||||
|
||||
config = config || {};
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
|
||||
this.inputValue = '';
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
this.$footer = config.$footer;
|
||||
this.$body = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-body' );
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterFloatingMenuSelectWidget.parent.call( this, $.extend( {
|
||||
$autoCloseIgnore: this.$overlay,
|
||||
width: 650
|
||||
}, config ) );
|
||||
this.setGroupElement(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-group' )
|
||||
);
|
||||
this.setClippableElement( this.$body );
|
||||
this.setClippableContainer( this.$element );
|
||||
|
||||
header = new mw.rcfilters.ui.FilterMenuHeaderWidget(
|
||||
this.controller,
|
||||
this.model,
|
||||
{
|
||||
$overlay: this.$overlay
|
||||
}
|
||||
);
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget' )
|
||||
.append(
|
||||
this.$body
|
||||
.append( header.$element, this.$group )
|
||||
);
|
||||
|
||||
if ( this.$footer ) {
|
||||
this.$element.append(
|
||||
this.$footer
|
||||
.addClass( 'mw-rcfilters-ui-filterFloatingMenuSelectWidget-footer' )
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/* Initialize */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterFloatingMenuSelectWidget, OO.ui.FloatingMenuSelectWidget );
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event itemVisibilityChange
|
||||
*
|
||||
* Item visibility has changed
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* @fires itemVisibilityChange
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.updateItemVisibility = function () {
|
||||
var i,
|
||||
itemWasHighlighted = false,
|
||||
inputVal = this.$input.val(),
|
||||
items = this.getItems();
|
||||
|
||||
// Since the method hides/shows items, we don't want to
|
||||
// call it unless the input actually changed
|
||||
if ( this.inputValue !== inputVal ) {
|
||||
// Parent method
|
||||
mw.rcfilters.ui.FilterFloatingMenuSelectWidget.parent.prototype.updateItemVisibility.call( this );
|
||||
|
||||
if ( inputVal !== '' ) {
|
||||
// Highlight the first item in the list
|
||||
for ( i = 0; i < items.length; i++ ) {
|
||||
if (
|
||||
!( items[ i ] instanceof OO.ui.MenuSectionOptionWidget ) &&
|
||||
items[ i ].isVisible()
|
||||
) {
|
||||
itemWasHighlighted = true;
|
||||
this.highlightItem( items[ i ] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !itemWasHighlighted ) {
|
||||
this.highlightItem( null );
|
||||
}
|
||||
|
||||
// Cache value
|
||||
this.inputValue = inputVal;
|
||||
|
||||
this.emit( 'itemVisibilityChange' );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the item matcher to use the model's match process
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.getItemMatcher = function ( s ) {
|
||||
var results = this.model.findMatches( s, true );
|
||||
|
||||
return function ( item ) {
|
||||
return results.indexOf( item.getModel() ) > -1;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll to the top of the menu
|
||||
*/
|
||||
mw.rcfilters.ui.FilterFloatingMenuSelectWidget.prototype.scrollToTop = function () {
|
||||
this.$body.scrollTop( 0 );
|
||||
};
|
||||
}( mediaWiki ) );
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* A group of filters
|
||||
*
|
||||
* @extends OO.ui.Widget
|
||||
* @mixins OO.ui.mixin.GroupWidget
|
||||
* @mixins OO.ui.mixin.LabelElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller Controller
|
||||
* @param {mw.rcfilters.dm.FilterGroup} model Filter group model
|
||||
* @param {Object} config Configuration object
|
||||
* @cfg {jQuery} [$overlay] Overlay
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget = function MwRcfiltersUiFilterGroupWidget( controller, model, config ) {
|
||||
var whatsThisMessages,
|
||||
$header = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-header' ),
|
||||
$popupContent = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content' );
|
||||
|
||||
config = config || {};
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterGroupWidget.parent.call( this, config );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.filters = {};
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.GroupWidget.call( this, config );
|
||||
OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, {
|
||||
label: this.model.getTitle(),
|
||||
$label: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-header-title' )
|
||||
} ) );
|
||||
|
||||
$header.append( this.$label );
|
||||
|
||||
if ( this.model.hasWhatsThis() ) {
|
||||
whatsThisMessages = this.model.getWhatsThis();
|
||||
|
||||
// Create popup
|
||||
if ( whatsThisMessages.header ) {
|
||||
$popupContent.append(
|
||||
( new OO.ui.LabelWidget( {
|
||||
label: mw.msg( whatsThisMessages.header ),
|
||||
classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content-header' ]
|
||||
} ) ).$element
|
||||
);
|
||||
}
|
||||
if ( whatsThisMessages.body ) {
|
||||
$popupContent.append(
|
||||
( new OO.ui.LabelWidget( {
|
||||
label: mw.msg( whatsThisMessages.body ),
|
||||
classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content-body' ]
|
||||
} ) ).$element
|
||||
);
|
||||
}
|
||||
if ( whatsThisMessages.linkText && whatsThisMessages.url ) {
|
||||
$popupContent.append(
|
||||
( new OO.ui.ButtonWidget( {
|
||||
framed: false,
|
||||
flags: [ 'progressive' ],
|
||||
href: whatsThisMessages.url,
|
||||
label: mw.msg( whatsThisMessages.linkText ),
|
||||
classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup-content-link' ]
|
||||
} ) ).$element
|
||||
);
|
||||
}
|
||||
|
||||
// Add button
|
||||
this.whatsThisButton = new OO.ui.PopupButtonWidget( {
|
||||
framed: false,
|
||||
label: mw.msg( 'rcfilters-filterlist-whatsthis' ),
|
||||
$overlay: this.$overlay,
|
||||
classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton' ],
|
||||
flags: [ 'progressive' ],
|
||||
popup: {
|
||||
padded: false,
|
||||
align: 'center',
|
||||
position: 'above',
|
||||
$content: $popupContent,
|
||||
classes: [ 'mw-rcfilters-ui-filterGroupWidget-whatsThisButton-popup' ]
|
||||
}
|
||||
} );
|
||||
|
||||
$header
|
||||
.append( this.whatsThisButton.$element );
|
||||
}
|
||||
|
||||
// Populate
|
||||
this.populateFromModel();
|
||||
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget' )
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-name-' + this.model.getName() )
|
||||
.append(
|
||||
$header,
|
||||
this.$group
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-group' )
|
||||
);
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.GroupWidget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.LabelElement );
|
||||
|
||||
/**
|
||||
* Respond to model update event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.onModelUpdate = function () {
|
||||
this.$element.toggleClass(
|
||||
'mw-rcfilters-ui-filterGroupWidget-active',
|
||||
this.model.isActive()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an item widget from its filter name
|
||||
*
|
||||
* @param {string} filterName Filter name
|
||||
* @return {mw.rcfilters.ui.FilterItemWidget} Item widget
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.getItemWidget = function ( filterName ) {
|
||||
return this.filters[ filterName ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate data from the model
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.populateFromModel = function () {
|
||||
var widget = this;
|
||||
|
||||
this.clearItems();
|
||||
this.filters = {};
|
||||
|
||||
this.addItems(
|
||||
this.model.getItems().map( function ( filterItem ) {
|
||||
var groupWidget = new mw.rcfilters.ui.FilterItemWidget(
|
||||
widget.controller,
|
||||
filterItem,
|
||||
{
|
||||
label: filterItem.getLabel(),
|
||||
description: filterItem.getDescription(),
|
||||
$overlay: widget.$overlay
|
||||
}
|
||||
);
|
||||
|
||||
widget.filters[ filterItem.getName() ] = groupWidget;
|
||||
|
||||
return groupWidget;
|
||||
} )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the group name
|
||||
*
|
||||
* @return {string} Group name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.getName = function () {
|
||||
return this.model.getName();
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
this.colorPickerWidget = new mw.rcfilters.ui.HighlightColorPickerWidget( controller, model );
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterItemHighlightButton.parent.call( this, $.extend( {}, config, {
|
||||
mw.rcfilters.ui.FilterItemHighlightButton.parent.call( this, $.extend( true, {}, config, {
|
||||
icon: 'highlight',
|
||||
indicator: 'down',
|
||||
popup: {
|
||||
|
|
@ -39,6 +39,10 @@
|
|||
// Event
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
this.colorPickerWidget.connect( this, { chooseColor: 'onChooseColor' } );
|
||||
// This lives inside a MenuOptionWidget, which intercepts mousedown
|
||||
// to select the item. We want to prevent that when we click the highlight
|
||||
// button
|
||||
this.$element.on( 'mousedown', function ( e ) { e.stopPropagation(); } );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterItemHighlightButton' );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* Menu header for the RCFilters filters menu
|
||||
*
|
||||
* @extends OO.ui.Widget
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller Controller
|
||||
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
|
||||
* @param {Object} config Configuration object
|
||||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
*/
|
||||
mw.rcfilters.ui.FilterMenuHeaderWidget = function MwRcfiltersUiFilterMenuHeaderWidget( controller, model, config ) {
|
||||
config = config || {};
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterMenuHeaderWidget.parent.call( this, config );
|
||||
OO.ui.mixin.LabelElement.call( this, $.extend( {
|
||||
label: mw.msg( 'rcfilters-filterlist-title' ),
|
||||
$label: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-title' )
|
||||
}, config ) );
|
||||
|
||||
// Highlight button
|
||||
this.highlightButton = new OO.ui.ToggleButtonWidget( {
|
||||
icon: 'highlight',
|
||||
label: mw.message( 'rcfilters-highlightbutton-title' ).text(),
|
||||
classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-hightlightButton' ]
|
||||
} );
|
||||
|
||||
// Events
|
||||
this.highlightButton
|
||||
.connect( this, { click: 'onHighlightButtonClick' } );
|
||||
this.model.connect( this, { highlightChange: 'onModelHighlightChange' } );
|
||||
|
||||
// Initialize
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-table' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-row' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header-title' )
|
||||
.append( this.$label ),
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuHeaderWidget-header-highlight' )
|
||||
.append( this.highlightButton.$element )
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterMenuHeaderWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FilterMenuHeaderWidget, OO.ui.mixin.LabelElement );
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to model highlight change event
|
||||
*
|
||||
* @param {boolean} highlightEnabled Highlight is enabled
|
||||
*/
|
||||
mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelHighlightChange = function ( highlightEnabled ) {
|
||||
this.highlightButton.setActive( highlightEnabled );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to highlight button click
|
||||
*/
|
||||
mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onHighlightButtonClick = function () {
|
||||
this.controller.toggleHighlight();
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
@ -1,27 +1,31 @@
|
|||
( function ( mw, $ ) {
|
||||
( function ( mw ) {
|
||||
/**
|
||||
* A widget representing a single toggle filter
|
||||
*
|
||||
* @extends OO.ui.Widget
|
||||
* @extends OO.ui.MenuOptionWidget
|
||||
*
|
||||
* @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.FilterItemWidget = function MwRcfiltersUiFilterItemWidget( controller, model, config ) {
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, model, config ) {
|
||||
var layout,
|
||||
$label = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterItemWidget-label' );
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label' );
|
||||
|
||||
config = config || {};
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterItemWidget.parent.call( this, config );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.selected = false;
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, $.extend( {
|
||||
// Override the 'check' icon that OOUI defines
|
||||
icon: '',
|
||||
data: this.model.getName(),
|
||||
label: this.model.getLabel()
|
||||
}, config ) );
|
||||
|
||||
this.checkboxWidget = new mw.rcfilters.ui.CheckboxInputWidget( {
|
||||
value: this.model.getName(),
|
||||
|
|
@ -30,13 +34,13 @@
|
|||
|
||||
$label.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterItemWidget-label-title' )
|
||||
.text( this.model.getLabel() )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label-title' )
|
||||
.append( this.$label )
|
||||
);
|
||||
if ( this.model.getDescription() ) {
|
||||
$label.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterItemWidget-label-desc' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuOptionWidget-label-desc' )
|
||||
.text( this.model.getDescription() )
|
||||
);
|
||||
}
|
||||
|
|
@ -57,12 +61,11 @@
|
|||
} );
|
||||
|
||||
// Event
|
||||
this.checkboxWidget.connect( this, { userChange: 'onCheckboxChange' } );
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
this.model.getGroupModel().connect( this, { update: 'onGroupModelUpdate' } );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterItemWidget' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuOptionWidget' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-table' )
|
||||
|
|
@ -71,10 +74,10 @@
|
|||
.addClass( 'mw-rcfilters-ui-row' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterItemWidget-filterCheckbox' )
|
||||
.addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterMenuOptionWidget-filterCheckbox' )
|
||||
.append( layout.$element ),
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterItemWidget-highlightButton' )
|
||||
.addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-filterMenuOptionWidget-highlightButton' )
|
||||
.append( this.highlightButton.$element )
|
||||
)
|
||||
)
|
||||
|
|
@ -83,25 +86,19 @@
|
|||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterItemWidget, OO.ui.Widget );
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterMenuOptionWidget, OO.ui.MenuOptionWidget );
|
||||
|
||||
/* Static properties */
|
||||
|
||||
// We do our own scrolling to top
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.static.scrollIntoViewOnSelect = false;
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to checkbox change.
|
||||
* NOTE: This event is emitted both for deliberate user action and for
|
||||
* a change that the code requests ('setSelected')
|
||||
*
|
||||
* @param {boolean} isSelected The checkbox is selected
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemWidget.prototype.onCheckboxChange = function ( isSelected ) {
|
||||
this.controller.toggleFilterSelect( this.model.getName(), isSelected );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to item model update event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemWidget.prototype.onModelUpdate = function () {
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.prototype.onModelUpdate = function () {
|
||||
this.checkboxWidget.setSelected( this.model.isSelected() );
|
||||
|
||||
this.setCurrentMuteState();
|
||||
|
|
@ -110,31 +107,16 @@
|
|||
/**
|
||||
* Respond to item group model update event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemWidget.prototype.onGroupModelUpdate = function () {
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.prototype.onGroupModelUpdate = function () {
|
||||
this.setCurrentMuteState();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set selected state on this widget
|
||||
*
|
||||
* @param {boolean} [isSelected] Widget is selected
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemWidget.prototype.toggleSelected = function ( isSelected ) {
|
||||
isSelected = isSelected !== undefined ? isSelected : !this.selected;
|
||||
|
||||
if ( this.selected !== isSelected ) {
|
||||
this.selected = isSelected;
|
||||
|
||||
this.$element.toggleClass( 'mw-rcfilters-ui-filterItemWidget-selected', this.selected );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current mute state for this item
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemWidget.prototype.setCurrentMuteState = function () {
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.prototype.setCurrentMuteState = function () {
|
||||
this.$element.toggleClass(
|
||||
'mw-rcfilters-ui-filterItemWidget-muted',
|
||||
'mw-rcfilters-ui-filterMenuOptionWidget-muted',
|
||||
this.model.isConflicted() ||
|
||||
(
|
||||
// Item is also muted when any of the items in its group is active
|
||||
|
|
@ -154,7 +136,12 @@
|
|||
*
|
||||
* @return {string} Filter name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterItemWidget.prototype.getName = function () {
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.prototype.getName = function () {
|
||||
return this.model.getName();
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
||||
mw.rcfilters.ui.FilterMenuOptionWidget.prototype.getModel = function () {
|
||||
return this.model;
|
||||
};
|
||||
|
||||
}( mediaWiki ) );
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
( function ( mw ) {
|
||||
/**
|
||||
* A widget representing a menu section for filter groups
|
||||
*
|
||||
* @extends OO.ui.MenuSectionOptionWidget
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller RCFilters controller
|
||||
* @param {mw.rcfilters.dm.FilterGroup} model Filter group model
|
||||
* @param {Object} config Configuration object
|
||||
* @cfg {jQuery} [$overlay] Overlay
|
||||
*/
|
||||
mw.rcfilters.ui.FilterMenuSectionOptionWidget = function MwRcfiltersUiFilterMenuSectionOptionWidget( controller, model, config ) {
|
||||
var whatsThisMessages,
|
||||
$header = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-header' ),
|
||||
$popupContent = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content' );
|
||||
|
||||
config = config || {};
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterMenuSectionOptionWidget.parent.call( this, $.extend( {
|
||||
label: this.model.getTitle(),
|
||||
$label: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-header-title' )
|
||||
}, config ) );
|
||||
|
||||
$header.append( this.$label );
|
||||
|
||||
if ( this.model.hasWhatsThis() ) {
|
||||
whatsThisMessages = this.model.getWhatsThis();
|
||||
|
||||
// Create popup
|
||||
if ( whatsThisMessages.header ) {
|
||||
$popupContent.append(
|
||||
( new OO.ui.LabelWidget( {
|
||||
label: mw.msg( whatsThisMessages.header ),
|
||||
classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content-header' ]
|
||||
} ) ).$element
|
||||
);
|
||||
}
|
||||
if ( whatsThisMessages.body ) {
|
||||
$popupContent.append(
|
||||
( new OO.ui.LabelWidget( {
|
||||
label: mw.msg( whatsThisMessages.body ),
|
||||
classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content-body' ]
|
||||
} ) ).$element
|
||||
);
|
||||
}
|
||||
if ( whatsThisMessages.linkText && whatsThisMessages.url ) {
|
||||
$popupContent.append(
|
||||
( new OO.ui.ButtonWidget( {
|
||||
framed: false,
|
||||
flags: [ 'progressive' ],
|
||||
href: whatsThisMessages.url,
|
||||
label: mw.msg( whatsThisMessages.linkText ),
|
||||
classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup-content-link' ]
|
||||
} ) ).$element
|
||||
);
|
||||
}
|
||||
|
||||
// Add button
|
||||
this.whatsThisButton = new OO.ui.PopupButtonWidget( {
|
||||
framed: false,
|
||||
label: mw.msg( 'rcfilters-filterlist-whatsthis' ),
|
||||
$overlay: this.$overlay,
|
||||
classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton' ],
|
||||
flags: [ 'progressive' ],
|
||||
popup: {
|
||||
$autoCloseIgnore: this.$element.add( this.$overlay ),
|
||||
padded: false,
|
||||
align: 'center',
|
||||
position: 'above',
|
||||
$content: $popupContent,
|
||||
classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton-popup' ]
|
||||
}
|
||||
} );
|
||||
|
||||
$header
|
||||
.append( this.whatsThisButton.$element );
|
||||
}
|
||||
|
||||
// Events
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
|
||||
// Initialize
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget' )
|
||||
.addClass( 'mw-rcfilters-ui-filterMenuSectionOptionWidget-name-' + this.model.getName() )
|
||||
.append( $header );
|
||||
};
|
||||
|
||||
/* Initialize */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterMenuSectionOptionWidget, OO.ui.MenuSectionOptionWidget );
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to model update event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterMenuSectionOptionWidget.prototype.onModelUpdate = function () {
|
||||
this.$element.toggleClass(
|
||||
'mw-rcfilters-ui-filterMenuSectionOptionWidget-active',
|
||||
this.model.isActive()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the group name
|
||||
*
|
||||
* @return {string} Group name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterMenuSectionOptionWidget.prototype.getName = function () {
|
||||
return this.model.getName();
|
||||
};
|
||||
|
||||
}( mediaWiki ) );
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* Extend OOUI's CapsuleItemWidget to also display a popup on hover.
|
||||
* Extend OOUI's FilterTagItemWidget to also display a popup on hover.
|
||||
*
|
||||
* @class
|
||||
* @extends OO.ui.CapsuleItemWidget
|
||||
* @extends OO.ui.FilterTagItemWidget
|
||||
* @mixins OO.ui.mixin.PopupElement
|
||||
*
|
||||
* @constructor
|
||||
|
|
@ -12,24 +12,21 @@
|
|||
* @param {Object} config Configuration object
|
||||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget = function MwRcfiltersUiCapsuleItemWidget( controller, model, config ) {
|
||||
mw.rcfilters.ui.FilterTagItemWidget = function MwRcfiltersUiFilterTagItemWidget( controller, model, config ) {
|
||||
// Configuration initialization
|
||||
config = config || {};
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.popupLabel = new OO.ui.LabelWidget();
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
this.positioned = false;
|
||||
this.popupTimeoutShow = null;
|
||||
this.popupTimeoutHide = null;
|
||||
|
||||
// Parent constructor
|
||||
mw.rcfilters.ui.CapsuleItemWidget.parent.call( this, $.extend( {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, $.extend( {
|
||||
data: this.model.getName(),
|
||||
label: this.model.getLabel()
|
||||
}, config ) );
|
||||
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
this.popupLabel = new OO.ui.LabelWidget();
|
||||
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.PopupElement.call( this, $.extend( {
|
||||
popup: {
|
||||
|
|
@ -37,15 +34,19 @@
|
|||
align: 'center',
|
||||
position: 'above',
|
||||
$content: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-capsuleItemWidget-popup-content' )
|
||||
.addClass( 'mw-rcfilters-ui-filterTagItemWidget-popup-content' )
|
||||
.append( this.popupLabel.$element ),
|
||||
$floatableContainer: this.$element,
|
||||
classes: [ 'mw-rcfilters-ui-capsuleItemWidget-popup' ]
|
||||
classes: [ 'mw-rcfilters-ui-filterTagItemWidget-popup' ]
|
||||
}
|
||||
}, config ) );
|
||||
|
||||
this.positioned = false;
|
||||
this.popupTimeoutShow = null;
|
||||
this.popupTimeoutHide = null;
|
||||
|
||||
this.$highlight = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-capsuleItemWidget-highlight' );
|
||||
.addClass( 'mw-rcfilters-ui-filterTagItemWidget-highlight' );
|
||||
|
||||
// Events
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
|
|
@ -55,8 +56,6 @@
|
|||
this.$element
|
||||
.prepend( this.$highlight )
|
||||
.attr( 'aria-haspopup', 'true' )
|
||||
.addClass( 'mw-rcfilters-ui-capsuleItemWidget' )
|
||||
.on( 'mousedown', this.onMouseDown.bind( this ) )
|
||||
.on( 'mouseenter', this.onMouseEnter.bind( this ) )
|
||||
.on( 'mouseleave', this.onMouseLeave.bind( this ) );
|
||||
|
||||
|
|
@ -64,58 +63,29 @@
|
|||
this.setHighlightColor();
|
||||
};
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.CapsuleItemWidget, OO.ui.CapsuleItemWidget );
|
||||
OO.mixinClass( mw.rcfilters.ui.CapsuleItemWidget, OO.ui.mixin.PopupElement );
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterTagItemWidget, OO.ui.TagItemWidget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FilterTagItemWidget, OO.ui.mixin.PopupElement );
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to model update event
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onModelUpdate = function () {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.onModelUpdate = function () {
|
||||
this.setCurrentMuteState();
|
||||
|
||||
this.setHighlightColor();
|
||||
};
|
||||
|
||||
/**
|
||||
* Override mousedown event to prevent its propagation to the parent,
|
||||
* since the parent (the multiselect widget) focuses the popup when its
|
||||
* mousedown event is fired.
|
||||
*
|
||||
* @param {jQuery.Event} e Event
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseDown = function ( e ) {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit a click event when the capsule is clicked so we can aggregate this
|
||||
* in the parent (the capsule)
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onClick = function () {
|
||||
this.emit( 'click' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the event listening to the item close button click
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onCloseClick = function () {
|
||||
var element = this.getElementGroup();
|
||||
|
||||
if ( element && $.isFunction( element.removeItems ) ) {
|
||||
element.removeItems( [ this ] );
|
||||
}
|
||||
|
||||
// Respond to user removing the filter
|
||||
this.controller.clearFilter( this.model.getName() );
|
||||
};
|
||||
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.setHighlightColor = function () {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.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',
|
||||
'mw-rcfilters-ui-filterTagItemWidget-highlight-highlighted',
|
||||
!!selectedColor
|
||||
);
|
||||
};
|
||||
|
|
@ -123,16 +93,16 @@
|
|||
/**
|
||||
* Set the current mute state for this item
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.setCurrentMuteState = function () {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.setCurrentMuteState = function () {
|
||||
this.$element
|
||||
.toggleClass(
|
||||
'mw-rcfilters-ui-capsuleItemWidget-muted',
|
||||
'mw-rcfilters-ui-filterTagItemWidget-muted',
|
||||
!this.model.isSelected() ||
|
||||
this.model.isIncluded() ||
|
||||
this.model.isFullyCovered()
|
||||
)
|
||||
.toggleClass(
|
||||
'mw-rcfilters-ui-capsuleItemWidget-conflicted',
|
||||
'mw-rcfilters-ui-filterTagItemWidget-conflicted',
|
||||
this.model.isSelected() && this.model.isConflicted()
|
||||
);
|
||||
};
|
||||
|
|
@ -140,7 +110,7 @@
|
|||
/**
|
||||
* Respond to mouse enter event
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseEnter = function () {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.onMouseEnter = function () {
|
||||
var labelText = this.model.getStateMessage();
|
||||
|
||||
if ( labelText ) {
|
||||
|
|
@ -166,7 +136,7 @@
|
|||
/**
|
||||
* Respond to mouse leave event
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseLeave = function () {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.onMouseLeave = function () {
|
||||
this.popupTimeoutHide = setTimeout( function () {
|
||||
this.popup.toggle( false );
|
||||
}.bind( this ), 250 );
|
||||
|
|
@ -181,20 +151,29 @@
|
|||
*
|
||||
* @param {boolean} [isSelected] Widget is selected
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.toggleSelected = function ( isSelected ) {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.toggleSelected = function ( isSelected ) {
|
||||
isSelected = isSelected !== undefined ? isSelected : !this.selected;
|
||||
|
||||
if ( this.selected !== isSelected ) {
|
||||
this.selected = isSelected;
|
||||
|
||||
this.$element.toggleClass( 'mw-rcfilters-ui-capsuleItemWidget-selected', this.selected );
|
||||
this.$element.toggleClass( 'mw-rcfilters-ui-filterTagItemWidget-selected', this.selected );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get item name
|
||||
*
|
||||
* @return {string} Filter name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.getName = function () {
|
||||
return this.model.getName();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove and destroy external elements of this widget
|
||||
*/
|
||||
mw.rcfilters.ui.CapsuleItemWidget.prototype.destroy = function () {
|
||||
mw.rcfilters.ui.FilterTagItemWidget.prototype.destroy = function () {
|
||||
// Destroy the popup
|
||||
this.popup.$element.detach();
|
||||
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
( function ( mw ) {
|
||||
/**
|
||||
* List displaying all filter groups
|
||||
*
|
||||
* @extends OO.ui.Widget
|
||||
* @mixins OO.ui.mixin.PendingElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller Controller
|
||||
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
|
||||
* @param {Object} config Configuration object
|
||||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget = function MwRcfiltersUiFilterTagMultiselectWidget( controller, model, config ) {
|
||||
var title = new OO.ui.LabelWidget( {
|
||||
label: mw.msg( 'rcfilters-activefilters' ),
|
||||
classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper-content-title' ]
|
||||
} ),
|
||||
$contentWrapper = $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-wrapper' );
|
||||
|
||||
config = config || {};
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.call( this, $.extend( true, {
|
||||
label: mw.msg( 'rcfilters-filterlist-title' ),
|
||||
placeholder: mw.msg( 'rcfilters-empty-filter' ),
|
||||
inputPosition: 'outline',
|
||||
allowArbitrary: false,
|
||||
allowDisplayInvalidTags: false,
|
||||
allowReordering: false,
|
||||
$overlay: this.$overlay,
|
||||
menu: {
|
||||
hideWhenOutOfView: false,
|
||||
hideOnChoose: false,
|
||||
width: 650,
|
||||
$footer: $( '<div>' )
|
||||
.append(
|
||||
new OO.ui.ButtonWidget( {
|
||||
framed: false,
|
||||
icon: 'feedback',
|
||||
flags: [ 'progressive' ],
|
||||
label: mw.msg( 'rcfilters-filterlist-feedbacklink' ),
|
||||
href: 'https://www.mediawiki.org/wiki/Help_talk:New_filters_for_edit_review'
|
||||
} ).$element
|
||||
)
|
||||
},
|
||||
input: {
|
||||
icon: 'search',
|
||||
placeholder: mw.msg( 'rcfilters-search-placeholder' )
|
||||
}
|
||||
}, config ) );
|
||||
|
||||
this.resetButton = new OO.ui.ButtonWidget( {
|
||||
framed: false,
|
||||
classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-resetButton' ]
|
||||
} );
|
||||
|
||||
this.emptyFilterMessage = new OO.ui.LabelWidget( {
|
||||
label: mw.msg( 'rcfilters-empty-filter' ),
|
||||
classes: [ 'mw-rcfilters-ui-filterTagMultiselectWidget-emptyFilters' ]
|
||||
} );
|
||||
this.$content.append( this.emptyFilterMessage.$element );
|
||||
|
||||
// Events
|
||||
this.resetButton.connect( this, { click: 'onResetButtonClick' } );
|
||||
// Stop propagation for mousedown, so that the widget doesn't
|
||||
// trigger the focus on the input and scrolls up when we click the reset button
|
||||
this.resetButton.$element.on( 'mousedown', function ( e ) { e.stopPropagation(); } );
|
||||
this.model.connect( this, {
|
||||
initialize: 'onModelInitialize',
|
||||
itemUpdate: 'onModelItemUpdate',
|
||||
highlightChange: 'onModelHighlightChange'
|
||||
} );
|
||||
this.menu.connect( this, { toggle: 'onMenuToggle' } );
|
||||
|
||||
// Build the content
|
||||
$contentWrapper.append(
|
||||
title.$element,
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-table' )
|
||||
.append(
|
||||
// The filter list and button should appear side by side regardless of how
|
||||
// wide the button is; the button also changes its width depending
|
||||
// on language and its state, so the safest way to present both side
|
||||
// by side is with a table layout
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-row' )
|
||||
.append(
|
||||
this.$content
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-cell-filters' ),
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-cell-reset' )
|
||||
.append( this.resetButton.$element )
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Initialize
|
||||
this.$handle.append( $contentWrapper );
|
||||
this.emptyFilterMessage.toggle( this.isEmpty() );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget' );
|
||||
|
||||
this.populateFromModel();
|
||||
this.reevaluateResetRestoreState();
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterTagMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to menu toggle
|
||||
*
|
||||
* @param {boolean} isVisible Menu is visible
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onMenuToggle = function ( isVisible ) {
|
||||
if ( isVisible ) {
|
||||
mw.hook( 'RcFilters.popup.open' ).fire( this.getMenu().getSelectedItem() );
|
||||
|
||||
if ( !this.getMenu().getSelectedItem() ) {
|
||||
// If there are no selected items, scroll menu to top
|
||||
// This has to be in a setTimeout so the menu has time
|
||||
// to be positioned and fixed
|
||||
setTimeout( function () { this.getMenu().scrollToTop(); }.bind( this ), 0 );
|
||||
}
|
||||
} else {
|
||||
// Clear selection
|
||||
this.getMenu().selectItem( null );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onInputFocus = function () {
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onInputFocus.call( this );
|
||||
|
||||
// Scroll to top
|
||||
this.scrollToTop( this.$element );
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheridoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onChangeTags = function () {
|
||||
// Parent method
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onChangeTags.call( this );
|
||||
|
||||
this.emptyFilterMessage.toggle( this.isEmpty() );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to model initialize event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelInitialize = function () {
|
||||
this.populateFromModel();
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to model itemUpdate event
|
||||
*
|
||||
* @param {mw.rcfilters.dm.FilterItem} item Filter item model
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
|
||||
if (
|
||||
item.isSelected() ||
|
||||
(
|
||||
this.model.isHighlightEnabled() &&
|
||||
item.isHighlightSupported() &&
|
||||
item.getHighlightColor()
|
||||
)
|
||||
) {
|
||||
this.addTag( item.getName(), item.getLabel() );
|
||||
} else {
|
||||
this.removeTagByData( item.getName() );
|
||||
}
|
||||
|
||||
// Re-evaluate reset state
|
||||
this.reevaluateResetRestoreState();
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.isAllowedData = function ( data ) {
|
||||
return (
|
||||
this.menu.getItemFromData( data ) &&
|
||||
!this.isDuplicateData( data )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onMenuChoose = function ( item ) {
|
||||
this.controller.toggleFilterSelect( item.model.getName() );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to highlightChange event
|
||||
*
|
||||
* @param {boolean} isHighlightEnabled Highlight is enabled
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelHighlightChange = function ( isHighlightEnabled ) {
|
||||
var highlightedItems = this.model.getHighlightedItems();
|
||||
|
||||
if ( isHighlightEnabled ) {
|
||||
// Add capsule widgets
|
||||
highlightedItems.forEach( function ( filterItem ) {
|
||||
this.addTag( filterItem.getName(), filterItem.getLabel() );
|
||||
}.bind( this ) );
|
||||
} else {
|
||||
// Remove capsule widgets if they're not selected
|
||||
highlightedItems.forEach( function ( filterItem ) {
|
||||
if ( !filterItem.isSelected() ) {
|
||||
this.removeTagByData( filterItem.getName() );
|
||||
}
|
||||
}.bind( this ) );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onTagSelect = function ( tagItem ) {
|
||||
var widget = this,
|
||||
menuOption = this.menu.getItemFromData( tagItem.getData() );
|
||||
|
||||
// Reset input
|
||||
this.input.setValue( '' );
|
||||
|
||||
// Parent method
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onTagSelect.call( this, tagItem );
|
||||
|
||||
this.menu.selectItem( menuOption );
|
||||
|
||||
// Scroll to the item
|
||||
// We're binding a 'once' to the itemVisibilityChange event
|
||||
// so this happens when the menu is ready after the items
|
||||
// are visible again, in case this is done right after the
|
||||
// user filtered the results
|
||||
this.getMenu().once(
|
||||
'itemVisibilityChange',
|
||||
function () { widget.scrollToTop( menuOption.$element ); }
|
||||
);
|
||||
};
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onTagRemove = function ( tagItem ) {
|
||||
// Parent method
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onTagRemove.call( this, tagItem );
|
||||
|
||||
this.controller.clearFilter( tagItem.getName() );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to click event on the reset button
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onResetButtonClick = function () {
|
||||
if ( this.model.areCurrentFiltersEmpty() ) {
|
||||
// Reset to default filters
|
||||
this.controller.resetToDefaults();
|
||||
} else {
|
||||
// Reset to have no filters
|
||||
this.controller.emptyFilters();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reevaluate the restore state for the widget between setting to defaults and clearing all filters
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.reevaluateResetRestoreState = function () {
|
||||
var defaultsAreEmpty = this.model.areDefaultFiltersEmpty(),
|
||||
currFiltersAreEmpty = this.model.areCurrentFiltersEmpty(),
|
||||
hideResetButton = currFiltersAreEmpty && defaultsAreEmpty;
|
||||
|
||||
this.resetButton.setIcon(
|
||||
currFiltersAreEmpty ? 'history' : 'trash'
|
||||
);
|
||||
|
||||
this.resetButton.setLabel(
|
||||
currFiltersAreEmpty ? mw.msg( 'rcfilters-restore-default-filters' ) : ''
|
||||
);
|
||||
this.resetButton.setTitle(
|
||||
currFiltersAreEmpty ? null : mw.msg( 'rcfilters-clear-all-filters' )
|
||||
);
|
||||
|
||||
this.resetButton.toggle( !hideResetButton );
|
||||
this.emptyFilterMessage.toggle( currFiltersAreEmpty );
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createMenuWidget = function ( menuConfig ) {
|
||||
return new mw.rcfilters.ui.FilterFloatingMenuSelectWidget(
|
||||
this.controller,
|
||||
this.model,
|
||||
$.extend( {
|
||||
filterFromInput: true
|
||||
}, menuConfig )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate the menu from the model
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.populateFromModel = function () {
|
||||
var widget = this,
|
||||
items = [];
|
||||
|
||||
// Reset
|
||||
this.getMenu().clearItems();
|
||||
|
||||
$.each( this.model.getFilterGroups(), function ( groupName, groupModel ) {
|
||||
items.push(
|
||||
// Group section
|
||||
new mw.rcfilters.ui.FilterMenuSectionOptionWidget(
|
||||
widget.controller,
|
||||
groupModel,
|
||||
{
|
||||
$overlay: widget.$overlay
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Add items
|
||||
widget.model.getGroupFilters( groupName ).forEach( function ( filterItem ) {
|
||||
items.push(
|
||||
new mw.rcfilters.ui.FilterMenuOptionWidget(
|
||||
widget.controller,
|
||||
filterItem,
|
||||
{
|
||||
$overlay: widget.$overlay
|
||||
}
|
||||
)
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
// Add all items to the menu
|
||||
this.getMenu().addItems( items );
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createTagItemWidget = function ( data ) {
|
||||
var filterItem = this.model.getItemByName( data );
|
||||
|
||||
if ( filterItem ) {
|
||||
return new mw.rcfilters.ui.FilterTagItemWidget(
|
||||
this.controller,
|
||||
filterItem,
|
||||
{
|
||||
$overlay: this.$overlay
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the element to top within its container
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $element Element to position
|
||||
* @param {number} [marginFromTop] When scrolling the entire widget to the top, leave this
|
||||
* much space (in pixels) above the widget.
|
||||
*/
|
||||
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.scrollToTop = function ( $element, marginFromTop ) {
|
||||
var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ),
|
||||
pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) ),
|
||||
containerScrollTop = $( container ).is( 'body, html' ) ? 0 : $( container ).scrollTop();
|
||||
|
||||
// Scroll to item
|
||||
$( container ).animate( {
|
||||
scrollTop: containerScrollTop + pos.top - ( marginFromTop || 0 )
|
||||
} );
|
||||
};
|
||||
}( mediaWiki ) );
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( controller, model, config ) {
|
||||
var $footer = $( '<div>' );
|
||||
config = config || {};
|
||||
|
||||
// Parent
|
||||
|
|
@ -25,188 +24,20 @@
|
|||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
|
||||
this.filterPopup = new mw.rcfilters.ui.FiltersListWidget(
|
||||
this.filterTagWidget = new mw.rcfilters.ui.FilterTagMultiselectWidget(
|
||||
this.controller,
|
||||
this.model,
|
||||
{
|
||||
label: mw.msg( 'rcfilters-filterlist-title' ),
|
||||
$overlay: this.$overlay
|
||||
}
|
||||
{ $overlay: this.$overlay }
|
||||
);
|
||||
|
||||
$footer.append(
|
||||
new OO.ui.ButtonWidget( {
|
||||
framed: false,
|
||||
icon: 'feedback',
|
||||
flags: [ 'progressive' ],
|
||||
label: mw.msg( 'rcfilters-filterlist-feedbacklink' ),
|
||||
href: 'https://www.mediawiki.org/wiki/Help_talk:New_filters_for_edit_review'
|
||||
} ).$element
|
||||
);
|
||||
|
||||
this.textInput = new OO.ui.TextInputWidget( {
|
||||
classes: [ 'mw-rcfilters-ui-filterWrapperWidget-search' ],
|
||||
icon: 'search',
|
||||
placeholder: mw.msg( 'rcfilters-search-placeholder' )
|
||||
} );
|
||||
|
||||
this.capsule = new mw.rcfilters.ui.FilterCapsuleMultiselectWidget( controller, this.model, this.textInput, {
|
||||
$overlay: this.$overlay,
|
||||
popup: {
|
||||
$content: this.filterPopup.$element,
|
||||
$footer: $footer,
|
||||
classes: [ 'mw-rcfilters-ui-filterWrapperWidget-popup' ],
|
||||
width: 650,
|
||||
hideWhenOutOfView: false
|
||||
}
|
||||
} );
|
||||
|
||||
// Events
|
||||
this.model.connect( this, {
|
||||
initialize: 'onModelInitialize',
|
||||
itemUpdate: 'onModelItemUpdate'
|
||||
} );
|
||||
this.textInput.connect( this, {
|
||||
change: 'onTextInputChange',
|
||||
enter: 'onTextInputEnter'
|
||||
} );
|
||||
this.capsule.connect( this, { capsuleItemClick: 'onCapsuleItemClick' } );
|
||||
this.capsule.popup.connect( this, {
|
||||
toggle: 'onCapsulePopupToggle',
|
||||
ready: 'onCapsulePopupReady'
|
||||
} );
|
||||
|
||||
// Initialize
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterWrapperWidget' )
|
||||
.append( this.capsule.$element, this.textInput.$element );
|
||||
.append( this.filterTagWidget.$element );
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.mixin.PendingElement );
|
||||
|
||||
/**
|
||||
* Respond to capsule item click and make the popup scroll down to the requested item
|
||||
*
|
||||
* @param {mw.rcfilters.ui.CapsuleItemWidget} item Clicked item
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsuleItemClick = function ( item ) {
|
||||
var filterName = item.getData(),
|
||||
// Find the item in the popup
|
||||
filterWidget = this.filterPopup.getItemWidget( filterName );
|
||||
|
||||
// Highlight item
|
||||
this.filterPopup.select( filterName );
|
||||
this.capsule.select( item );
|
||||
|
||||
this.capsule.popup.toggle( true );
|
||||
this.scrollToTop( filterWidget.$element );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to capsule popup ready event, fired after the popup is visible, positioned and clipped
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsulePopupReady = function () {
|
||||
mw.hook( 'RcFilters.popup.open' ).fire( this.filterPopup.getSelectedFilter() );
|
||||
|
||||
this.scrollToTop( this.capsule.$element, 10 );
|
||||
if ( !this.filterPopup.getSelectedFilter() ) {
|
||||
// No selection, scroll the popup list to top
|
||||
setTimeout( function () { this.capsule.popup.$body.scrollTop( 0 ); }.bind( this ), 0 );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to popup toggle event. Reset selection in the list when the popup is closed.
|
||||
*
|
||||
* @param {boolean} isVisible Popup is visible
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsulePopupToggle = function ( isVisible ) {
|
||||
if ( !isVisible && !this.textInput.getValue() ) {
|
||||
// Only reset selection if we are not filtering
|
||||
this.filterPopup.resetSelection();
|
||||
this.capsule.resetSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to text input change
|
||||
*
|
||||
* @param {string} newValue Current value
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onTextInputChange = function ( newValue ) {
|
||||
// Filter the results
|
||||
this.filterPopup.filter( this.model.findMatches( newValue ) );
|
||||
|
||||
if ( !newValue ) {
|
||||
// If the value is empty, we didn't actually
|
||||
// filter anything. the filter method will run
|
||||
// and show all, but then will select the
|
||||
// top item - but in this case, no selection
|
||||
// should be made.
|
||||
this.filterPopup.resetSelection();
|
||||
}
|
||||
this.capsule.popup.clip();
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to text input enter event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onTextInputEnter = function () {
|
||||
var filter = this.filterPopup.getSelectedFilter();
|
||||
|
||||
// Toggle the filter
|
||||
if ( filter ) {
|
||||
this.controller.toggleFilterSelect( filter );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to model update event and set up the available filters to choose
|
||||
* from.
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelInitialize = function () {
|
||||
var wrapper = this;
|
||||
|
||||
// Add defaults to capsule. We have to do this
|
||||
// after we added to the capsule menu, since that's
|
||||
// how the capsule multiselect widget knows which
|
||||
// object to add
|
||||
this.model.getItems().forEach( function ( filterItem ) {
|
||||
if ( filterItem.isSelected() ) {
|
||||
wrapper.capsule.addItemByName( filterItem.getName() );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to item update and reset the selection. This will make it so that
|
||||
* any actual interaction with the system resets the selection state of any item.
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelItemUpdate = function () {
|
||||
if ( !this.textInput.getValue() ) {
|
||||
this.filterPopup.resetSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the element to top within its container
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $element Element to position
|
||||
* @param {number} [marginFromTop] When scrolling the entire widget to the top, leave this
|
||||
* much space (in pixels) above the widget.
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.scrollToTop = function ( $element, marginFromTop ) {
|
||||
var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ),
|
||||
pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) ),
|
||||
containerScrollTop = $( container ).is( 'body, html' ) ? 0 : $( container ).scrollTop();
|
||||
|
||||
// Scroll to item
|
||||
$( container ).animate( {
|
||||
scrollTop: containerScrollTop + pos.top - ( marginFromTop || 0 )
|
||||
} );
|
||||
};
|
||||
}( mediaWiki ) );
|
||||
|
|
|
|||
|
|
@ -1,256 +0,0 @@
|
|||
( function ( mw, $ ) {
|
||||
/**
|
||||
* List displaying all filter groups
|
||||
*
|
||||
* @extends OO.ui.Widget
|
||||
* @mixins OO.ui.mixin.GroupWidget
|
||||
* @mixins OO.ui.mixin.LabelElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.rcfilters.Controller} controller Controller
|
||||
* @param {mw.rcfilters.dm.FiltersViewModel} model View model
|
||||
* @param {Object} config Configuration object
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget = function MwRcfiltersUiFiltersListWidget( controller, model, config ) {
|
||||
config = config || {};
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FiltersListWidget.parent.call( this, config );
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.GroupWidget.call( this, config );
|
||||
OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, {
|
||||
$label: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filtersListWidget-title' )
|
||||
} ) );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
this.$overlay = config.$overlay || this.$element;
|
||||
this.groups = {};
|
||||
this.selected = null;
|
||||
|
||||
this.highlightButton = new OO.ui.ToggleButtonWidget( {
|
||||
icon: 'highlight',
|
||||
label: mw.message( 'rcfilters-highlightbutton-title' ).text(),
|
||||
classes: [ 'mw-rcfilters-ui-filtersListWidget-hightlightButton' ]
|
||||
} );
|
||||
|
||||
this.noResultsLabel = new OO.ui.LabelWidget( {
|
||||
label: mw.msg( 'rcfilters-filterlist-noresults' ),
|
||||
classes: [ 'mw-rcfilters-ui-filtersListWidget-noresults' ]
|
||||
} );
|
||||
|
||||
// Events
|
||||
this.highlightButton.connect( this, { click: 'onHighlightButtonClick' } );
|
||||
this.model.connect( this, {
|
||||
initialize: 'onModelInitialize',
|
||||
highlightChange: 'onModelHighlightChange'
|
||||
} );
|
||||
|
||||
// Initialize
|
||||
this.showNoResultsMessage( false );
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filtersListWidget' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-table' )
|
||||
.addClass( 'mw-rcfilters-ui-filtersListWidget-header' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-row' )
|
||||
.append(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filtersListWidget-header-title' )
|
||||
.append( this.$label ),
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-cell' )
|
||||
.addClass( 'mw-rcfilters-ui-filtersListWidget-header-highlight' )
|
||||
.append( this.highlightButton.$element )
|
||||
)
|
||||
),
|
||||
// this.$label,
|
||||
this.$group
|
||||
.addClass( 'mw-rcfilters-ui-filtersListWidget-group' ),
|
||||
this.noResultsLabel.$element
|
||||
);
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
|
||||
OO.inheritClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.mixin.GroupWidget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FiltersListWidget, OO.ui.mixin.LabelElement );
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to initialize event from the model
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.onModelInitialize = function () {
|
||||
var widget = this;
|
||||
|
||||
// Reset
|
||||
this.clearItems();
|
||||
this.groups = {};
|
||||
|
||||
this.addItems(
|
||||
Object.keys( this.model.getFilterGroups() ).map( function ( groupName ) {
|
||||
var groupWidget = new mw.rcfilters.ui.FilterGroupWidget(
|
||||
widget.controller,
|
||||
widget.model.getGroup( groupName ),
|
||||
{
|
||||
$overlay: widget.$overlay
|
||||
}
|
||||
);
|
||||
|
||||
widget.groups[ groupName ] = groupWidget;
|
||||
return groupWidget;
|
||||
} )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to model highlight change event
|
||||
*
|
||||
* @param {boolean} highlightEnabled Highlight is enabled
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.onModelHighlightChange = function ( highlightEnabled ) {
|
||||
this.highlightButton.setActive( highlightEnabled );
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to highlight button click
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.onHighlightButtonClick = function () {
|
||||
this.controller.toggleHighlight();
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the filter item widget that corresponds to the item name
|
||||
*
|
||||
* @param {string} itemName Filter name
|
||||
* @return {mw.rcfilters.ui.FilterItemWidget} Filter widget
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.getItemWidget = function ( itemName ) {
|
||||
var filterItem = this.model.getItemByName( itemName ),
|
||||
// Find the group
|
||||
groupWidget = this.groups[ filterItem.getGroupName() ];
|
||||
|
||||
// Find the item inside the group
|
||||
return groupWidget.getItemWidget( itemName );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current selection
|
||||
*
|
||||
* @return {string|null} Selected filter. Null if none is selected.
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.getSelectedFilter = function () {
|
||||
return this.selected;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark an item widget as selected
|
||||
*
|
||||
* @param {string} itemName Filter name
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.select = function ( itemName ) {
|
||||
var filterWidget;
|
||||
|
||||
if ( this.selected !== itemName ) {
|
||||
// Unselect previous
|
||||
if ( this.selected ) {
|
||||
filterWidget = this.getItemWidget( this.selected );
|
||||
filterWidget.toggleSelected( false );
|
||||
}
|
||||
|
||||
// Select new one
|
||||
this.selected = itemName;
|
||||
if ( this.selected ) {
|
||||
filterWidget = this.getItemWidget( this.selected );
|
||||
filterWidget.toggleSelected( true );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset selection and remove selected states from all items
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.resetSelection = function () {
|
||||
if ( this.selected !== null ) {
|
||||
this.selected = null;
|
||||
this.getItems().forEach( function ( groupWidget ) {
|
||||
groupWidget.getItems().forEach( function ( filterItemWidget ) {
|
||||
filterItemWidget.toggleSelected( false );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch between showing the 'no results' message for filtering results or the result list.
|
||||
*
|
||||
* @param {boolean} showNoResults Show no results message
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.showNoResultsMessage = function ( showNoResults ) {
|
||||
this.noResultsLabel.toggle( !!showNoResults );
|
||||
this.$group.toggleClass( 'oo-ui-element-hidden', !!showNoResults );
|
||||
};
|
||||
|
||||
/**
|
||||
* Show only the items matching with the models in the given list
|
||||
*
|
||||
* @param {Object} groupItems An object of items to show
|
||||
* arranged by their group names
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.filter = function ( groupItems ) {
|
||||
var i, j, groupName, itemWidgets, topItem, isVisible,
|
||||
groupWidgets = this.getItems(),
|
||||
hasItemWithName = function ( itemArr, name ) {
|
||||
return !!itemArr.filter( function ( item ) {
|
||||
return item.getName() === name;
|
||||
} ).length;
|
||||
};
|
||||
|
||||
this.resetSelection();
|
||||
|
||||
if ( $.isEmptyObject( groupItems ) ) {
|
||||
// No results. Hide everything, show only 'no results'
|
||||
// message
|
||||
this.showNoResultsMessage( true );
|
||||
return;
|
||||
}
|
||||
|
||||
this.showNoResultsMessage( false );
|
||||
for ( i = 0; i < groupWidgets.length; i++ ) {
|
||||
groupName = groupWidgets[ i ].getName();
|
||||
|
||||
// If this group widget is in the filtered results,
|
||||
// show it - otherwise, hide it
|
||||
groupWidgets[ i ].toggle( !!groupItems[ groupName ] );
|
||||
|
||||
if ( !groupItems[ groupName ] ) {
|
||||
// Continue to next group
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have items to show
|
||||
itemWidgets = groupWidgets[ i ].getItems();
|
||||
for ( j = 0; j < itemWidgets.length; j++ ) {
|
||||
isVisible = hasItemWithName( groupItems[ groupName ], itemWidgets[ j ].getName() );
|
||||
// Only show items that are in the filtered list
|
||||
itemWidgets[ j ].toggle( isVisible );
|
||||
|
||||
if ( !topItem && isVisible ) {
|
||||
topItem = itemWidgets[ j ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select the first item
|
||||
if ( topItem ) {
|
||||
this.select( topItem.getName() );
|
||||
}
|
||||
};
|
||||
}( mediaWiki, jQuery ) );
|
||||
Loading…
Reference in a new issue