From: jdlrobson Date: Sat, 2 Apr 2016 08:03:42 +0000 (+0300) Subject: Make Special:Contributions use OOUI X-Git-Tag: 1.34.0-rc.0~99^2~1 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=2bb8515286da99953c01ef2f754941e7a4943e5f 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 --- diff --git a/RELEASE-NOTES-1.34 b/RELEASE-NOTES-1.34 index 508ba08955..6b89ff6633 100644 --- a/RELEASE-NOTES-1.34 +++ b/RELEASE-NOTES-1.34 @@ -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 === diff --git a/docs/hooks.txt b/docs/hooks.txt index 55ba06e605..4261b0b0ef 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -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(). diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index e8b85fa024..3b3f2e9a6f 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -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"; - } - $tagFilter = ChangeTags::buildTagFilterSelector( - $this->opts['tagfilter'], false, $this->getContext() ); - - 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}" - ) - ); + $fields[$name] = [ + 'name' => $name, + 'type' => 'hidden', + 'default' => $value, + ]; + } + + $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', + ]; - $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' ] - ) - ); - } - - $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['deletedOnly'] = [ + 'type' => 'check', + 'id' => 'mw-show-deleted-only', + 'label' => $this->msg( 'history-show-deleted' )->text(), + 'name' => 'deletedOnly', + 'section' => 'contribs-top', + ]; + } + + $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 ] - ); - - $extraOptions = Html::rawElement( - 'div', - [], - implode( '', $filters ) - ); - - $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, - ] ) . '
' . - 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, - ] ) + [ $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; + } + } - $submit = Xml::tags( 'div', [], - Html::submitButton( - $this->msg( 'sp-contributions-submit' )->text(), - [ 'class' => 'mw-submit' ], [ 'mw-ui-progressive' ] - ) - ); + $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', + ]; - $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( "

{$explain->parse()}

" ); } - $form .= Xml::closeElement( 'form' ); + $htmlForm->loadData(); - return $form; + return $htmlForm->getHTML( false ); } /** diff --git a/resources/Resources.php b/resources/Resources.php index c8eae03120..383c540fdd 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -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' ], ], diff --git a/resources/src/mediawiki.special.recentchanges.js b/resources/src/mediawiki.special.recentchanges.js index 310832defb..c62acd90ca 100644 --- a/resources/src/mediawiki.special.recentchanges.js +++ b/resources/src/mediawiki.special.recentchanges.js @@ -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 ); + } } }; diff --git a/resources/src/mediawiki.special/contributions.less b/resources/src/mediawiki.special/contributions.less new file mode 100644 index 0000000000..cc0e5386db --- /dev/null +++ b/resources/src/mediawiki.special/contributions.less @@ -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; + } +} diff --git a/resources/src/mediawiki.special/special.less b/resources/src/mediawiki.special/special.less index 3f76cf068a..40a2ec2d31 100644 --- a/resources/src/mediawiki.special/special.less +++ b/resources/src/mediawiki.special/special.less @@ -88,10 +88,6 @@ font-weight: bold; } -.mw-contributions-form select { - vertical-align: middle; -} - /* Special:EditWatchlist */ .watchlistredir { font-style: italic;