Make Special:Contributions use OOUI

Changes:
* IP address/username is now a single label & input element combination
* Add page-specific styles in separate LESS file
* Remove no longer necessary CSS rule

Bug: T117736
Bug: T219238
Change-Id: I979078d8937898acae22bc28d5ed51da1d4ed627
This commit is contained in:
jdlrobson 2016-04-02 11:03:42 +03:00 committed by Jdlrobson
parent 97eab7f35b
commit 2bb8515286
7 changed files with 241 additions and 248 deletions

View file

@ -126,6 +126,9 @@ $wgPasswordPolicy['policies']['default']['PasswordNotInLargeBlacklist'] = false;
* (T222388) API modules can now be specified as an ObjectFactory spec,
allowing the construction of modules that require services to be injected
in their constructor.
* (T117736) The function signature of SpecialContributions::getForm::filters
has changed. It now expects definitions of additional filter fields as array
rather than string.
=== External library changes in 1.34 ===

View file

@ -3177,7 +3177,7 @@ $row: Revision information from the database
'SpecialContributions::getForm::filters': Called with a list of filters to render
on Special:Contributions.
$sp: SpecialContributions object, for context
&$filters: List of filters rendered as HTML
&$filters: List of filter object definitions (compatible with OOUI form)
'SpecialListusersDefaultQuery': Called right before the end of
UsersPager::getDefaultQuery().

View file

