'ChangesListSpecialPageStructuredFilters': Called to allow extensions to register
filters for pages inheriting from ChangesListSpecialPage (in core: RecentChanges,
RecentChangesLinked, and Watchlist). Generally, you will want to construct
-new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects. You can
-then either add them to existing ChangesListFilterGroup objects (accessed through
-$special->getFilterGroup), or create your own. If you create new groups, you
-must register them with $special->registerFilterGroup.
+new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects.
+
+When constructing them, you specify which group they belong to. You can reuse
+existing groups (accessed through $special->getFilterGroup), or create your own.
+If you create new groups, you must register them with $special->registerFilterGroup.
$special: ChangesListSpecialPage instance
'ChangeTagAfterDelete': Called after a change tag has been deleted (that is,
const RESERVED_NAME_CHAR = '_';
/**
- * Create a new filter with the specified configuration.
+ * Creates a new filter with the specified configuration, and registers it to the
+ * specified group.
*
* It infers which UI (it can be either or both) to display the filter on based on
* which messages are provided.
);
}
+ if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
+ throw new MWException( 'Two filters in a group cannot have the ' .
+ "same name: '{$filterDefinition['name']}'" );
+ }
+
$this->name = $filterDefinition['name'];
if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
* Get filter by name
*
* @param string $name Filter name
- * @return ChangesListFilter Specified filter
+ * @return ChangesListFilter|null Specified filter, or null if it is not registered
*/
public function getFilter( $name ) {
- return $this->filters[$name];
+ return isset( $this->filters[$name] ) ? $this->filters[$name] : null;
}
/**
*
* @param string $groupName Name of group
*
- * @return ChangesListFilterGroup
+ * @return ChangesListFilterGroup|null Group, or null if not registered
*/
public function getFilterGroup( $groupName ) {
- return $this->filterGroups[$groupName];
+ return isset( $this->filterGroups[$groupName] ) ?
+ $this->filterGroups[$groupName] :
+ null;
}
// Currently, this intentionally only includes filters that display
.mw-rcfilters-ui-capsuleItemWidget {
background-color: #fff;
border-color: #979797;
+ margin: 0 0.6em 0 0;
color: #222;
// Background and color of the capsule widget need a bit
&.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
background-color: #f8f9fa;
- border: 1px solid #a2a9b1;
- min-height: 5.5em;
- padding: 0.75em;
+ border-radius: 2px 2px 0 0;
+ padding: 0.3em 0.6em 0.6em 0.6em;
+ margin-top: 1.6em;
+ }
+ .mw-rcfilters-ui-table {
+ margin-top: 0.3em;
}
- &-content-title {
+ &-wrapper-content-title {
font-weight: bold;
color: #54595d;
}
&-search {
max-width: none;
- margin-top: -0.5em;
+ margin-top: -1px;
input {
// We need to reiterate the directionality
.mw-rcfilters-ui-filtersListWidget {
&-title {
font-size: 1.2em;
- padding: 0.75em;
+ padding: 0.75em 0.5em;
// TODO: Unify colors with official design palette
color: #54595d;
}
&-highlight {
width: 1em;
vertical-align: middle;
+ // Using the same padding that the filter item
+ // uses, so the button is aligned with the highlight
+ // buttons for the filters
+ padding-right: 0.5em;
}
&-title {
// Replace the entire fieldset
this.$element.empty().append( $fieldset.contents() );
+ // Make sure enhanced RC re-initializes correctly
+ mw.hook( 'wikipage.content' ).fire( this.$element );
this.cleanUpFieldset();
* Clean up the old-style show/hide that we have implemented in the filter list
*/
mw.rcfilters.ui.FormWrapperWidget.prototype.cleanUpFieldset = function () {
+ var $namespaceSelect = this.$element.find( '#namespace' ),
+ $namespaceCheckboxes = this.$element.find( '#nsassociated, #nsinvert' ),
+ collapseCookieName = 'changeslist-state';
+
this.$element.find( '.rcshowhideoption[data-feature-in-structured-ui=1]' ).each( function () {
// HACK: Remove the text node after the span.
// If there isn't one, we're at the end, so remove the text node before the span.
// Remove the span itself
this.parentNode.removeChild( this );
} );
+
+ // Bind namespace select to change event
+ // see resources/src/mediawiki.special/mediawiki.special.recentchanges.js
+ $namespaceCheckboxes.prop( 'disabled', $namespaceSelect.val() === '' );
+ $namespaceSelect.on( 'change', function () {
+ $namespaceCheckboxes.prop( 'disabled', $( this ).val() === '' );
+ } );
+
+ // Collapse legend
+ // see resources/src/mediawiki.special/mediawiki.special.changelist.legend.js
+ this.$element.find( '.mw-changeslist-legend' )
+ .makeCollapsible( {
+ collapsed: mw.cookie.get( collapseCookieName ) === 'collapsed'
+ } )
+ .on( 'beforeExpand.mw-collapsible', function () {
+ mw.cookie.set( collapseCookieName, 'expanded' );
+ } )
+ .on( 'beforeCollapse.mw-collapsible', function () {
+ mw.cookie.set( collapseCookieName, 'collapsed' );
+ } );
+
};
}( mediaWiki ) );
)
);
}
+
+ // Get without warnings
+ public function testGetFilter() {
+ $group = new MockChangesListFilterGroup(
+ [
+ 'type' => 'some_type',
+ 'name' => 'groupName',
+ 'isFullCoverage' => true,
+ 'priority' => 1,
+ 'filters' => [
+ [ 'name' => 'foo' ],
+ ],
+ ]
+ );
+
+ $this->assertEquals(
+ 'foo',
+ $group->getFilter( 'foo' )->getName()
+ );
+
+ $this->assertEquals(
+ null,
+ $group->getFilter( 'bar' )
+ );
+ }
}
);
}
+ // @codingStandardsIgnoreStart
+ /**
+ * @expectedException MWException
+ * @expectedExceptionMessage Two filters in a group cannot have the same name: 'somename'
+ */
+ // @codingStandardsIgnoreEnd
+ public function testDuplicateName() {
+ new MockChangesListFilter(
+ [
+ 'group' => $this->group,
+ 'name' => 'somename',
+ 'priority' => 1,
+ ]
+ );
+
+ new MockChangesListFilter(
+ [
+ 'group' => $this->group,
+ 'name' => 'somename',
+ 'priority' => 2,
+ ]
+ );
+ }
+
/**
* @expectedException MWException
* @expectedExceptionMessage Supersets can only be defined for filters in the same group