@ -23,7 +23,6 @@
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\Widget\DateInputWidget;
/**
* Special:Contributions, show user contributions in a paged list
@ -114,14 +113,23 @@ class SpecialContributions extends IncludableSpecialPage {
}
$ns = $request->getVal( 'namespace', null );
if ( $ns !== null && $ns !== '' ) {
if ( $ns !== null && $ns !== '' && $ns !== 'all' ) {
$this->opts['namespace'] = intval( $ns );
} else {
$this->opts['namespace'] = '';
}
// Backwards compatibility: Before using OOUI form the old HTML form had
// fields for nsInvert and associated. These have now been replaced with the
// wpFilters query string parameters. These are retained to keep old URIs working.
$this->opts['associated'] = $request->getBool( 'associated' );
$this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' );
$nsFilters = $request->getArray( 'wpfilters', null );
if ( $nsFilters !== null ) {
$this->opts['associated'] = in_array( 'associated', $nsFilters );
$this->opts['nsInvert'] = in_array( 'nsInvert', $nsFilters );
}
$this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' );
// Allows reverts to have the bot flag in recent changes. It is just here to
@ -462,48 +470,6 @@ class SpecialContributions extends IncludableSpecialPage {
*/
protected function getForm() {
$this->opts['title'] = $this->getPageTitle()->getPrefixedText();
if ( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
$this->opts['target'] = str_replace( '_', ' ', $this->opts['target'] );
}
if ( !isset( $this->opts['namespace'] ) ) {
$this->opts['namespace'] = '';
}
if ( !isset( $this->opts['nsInvert'] ) ) {
$this->opts['nsInvert'] = '';
}
if ( !isset( $this->opts['associated'] ) ) {
$this->opts['associated'] = false;
}
if ( !isset( $this->opts['start'] ) ) {
$this->opts['start'] = '';
}
if ( !isset( $this->opts['end'] ) ) {
$this->opts['end'] = '';
}
if ( !isset( $this->opts['tagfilter'] ) ) {
$this->opts['tagfilter'] = '';
}
if ( !isset( $this->opts['topOnly'] ) ) {
$this->opts['topOnly'] = false;
}
if ( !isset( $this->opts['newOnly'] ) ) {
$this->opts['newOnly'] = false;
}
if ( !isset( $this->opts['hideMinor'] ) ) {
$this->opts['hideMinor'] = false;
}
// Modules required only for the form
$this->getOutput()->addModules( [
'mediawiki.userSuggest',
@ -511,15 +477,7 @@ class SpecialContributions extends IncludableSpecialPage {
] );
$this->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
$this->getOutput()->enableOOUI();
$form = Html::openElement(
'form',
[
'method' => 'get',
'action' => wfScript(),
'class' => 'mw-contributions-form'
]
);
$fields = [];
# Add hidden params for tracking except for parameters in $skipParameters
$skipParameters = [
@ -527,7 +485,6 @@ class SpecialContributions extends IncludableSpecialPage {
'nsInvert',
'deletedOnly',
'target',
'contribs',
'year',
'month',
'start',
@ -543,207 +500,163 @@ class SpecialContributions extends IncludableSpecialPage {
if ( in_array( $name, $skipParameters ) ) {
continue;
}
$form .= "\t" . Html::hidden( $name, $value ) . "\n";
$fields[$name] = [
'name' => $name,
'type' => 'hidden',
'default' => $value,
];
}
$tagFilter = ChangeTags::buildTagFilterSelector(
$this->opts['tagfilter'], false, $this->getContext() );
$target = $this->opts['target'] ?? null;
$fields['target'] = [
'type' => 'text',
'cssclass' => 'mw-autocomplete-user mw-ui-input-inline mw-input',
'default' => $target ?
str_replace( '_', ' ', $target ) : '' ,
'label' => $this->msg( 'sp-contributions-username' )->text(),
'name' => 'target',
'id' => 'mw-target-user-or-ip',
'size' => 40,
'autofocus' => !$target,
'section' => 'contribs-top',
];
if ( $tagFilter ) {
$filterSelection = Html::rawElement(
'div',
[],
implode( "\u{00A0}", $tagFilter )
);
} else {
$filterSelection = Html::rawElement( 'div', [], '' );
}
$labelUsername = Xml::label(
$this->msg( 'sp-contributions-username' )->text(),
'mw-target-user-or-ip'
);
$input = Html::input(
'target',
$this->opts['target'],
'text',
[
'id' => 'mw-target-user-or-ip',
'size' => '40',
'class' => [
'mw-input',
'mw-ui-input-inline',
'mw-autocomplete-user', // used by mediawiki.userSuggest
],
] + (
// Only autofocus if target hasn't been specified
$this->opts['target'] ? [] : [ 'autofocus' => true ]
)
);
$targetSelection = Html::rawElement(
'div',
[],
$labelUsername . ' ' . $input . ' '
);
$hidden = $this->opts['namespace'] === '' ? ' mw-input-hidden' : '';
$namespaceSelection = Xml::tags(
'div',
[],
Xml::label(
$this->msg( 'namespace' )->text(),
'namespace'
) . "\u{00A0}" .
Html::namespaceSelector(
[ 'selected' => $this->opts['namespace'], 'all' => '', 'in-user-lang' => true ],
[
'name' => 'namespace',
'id' => 'namespace',
'class' => 'namespaceselector',
]
) . "\u{00A0}" .
Html::rawElement(
'span',
[ 'class' => 'mw-input-with-label' . $hidden ],
Xml::checkLabel(
$this->msg( 'invert' )->text(),
'nsInvert',
'nsinvert',
$this->opts['nsInvert'],
[
'title' => $this->msg( 'tooltip-invert' )->text(),
'class' => 'mw-input'
]
) . "\u{00A0}"
) .
Html::rawElement( 'span', [ 'class' => 'mw-input-with-label' . $hidden ],
Xml::checkLabel(
$this->msg( 'namespace_association' )->text(),
'associated',
'nsassociated',
$this->opts['associated'],
[
'title' => $this->msg( 'tooltip-namespace_association' )->text(),
'class' => 'mw-input'
]
) . "\u{00A0}"
)
);
$filters = [];
$ns = $this->opts['namespace'] ?? null;
$fields['namespace'] = [
'type' => 'namespaceselect',
'label' => $this->msg( 'namespace' )->text(),
'name' => 'namespace',
'cssclass' => 'namespaceselector',
'default' => $ns,
'id' => 'namespace',
'section' => 'contribs-top',
];
$request = $this->getRequest();
$nsFilters = $request->getArray( 'wpfilters' );
$fields['nsFilters'] = [
'class' => 'HTMLMultiSelectField',
'label' => '',
'name' => 'wpfilters',
'flatlist' => true,
// Only shown when namespaces are selected.
'cssclass' => $ns === '' ?
'contribs-ns-filters mw-input-with-label mw-input-hidden' :
'contribs-ns-filters mw-input-with-label',
// `contribs-ns-filters` class allows these fields to be toggled on/off by JavaScript.
// See resources/src/mediawiki.special.recentchanges.js
'infusable' => true,
'options' => [
$this->msg( 'invert' )->text() => 'nsInvert',
$this->msg( 'namespace_association' )->text() => 'associated',
],
'default' => $nsFilters,
'section' => 'contribs-top',
];
$fields['tagfilter'] = [
'type' => 'tagfilter',
'cssclass' => 'mw-tagfilter-input',
'id' => 'tagfilter',
'label-message' => [ 'tag-filter', 'parse' ],
'name' => 'tagfilter',
'size' => 20,
'section' => 'contribs-top',
];
if ( MediaWikiServices::getInstance()
->getPermissionManager()
->userHasRight( $this->getUser(), 'deletedhistory' )
->getPermissionManager()
->userHasRight( $this->getUser(), 'deletedhistory' )
) {
$filters[] = Html::rawElement(
'span',
[ 'class' => 'mw-input-with-label' ],
Xml::checkLabel(
$this->msg( 'history-show-deleted' )->text(),
'deletedOnly',
'mw-show-deleted-only',
$this->opts['deletedOnly'],
[ 'class' => 'mw-input' ]
)
);
$fields['deletedOnly'] = [
'type' => 'check',
'id' => 'mw-show-deleted-only',
'label' => $this->msg( 'history-show-deleted' )->text(),
'name' => 'deletedOnly',
'section' => 'contribs-top',
];
}
$filters[] = Html::rawElement(
'span',
[ 'class' => 'mw-input-with-label' ],
Xml::checkLabel(
$this->msg( 'sp-contributions-toponly' )->text(),
'topOnly',
'mw-show-top-only',
$this->opts['topOnly'],
[ 'class' => 'mw-input' ]
)
);
$filters[] = Html::rawElement(
'span',
[ 'class' => 'mw-input-with-label' ],
Xml::checkLabel(
$this->msg( 'sp-contributions-newonly' )->text(),
'newOnly',
'mw-show-new-only',
$this->opts['newOnly'],
[ 'class' => 'mw-input' ]
)
);
$filters[] = Html::rawElement(
'span',
[ 'class' => 'mw-input-with-label' ],
Xml::checkLabel(
$this->msg( 'sp-contributions-hideminor' )->text(),
'hideMinor',
'mw-hide-minor-edits',
$this->opts['hideMinor'],
[ 'class' => 'mw-input' ]
)
);
$fields['topOnly'] = [
'type' => 'check',
'id' => 'mw-show-top-only',
'label' => $this->msg( 'sp-contributions-toponly' )->text(),
'name' => 'topOnly',
'section' => 'contribs-top',
];
$fields['newOnly'] = [
'type' => 'check',
'id' => 'mw-show-new-only',
'label' => $this->msg( 'sp-contributions-newonly' )->text(),
'name' => 'newOnly',
'section' => 'contribs-top',
];
$fields['hideMinor'] = [
'type' => 'check',
'cssclass' => 'mw-hide-minor-edits',
'id' => 'mw-show-new-only',
'label' => $this->msg( 'sp-contributions-hideminor' )->text(),
'name' => 'hideMinor',
'section' => 'contribs-top',
];
// Allow additions at this point to the filters.
$rawFilters = [];
Hooks::run(
'SpecialContributions::getForm::filters',
[ $this, &$filters ]
[ $this, &$rawFilters ]
);
foreach ( $rawFilters as $filter ) {
// Backwards compatibility support for previous hook function signature.
if ( is_string( $filter ) ) {
$fields[] = [
'type' => 'info',
'default' => $filter,
'raw' => true,
'section' => 'contribs-top',
];
wfDeprecated(
__METHOD__ .
' returning string[]',
'1.33'
);
} else {
// Preferred append method.
$fields[] = $filter;
}
}
$extraOptions = Html::rawElement(
'div',
[],
implode( '', $filters )
);
$fields['start'] = [
'type' => 'date',
'default' => '',
'id' => 'mw-date-start',
'label' => $this->msg( 'date-range-from' )->text(),
'name' => 'start',
'section' => 'contribs-date',
];
$fields['end'] = [
'type' => 'date',
'default' => '',
'id' => 'mw-date-end',
'label' => $this->msg( 'date-range-to' )->text(),
'name' => 'end',
'section' => 'contribs-date',
];
$dateRangeSelection = Html::rawElement(
'div',
[],
Xml::label( $this->msg( 'date-range-from' )->text(), 'mw-date-start' ) . ' ' .
new DateInputWidget( [
'infusable' => true,
'id' => 'mw-date-start',
'name' => 'start',
'value' => $this->opts['start'],
'longDisplayFormat' => true,
] ) . '<br>' .
Xml::label( $this->msg( 'date-range-to' )->text(), 'mw-date-end' ) . ' ' .
new DateInputWidget( [
'infusable' => true,
'id' => 'mw-date-end',
'name' => 'end',
'value' => $this->opts['end'],
'longDisplayFormat' => true,
] )
);
$submit = Xml::tags( 'div', [],
Html::submitButton(
$this->msg( 'sp-contributions-submit' )->text(),
[ 'class' => 'mw-submit' ], [ 'mw-ui-progressive' ]
)
);
$form .= Xml::fieldset(
$this->msg( 'sp-contributions-search' )->text(),
$targetSelection .
$namespaceSelection .
$filterSelection .
$extraOptions .
$dateRangeSelection .
$submit,
[ 'class' => 'mw-contributions-table' ]
);
$htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
$htmlForm
->setMethod( 'get' )
->setAction( wfScript() )
->setSubmitText( $this->msg( 'sp-contributions-submit' )->text() )
->setWrapperLegend( $this->msg( 'sp-contributions-search' )->text() );
$explain = $this->msg( 'sp-contributions-explain' );
if ( !$explain->isBlank() ) {
$form .= Html::rawElement(
'p', [ 'id' => 'mw-sp-contributions-explain' ], $explain->parse()
);
$htmlForm->addFooterText( "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>" );
}
$form .= Xml::closeElement( 'form' );
$htmlForm->loadData();
return $form;
return $htmlForm->getHTML( false );
}
/**

View file

@ -1983,6 +1983,7 @@ return [
'resources/src/mediawiki.special/special.less',
'resources/src/mediawiki.special/apisandbox.css',
'resources/src/mediawiki.special/comparepages.less',
'resources/src/mediawiki.special/contributions.less',
'resources/src/mediawiki.special/edittags.css',
'resources/src/mediawiki.special/movePage.css',
'resources/src/mediawiki.special/newpages.less',
@ -2146,9 +2147,11 @@ return [
'mediawiki.special.contributions' => [
'scripts' => 'resources/src/mediawiki.special.contributions.js',
'dependencies' => [
'oojs-ui',
'mediawiki.widgets.DateInputWidget',
'mediawiki.jqueryMsg',
]
],
'targets' => [ 'desktop', 'mobile' ],
],
'mediawiki.special.edittags' => [
'scripts' => 'resources/src/mediawiki.special.edittags.js',
@ -2195,6 +2198,9 @@ return [
'styles' => 'resources/src/mediawiki.special.preferences.styles.ooui.less',
],
'mediawiki.special.recentchanges' => [
'dependencies' => [
'mediawiki.widgets'
],
'scripts' => 'resources/src/mediawiki.special.recentchanges.js',
'targets' => [ 'desktop', 'mobile' ],
],

View file

@ -2,7 +2,7 @@
* JavaScript for Special:RecentChanges
*/
( function () {
var rc, $checkboxes, $select;
var rc, $checkboxes, $select, namespaceDropdown;
/**
* @class mw.special.recentchanges
@ -15,19 +15,29 @@
*/
updateCheckboxes: function () {
// The option element for the 'all' namespace has an empty value
var isAllNS = $select.val() === '';
var value = $select.val(),
isAllNS = value === 'all' || value === '';
// Iterates over checkboxes and propagate the selected option
$checkboxes.toggleClass( 'mw-input-hidden', isAllNS );
},
init: function () {
$select = $( '#namespace' );
$checkboxes = $( '#nsassociated, #nsinvert' ).closest( '.mw-input-with-label' );
$select = $( 'select#namespace' );
$checkboxes = $( '#nsassociated, #nsinvert, .contribs-ns-filters' )
.closest( '.mw-input-with-label' );
// Bind to change event of the checkboxes.
// The initial state is already set in HTML.
$select.on( 'change', rc.updateCheckboxes );
if ( $select.length === 0 ) {
$select = $( '#namespace select' );
if ( $select.length > 0 ) {
namespaceDropdown = OO.ui.infuse( $( '#namespace' ).closest( '[data-ooui]' ) );
namespaceDropdown.on( 'change', rc.updateCheckboxes );
}
} else {
// Bind to change event of the checkboxes.
// The initial state is already set in HTML.
$select.on( 'change', rc.updateCheckboxes );
}
}
};

View file

@ -0,0 +1,65 @@
/*!
* Styling for Special:Contributions
*/
@import 'mediawiki.ui/variables.less';
// OOUIHTMLForm styles.
@ooui-font-size-browser: 16; // Assumed browser default of `16px`.
@ooui-font-size-base: 0.875em; // Equals `14px` at browser default of `16px`.
@ooui-spacing-small: 8 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `0.57142857em`≈`8px`.
@ooui-spacing-medium: 12 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `0.8571429em`≈`12px`.
@ooui-spacing-large: 16 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `1.1428571em`≈`16px`.
.oo-ui-fieldsetLayout-group {
max-width: 50em;
.oo-ui-panelLayout-padded.oo-ui-panelLayout-framed {
margin: 0;
border: 0;
padding: 0;
}
// Hide extra `legend`s when grouping form in sections.
.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-fieldsetLayout-header {
display: none;
}
}
.mw-autocomplete-user.oo-ui-fieldLayout {
margin-top: @ooui-spacing-small;
}
// Higher specificity needed to override OOUIHTMLForm styles.
.mw-htmlform-field-HTMLMultiSelectField.mw-htmlform-flatlist.oo-ui-fieldLayout {
margin-top: @ooui-spacing-small;
}
.mw-htmlform-field-HTMLTagFilter ~ .mw-htmlform-field-HTMLCheckField.oo-ui-fieldLayout {
display: inline-block;
padding-right: @ooui-spacing-large;
}
// Clearfix for floated `.mw-htmlform-field-HTMLDateTimeField` below.
#mw-htmlform-contribs-date:after {
content: '';
clear: both;
display: block;
}
.mw-htmlform-field-HTMLDateTimeField {
margin-right: @ooui-spacing-large;
margin-bottom: @ooui-spacing-small;
.oo-ui-fieldLayout.oo-ui-labelElement&:first-child {
margin-top: @ooui-spacing-medium;
}
}
@media all and ( min-width: @width-breakpoint-tablet ) {
.mw-htmlform-field-HTMLDateTimeField {
float: left;
// Same `width` as DateInputWidget.
width: 21em;
}
}

View file

@ -88,10 +88,6 @@
font-weight: bold;
}
.mw-contributions-form select {
vertical-align: middle;
}
/* Special:EditWatchlist */
.watchlistredir {
font-style: italic;