'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
'Preferences' => __DIR__ . '/includes/Preferences.php',
'PreferencesForm' => __DIR__ . '/includes/specials/forms/PreferencesForm.php',
+ 'PreferencesFormLegacy' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php',
+ 'PreferencesFormOOUI' => __DIR__ . '/includes/specials/forms/PreferencesFormOOUI.php',
'PrefixSearch' => __DIR__ . '/includes/PrefixSearch.php',
'PreprocessDump' => __DIR__ . '/maintenance/preprocessDump.php',
'Preprocessor' => __DIR__ . '/includes/parser/Preprocessor.php',
*/
$wgUseMediaWikiUIEverywhere = false;
+/**
+ * Temporary variable that determines whether the EditPage class should use OOjs UI or not.
+ * This will be removed later and OOjs UI will become the only option.
+ *
+ * @since 1.32
+ */
+$wgOOUIPreferences = false;
+
/**
* Whether to label the store-to-database-and-show-to-others button in the editor
* as "Save page"/"Save changes" if false (the default) or, if true, instead as
use MWException;
use MWNamespace;
use MWTimestamp;
+use OutputPage;
use Parser;
use ParserOptions;
use PreferencesForm;
+use PreferencesFormOOUI;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Skin;
use SpecialPage;
+use SpecialPreferences;
use Status;
use Title;
use User;
public function getFormDescriptor( User $user, IContextSource $context ) {
$preferences = [];
+ if ( SpecialPreferences::isOouiEnabled( $context ) ) {
+ OutputPage::setupOOUI(
+ strtolower( $context->getSkin()->getSkinName() ),
+ $context->getLanguage()->getDir()
+ );
+ }
+
$canIPUseHTTPS = wfCanIPUseHTTPS( $context->getRequest()->getIP() );
$this->profilePreferences( $user, $context, $preferences, $canIPUseHTTPS );
$this->skinPreferences( $user, $context, $preferences );
protected function profilePreferences(
User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS
) {
+ $oouiEnabled = SpecialPreferences::isOouiEnabled( $context );
+
// retrieving user name for GENDER and misc.
$userName = $user->getName();
if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
new PasswordAuthenticationRequest(), false )->isGood()
) {
- $link = $this->linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
- $context->msg( 'prefs-resetpass' )->text(), [],
- [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+ if ( $oouiEnabled ) {
+ $link = new \OOUI\ButtonWidget( [
+ 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
+ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+ ] ),
+ 'label' => $context->msg( 'prefs-resetpass' )->text(),
+ ] );
+ } else {
+ $link = $this->linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
+ $context->msg( 'prefs-resetpass' )->text(), [],
+ [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+ }
+
$defaultPreferences['password'] = [
'type' => 'info',
'raw' => true,
- 'default' => $link,
+ 'default' => (string)$link,
'label-message' => 'yourpassword',
'section' => 'personal/info',
];
$emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
- $link = $this->linkRenderer->makeLink(
- SpecialPage::getTitleFor( 'ChangeEmail' ),
- $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
- [],
- [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
-
- $emailAddress .= $emailAddress == '' ? $link : (
- $context->msg( 'word-separator' )->escaped()
- . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
- );
+ if ( $oouiEnabled ) {
+ $link = new \OOUI\ButtonWidget( [
+ 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
+ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+ ] ),
+ 'label' =>
+ $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
+ ] );
+
+ $emailAddress .= $emailAddress == '' ? $link : ( '<br />' . $link );
+ } else {
+ $link = $this->linkRenderer->makeLink(
+ SpecialPage::getTitleFor( 'ChangeEmail' ),
+ $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
+ [],
+ [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+
+ $emailAddress .= $emailAddress == '' ? $link : (
+ $context->msg( 'word-separator' )->escaped()
+ . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
+ );
+ }
}
$defaultPreferences['emailaddress'] = [
$emailauthenticationclass = 'mw-email-authenticated';
} else {
$disableEmailPrefs = true;
- $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
- $this->linkRenderer->makeKnownLink(
- SpecialPage::getTitleFor( 'Confirmemail' ),
- $context->msg( 'emailconfirmlink' )->text()
- ) . '<br />';
+ if ( $oouiEnabled ) {
+ $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
+ new \OOUI\ButtonWidget( [
+ 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
+ 'label' => $context->msg( 'emailconfirmlink' )->text(),
+ ] );
+ } else {
+ $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
+ $this->linkRenderer->makeKnownLink(
+ SpecialPage::getTitleFor( 'Confirmemail' ),
+ $context->msg( 'emailconfirmlink' )->text()
+ ) . '<br />';
+ }
$emailauthenticationclass = "mw-email-not-authenticated";
}
} else {
'default' => $tzSetting,
'size' => 20,
'section' => 'rendering/timeoffset',
+ 'id' => 'wpTimeCorrection',
];
}
protected function watchlistPreferences(
User $user, IContextSource $context, &$defaultPreferences
) {
+ $oouiEnabled = SpecialPreferences::isOouiEnabled( $context );
+
$watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
# # Watchlist #####################################
if ( $user->isAllowed( 'editmywatchlist' ) ) {
- $editWatchlistLinks = [];
+ $editWatchlistLinks = '';
+ $editWatchlistLinksOld = [];
$editWatchlistModes = [
- 'edit' => [ 'EditWatchlist', false ],
- 'raw' => [ 'EditWatchlist', 'raw' ],
- 'clear' => [ 'EditWatchlist', 'clear' ],
+ 'edit' => [ 'subpage' => false, 'flags' => [] ],
+ 'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
+ 'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
];
- foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) {
+ foreach ( $editWatchlistModes as $mode => $options ) {
// Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
- $editWatchlistLinks[] = $this->linkRenderer->makeKnownLink(
- SpecialPage::getTitleFor( $mode[0], $mode[1] ),
- new HtmlArmor( $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() )
- );
+ if ( $oouiEnabled ) {
+ $editWatchlistLinks .=
+ new \OOUI\ButtonWidget( [
+ 'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
+ 'flags' => $options[ 'flags' ],
+ 'label' => new \OOUI\HtmlSnippet(
+ $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
+ ),
+ ] );
+ } else {
+ $editWatchlistLinksOld[] = $this->linkRenderer->makeKnownLink(
+ SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] ),
+ new HtmlArmor( $context->msg( "prefs-editwatchlist-{$mode}" )->parse() )
+ );
+ }
}
$defaultPreferences['editwatchlist'] = [
'type' => 'info',
'raw' => true,
- 'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ),
+ 'default' => $oouiEnabled ?
+ $editWatchlistLinks :
+ $context->getLanguage()->pipeList( $editWatchlistLinksOld ),
'label-message' => 'prefs-editwatchlist-label',
'section' => 'watchlist/editwatchlist',
];
$defaultPreferences['watchlisttoken'] = [
'type' => 'api',
];
- $defaultPreferences['watchlisttoken-info'] = [
- 'type' => 'info',
- 'section' => 'watchlist/tokenwatchlist',
- 'label-message' => 'prefs-watchlist-token',
- 'default' => $user->getTokenFromOption( 'watchlisttoken' ),
- 'help-message' => 'prefs-help-watchlist-token2',
- ];
+
+ if ( $oouiEnabled ) {
+ $tokenButton = new \OOUI\ButtonWidget( [
+ 'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
+ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+ ] ),
+ 'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
+ ] );
+ $defaultPreferences['watchlisttoken-info'] = [
+ 'type' => 'info',
+ 'section' => 'watchlist/tokenwatchlist',
+ 'label-message' => 'prefs-watchlist-token',
+ 'help-message' => 'prefs-help-tokenmanagement',
+ 'raw' => true,
+ 'default' => (string)$tokenButton,
+ ];
+ } else {
+ $defaultPreferences['watchlisttoken-info'] = [
+ 'type' => 'info',
+ 'section' => 'watchlist/tokenwatchlist',
+ 'label-message' => 'prefs-watchlist-token',
+ 'default' => $user->getTokenFromOption( 'watchlisttoken' ),
+ 'help-message' => 'prefs-help-watchlist-token2',
+ ];
+ }
}
/**
* @param IContextSource $context
* @param string $formClass
* @param array $remove Array of items to remove
- * @return PreferencesForm|HTMLForm
+ * @return PreferencesForm
*/
public function getForm(
User $user,
IContextSource $context,
- $formClass = PreferencesForm::class,
+ $formClass = PreferencesFormOOUI::class,
array $remove = []
) {
+ if ( SpecialPreferences::isOouiEnabled( $context ) ) {
+ // We use ButtonWidgets in some of the getPreferences() functions
+ $context->getOutput()->enableOOUI();
+ }
+
$formDescriptor = $this->getFormDescriptor( $user, $context );
if ( count( $remove ) ) {
$removeKeys = array_flip( $remove );
* @ingroup SpecialPage
*/
class SpecialPreferences extends SpecialPage {
+ /**
+ * @var bool Whether OOUI should be enabled here
+ */
+ private $oouiEnabled = false;
+
function __construct() {
parent::__construct( 'Preferences' );
+
+ $this->oouiEnabled = self::isOouiEnabled( $this->getContext() );
+ }
+
+ /**
+ * Check if OOUI mode is enabled, by config or query string
+ * @param IContextSource $context The context.
+ * @return bool
+ */
+ public static function isOouiEnabled( IContextSource $context ) {
+ return $context->getRequest()->getFuzzyBool( 'ooui',
+ $context->getConfig()->get( 'OOUIPreferences' )
+ );
}
public function doesWrites() {
return;
}
- $out->addModules( 'mediawiki.special.preferences' );
- $out->addModuleStyles( 'mediawiki.special.preferences.styles' );
+ if ( $this->oouiEnabled ) {
+ $out->addModules( 'mediawiki.special.preferences.ooui' );
+ $out->addModuleStyles( 'mediawiki.special.preferences.styles.ooui' );
+ } else {
+ $out->addModules( 'mediawiki.special.preferences' );
+ $out->addModuleStyles( 'mediawiki.special.preferences.styles' );
+ }
$session = $this->getRequest()->getSession();
if ( $session->get( 'specialPreferencesSaveSuccess' ) ) {
$htmlForm = $this->getFormObject( $user, $this->getContext() );
$sectionTitles = $htmlForm->getPreferenceSections();
- $prefTabs = '';
- foreach ( $sectionTitles as $key ) {
- $prefTabs .= Html::rawElement( 'li',
- [
- 'role' => 'presentation',
- 'class' => ( $key === 'personal' ) ? 'selected' : null
- ],
- Html::rawElement( 'a',
+ if ( $this->oouiEnabled ) {
+ $prefTabs = [];
+ foreach ( $sectionTitles as $key ) {
+ $prefTabs[] = [
+ 'name' => $key,
+ 'label' => $htmlForm->getLegend( $key ),
+ ];
+ }
+ $out->addJsConfigVars( 'wgPreferencesTabs', $prefTabs );
+
+ // TODO: Render fake tabs here to avoid FOUC.
+ // $out->addHTML( $fakeTabs );
+ } else {
+
+ $prefTabs = '';
+ foreach ( $sectionTitles as $key ) {
+ $prefTabs .= Html::rawElement( 'li',
[
- 'id' => 'preftab-' . $key,
- 'role' => 'tab',
- 'href' => '#mw-prefsection-' . $key,
- 'aria-controls' => 'mw-prefsection-' . $key,
- 'aria-selected' => ( $key === 'personal' ) ? 'true' : 'false',
- 'tabIndex' => ( $key === 'personal' ) ? 0 : -1,
+ 'role' => 'presentation',
+ 'class' => ( $key === 'personal' ) ? 'selected' : null
],
- $htmlForm->getLegend( $key )
- )
+ Html::rawElement( 'a',
+ [
+ 'id' => 'preftab-' . $key,
+ 'role' => 'tab',
+ 'href' => '#mw-prefsection-' . $key,
+ 'aria-controls' => 'mw-prefsection-' . $key,
+ 'aria-selected' => ( $key === 'personal' ) ? 'true' : 'false',
+ 'tabIndex' => ( $key === 'personal' ) ? 0 : -1,
+ ],
+ $htmlForm->getLegend( $key )
+ )
+ );
+ }
+
+ $out->addHTML(
+ Html::rawElement( 'ul',
+ [
+ 'id' => 'preftoc',
+ 'role' => 'tablist'
+ ],
+ $prefTabs )
);
}
- $out->addHTML(
- Html::rawElement( 'ul',
- [
- 'id' => 'preftoc',
- 'role' => 'tablist'
- ],
- $prefTabs )
- );
$htmlForm->show();
}
*/
protected function getFormObject( $user, IContextSource $context ) {
$preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
- $form = $preferencesFactory->getForm( $user, $context );
+ if ( $this->oouiEnabled ) {
+ $form = $preferencesFactory->getForm( $user, $context, PreferencesFormOOUI::class );
+ } else {
+ $form = $preferencesFactory->getForm( $user, $context, PreferencesFormLegacy::class );
+ }
return $form;
}
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $this->getPageTitle( 'reset' ) ); // Reset subpage
- $htmlForm = new HTMLForm( [], $context, 'prefs-restore' );
+ $htmlForm = HTMLForm::factory(
+ $this->oouiEnabled ? 'ooui' : 'vform', [], $context, 'prefs-restore'
+ );
$htmlForm->setSubmitTextMsg( 'restoreprefs' );
$htmlForm->setSubmitDestructive();
* @file
*/
-use MediaWiki\MediaWikiServices;
-
/**
- * Form to edit user preferences.
+ * Temporarily define PreferencesForm as an interface, so PreferencesFormOOUI
+ * and PreferencesFormLegacy can implement it.
+ *
+ * When PreferencesFormLegacy we can merge PreferencesFormOOUI with PreferencesForm.
*/
-class PreferencesForm extends HTMLForm {
- // Override default value from HTMLForm
- protected $mSubSectionBeforeFields = false;
-
- private $modifiedUser;
-
- /**
- * @param User $user
- */
- public function setModifiedUser( $user ) {
- $this->modifiedUser = $user;
- }
-
- /**
- * @return User
- */
- public function getModifiedUser() {
- if ( $this->modifiedUser === null ) {
- return $this->getUser();
- } else {
- return $this->modifiedUser;
- }
- }
-
- /**
- * Get extra parameters for the query string when redirecting after
- * successful save.
- *
- * @return array
- */
- public function getExtraSuccessRedirectParameters() {
- return [];
- }
-
- /**
- * @param string $html
- * @return string
- */
- function wrapForm( $html ) {
- $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
-
- return parent::wrapForm( $html );
- }
-
- /**
- * @return string
- */
- function getButtons() {
- $attrs = [ 'id' => 'mw-prefs-restoreprefs' ];
-
- if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
- return '';
- }
-
- $html = parent::getButtons();
-
- if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
- $t = $this->getTitle()->getSubpage( 'reset' );
-
- $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
- $html .= "\n" . $linkRenderer->makeLink( $t, $this->msg( 'restoreprefs' )->text(),
- Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) );
-
- $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
- }
-
- return $html;
- }
-
- /**
- * Separate multi-option preferences into multiple preferences, since we
- * have to store them separately
- * @param array $data
- * @return array
- */
- function filterDataForSubmit( $data ) {
- foreach ( $this->mFlatFields as $fieldname => $field ) {
- if ( $field instanceof HTMLNestedFilterable ) {
- $info = $field->mParams;
- $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
- foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
- $data["$prefix$key"] = $value;
- }
- unset( $data[$fieldname] );
- }
- }
-
- return $data;
- }
-
- /**
- * Get the whole body of the form.
- * @return string
- */
- function getBody() {
- return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
- }
-
- /**
- * Get the "<legend>" for a given section key. Normally this is the
- * prefs-$key message but we'll allow extensions to override it.
- * @param string $key
- * @return string
- */
- function getLegend( $key ) {
- $legend = parent::getLegend( $key );
- Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
- return $legend;
- }
-
- /**
- * Get the keys of each top level preference section.
- * @return array of section keys
- */
- function getPreferenceSections() {
- return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
- }
+interface PreferencesForm {
}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Form to edit user preferences.
+ */
+class PreferencesFormLegacy extends HTMLForm implements PreferencesForm {
+ // Override default value from HTMLForm
+ protected $mSubSectionBeforeFields = false;
+
+ private $modifiedUser;
+
+ /**
+ * @param User $user
+ */
+ public function setModifiedUser( $user ) {
+ $this->modifiedUser = $user;
+ }
+
+ /**
+ * @return User
+ */
+ public function getModifiedUser() {
+ if ( $this->modifiedUser === null ) {
+ return $this->getUser();
+ } else {
+ return $this->modifiedUser;
+ }
+ }
+
+ /**
+ * Get extra parameters for the query string when redirecting after
+ * successful save.
+ *
+ * @return array
+ */
+ public function getExtraSuccessRedirectParameters() {
+ return [];
+ }
+
+ /**
+ * @param string $html
+ * @return string
+ */
+ function wrapForm( $html ) {
+ $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
+
+ return parent::wrapForm( $html );
+ }
+
+ /**
+ * @return string
+ */
+ function getButtons() {
+ $attrs = [ 'id' => 'mw-prefs-restoreprefs' ];
+
+ if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+ return '';
+ }
+
+ $html = parent::getButtons();
+
+ if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
+ $t = $this->getTitle()->getSubpage( 'reset' );
+
+ $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+ $html .= "\n" . $linkRenderer->makeLink( $t, $this->msg( 'restoreprefs' )->text(),
+ Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) );
+
+ $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Separate multi-option preferences into multiple preferences, since we
+ * have to store them separately
+ * @param array $data
+ * @return array
+ */
+ function filterDataForSubmit( $data ) {
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( $field instanceof HTMLNestedFilterable ) {
+ $info = $field->mParams;
+ $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
+ foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
+ $data["$prefix$key"] = $value;
+ }
+ unset( $data[$fieldname] );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get the whole body of the form.
+ * @return string
+ */
+ function getBody() {
+ return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
+ }
+
+ /**
+ * Get the "<legend>" for a given section key. Normally this is the
+ * prefs-$key message but we'll allow extensions to override it.
+ * @param string $key
+ * @return string
+ */
+ function getLegend( $key ) {
+ $legend = parent::getLegend( $key );
+ Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
+ return $legend;
+ }
+
+ /**
+ * Get the keys of each top level preference section.
+ * @return array of section keys
+ */
+ function getPreferenceSections() {
+ return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Form to edit user preferences.
+ */
+class PreferencesFormOOUI extends OOUIHTMLForm implements PreferencesForm {
+ // Override default value from HTMLForm
+ protected $mSubSectionBeforeFields = false;
+
+ private $modifiedUser;
+
+ /**
+ * @param User $user
+ */
+ public function setModifiedUser( $user ) {
+ $this->modifiedUser = $user;
+ }
+
+ /**
+ * @return User
+ */
+ public function getModifiedUser() {
+ if ( $this->modifiedUser === null ) {
+ return $this->getUser();
+ } else {
+ return $this->modifiedUser;
+ }
+ }
+
+ /**
+ * Get extra parameters for the query string when redirecting after
+ * successful save.
+ *
+ * @return array
+ */
+ public function getExtraSuccessRedirectParameters() {
+ return [];
+ }
+
+ /**
+ * @param string $html
+ * @return string
+ */
+ function wrapForm( $html ) {
+ $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
+
+ return parent::wrapForm( $html );
+ }
+
+ /**
+ * @return string
+ */
+ function getButtons() {
+ if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+ return '';
+ }
+
+ $html = parent::getButtons();
+
+ if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
+ $t = $this->getTitle()->getSubpage( 'reset' );
+
+ $html .= new OOUI\ButtonWidget( [
+ 'infusable' => true,
+ 'id' => 'mw-prefs-restoreprefs',
+ 'label' => $this->msg( 'restoreprefs' )->text(),
+ 'href' => $t->getLinkURL(),
+ 'flags' => [ 'destructive' ],
+ 'framed' => false,
+ ] );
+
+ $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Separate multi-option preferences into multiple preferences, since we
+ * have to store them separately
+ * @param array $data
+ * @return array
+ */
+ function filterDataForSubmit( $data ) {
+ foreach ( $this->mFlatFields as $fieldname => $field ) {
+ if ( $field instanceof HTMLNestedFilterable ) {
+ $info = $field->mParams;
+ $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
+ foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
+ $data["$prefix$key"] = $value;
+ }
+ unset( $data[$fieldname] );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get the whole body of the form.
+ * @return string
+ */
+ function getBody() {
+ return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
+ }
+
+ /**
+ * Get the "<legend>" for a given section key. Normally this is the
+ * prefs-$key message but we'll allow extensions to override it.
+ * @param string $key
+ * @return string
+ */
+ function getLegend( $key ) {
+ $legend = parent::getLegend( $key );
+ Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
+ return $legend;
+ }
+
+ /**
+ * Get the keys of each top level preference section.
+ * @return array of section keys
+ */
+ function getPreferenceSections() {
+ return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
+ }
+}
"prefs-watchlist-edits": "Maximum number of changes to show in watchlist:",
"prefs-watchlist-edits-max": "Maximum number: 1000",
"prefs-watchlist-token": "Watchlist token:",
+ "prefs-watchlist-managetokens": "Manage tokens",
"prefs-misc": "Misc",
"prefs-resetpass": "Change password",
"prefs-changeemail": "Change or remove email address",
"recentchangescount": "Number of edits to show in recent changes, page histories, and in logs, by default:",
"prefs-help-recentchangescount": "Maximum number: 1000",
"prefs-help-watchlist-token2": "This is the secret key to the web feed of your watchlist.\nAnyone who knows it will be able to read your watchlist, so do not share it.\nIf you need to, [[Special:ResetTokens|you can reset it]].",
+ "prefs-help-tokenmanagement": "You can see and reset the secret key for your account that can access the Web feed of your watchlist. Anyone who knows the key will be able to read your watchlist, so do not share it.",
"savedprefs": "Your preferences have been saved.",
"savedrights": "The user groups of {{GENDER:$1|$1}} have been saved.",
"timezonelegend": "Time zone:",
"prefs-watchlist-edits": "Used in [[Special:Preferences]], tab \"Watchlist\".",
"prefs-watchlist-edits-max": "Shown as hint in [[Special:Preferences]], tab \"Watchlist\"",
"prefs-watchlist-token": "Used in [[Special:Preferences]], tab Watchlist.",
+ "prefs-watchlist-managetokens": "Label for the button to see and reset the user's private tokens",
"prefs-misc": "Tab used on the [[Special:Preferences|user preferences]] special page.",
"prefs-resetpass": "Button on user data tab in user preferences. When you click the button you go to the special page [[Special:ResetPass]].\n\n{{Identical|Change password}}",
"prefs-changeemail": "Link on [[Special:Preferences]] to [[Special:ChangeEmail]]. [[Special:ChangeEmail]] also allows removing email address. \n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* {{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* {{msg-mw|prefs-setemail|link title}}",
"recentchangescount": "Used in [[Special:Preferences]], tab \"Recent changes\".",
"prefs-help-recentchangescount": "Used in [[Special:Preferences]], tab \"Recent changes\".",
"prefs-help-watchlist-token2": "Used in [[Special:Preferences]], tab Watchlist. (Formerly in {{msg-mw|prefs-help-watchlist-token}}.)",
+ "prefs-help-tokenmanagement": "Used in [[Special:Preferences]], Watchlist tab.",
"savedprefs": "This message appears after saving changes to your user preferences.",
"savedrights": "This message appears after saving the user groups on [[Special:UserRights]].\n* $1 - The user name of the user which groups was saved.",
"timezonelegend": "{{Identical|Time zone}}",
'scripts' => [
'resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js',
'resources/src/mediawiki.special/mediawiki.special.preferences.convertmessagebox.js',
- 'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.legacy.js',
'resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js',
'resources/src/mediawiki.special/mediawiki.special.preferences.personalEmail.js',
],
],
],
'mediawiki.special.preferences.styles' => [
+ 'targets' => [ 'desktop', 'mobile' ],
+ 'styles' => 'resources/src/mediawiki.special/mediawiki.special.preferences.styles.legacy.css',
+ ],
+ 'mediawiki.special.preferences.ooui' => [
+ 'targets' => [ 'desktop', 'mobile' ],
+ 'scripts' => [
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.convertmessagebox.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js',
+ 'resources/src/mediawiki.special/mediawiki.special.preferences.personalEmail.js',
+ ],
+ 'messages' => [
+ 'prefs-tabs-navigation-hint',
+ 'prefswarning-warning',
+ 'saveprefs',
+ 'savedprefs',
+ ],
+ 'dependencies' => [
+ 'mediawiki.language',
+ 'mediawiki.confirmCloseWindow',
+ 'mediawiki.notification.convertmessagebox',
+ 'oojs-ui-widgets',
+ 'mediawiki.widgets.SelectWithInputWidget',
+ 'mediawiki.editfont.styles',
+ ],
+ ],
+ 'mediawiki.special.preferences.styles.ooui' => [
'targets' => [ 'desktop', 'mobile' ],
'styles' => 'resources/src/mediawiki.special/mediawiki.special.preferences.styles.css',
],
}
/* preference page with js-genrated toc */
+/* TODO: Delete #preftoc when Special:Preference's non-OOUI mode is disabled */
#preftoc {
float: left;
margin: 1em 1em 1em 1em;
*/
( function ( mw, $ ) {
$( function () {
- var allowCloseWindow;
+ var allowCloseWindow, saveButton, restoreButton,
+ oouiEnabled = $( '#mw-prefs-form' ).hasClass( 'mw-htmlform-ooui' );
- // Check if all of the form values are unchanged
+ // Check if all of the form values are unchanged.
+ // (This function could be changed to infuse and check OOUI widgets, but that would only make it
+ // slower and more complicated. It works fine to treat them as HTML elements.)
function isPrefsChanged() {
var inputs = $( '#mw-prefs-form :input[name]' ),
input, $input, inputType,
return false;
}
- // Disable the button to save preferences unless preferences have changed
- // Check if preferences have been changed before JS has finished loading
- $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
- $( '#preferences > fieldset' ).on( 'change keyup mouseup', function () {
+ if ( oouiEnabled ) {
+ saveButton = OO.ui.infuse( $( '#prefcontrol' ) );
+ restoreButton = OO.ui.infuse( $( '#mw-prefs-restoreprefs' ) );
+
+ // Disable the button to save preferences unless preferences have changed
+ // Check if preferences have been changed before JS has finished loading
+ saveButton.setDisabled( !isPrefsChanged() );
+ $( '#preferences .oo-ui-fieldsetLayout' ).on( 'change keyup mouseup', function () {
+ saveButton.setDisabled( !isPrefsChanged() );
+ } );
+ } else {
+ // Disable the button to save preferences unless preferences have changed
+ // Check if preferences have been changed before JS has finished loading
$( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
- } );
+ $( '#preferences > fieldset' ).on( 'change keyup mouseup', function () {
+ $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
+ } );
+ }
// Set up a message to notify users if they try to leave the page without
// saving.
message: mw.msg( 'prefswarning-warning', mw.msg( 'saveprefs' ) ),
namespace: 'prefswarning'
} );
- $( '#mw-prefs-form' ).submit( $.proxy( allowCloseWindow, 'release' ) );
- $( '#mw-prefs-restoreprefs' ).click( $.proxy( allowCloseWindow, 'release' ) );
+ $( '#mw-prefs-form' ).on( 'submit', $.proxy( allowCloseWindow, 'release' ) );
+ if ( oouiEnabled ) {
+ restoreButton.on( 'click', function () {
+ allowCloseWindow.release();
+ // The default behavior of events in OOUI is always prevented. Follow the link manually.
+ // Note that middle-click etc. still works, as it doesn't emit a OOUI 'click' event.
+ location.href = restoreButton.getHref();
+ } );
+ } else {
+ $( '#mw-prefs-restoreprefs' ).on( 'click', $.proxy( allowCloseWindow, 'release' ) );
+ }
} );
}( mediaWiki, jQuery ) );
--- /dev/null
+/*!
+ * JavaScript for Special:Preferences: editfont field enhancements.
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var widget, lastValue;
+
+ try {
+ widget = OO.ui.infuse( $( '#mw-input-wpeditfont' ) );
+ } catch ( err ) {
+ // This preference could theoretically be disabled ($wgHiddenPrefs)
+ return;
+ }
+
+ // Style options
+ widget.dropdownWidget.menu.items.forEach( function ( item ) {
+ item.$label.addClass( 'mw-editfont-' + item.getData() );
+ } );
+
+ function updateLabel( value ) {
+ // Style selected item label
+ widget.dropdownWidget.$label
+ .removeClass( 'mw-editfont-' + lastValue )
+ .addClass( 'mw-editfont-' + value );
+ lastValue = value;
+ }
+
+ widget.on( 'change', updateLabel );
+ updateLabel( widget.getValue() );
+
+ } );
+}( mediaWiki, jQuery ) );
/* Reuses colors from mediawiki.legacy/shared.css */
-.mw-email-not-authenticated .mw-input,
-.mw-email-none .mw-input {
+.mw-email-not-authenticated .oo-ui-labelWidget,
+.mw-email-none .oo-ui-labelWidget {
border: 1px solid #fde29b;
background-color: #fdf1d1;
color: #000;
+ padding: 0.5em;
}
/* Authenticated email field has its own class too. Unstyled by default */
/*
-.mw-email-authenticated .mw-input { }
+.mw-email-authenticated .oo-ui-labelWidget { }
*/
-/* This breaks due to nolabel styling */
-#preferences > fieldset td.mw-label {
- width: 20%;
-}
-#preferences > fieldset table {
- width: 100%;
+/* This is needed because add extra buttons in a weird way */
+.mw-prefs-buttons .mw-htmlform-submit-buttons {
+ margin: 0;
+ display: inline;
}
-#preferences > fieldset table.mw-htmlform-matrix {
- width: auto;
+
+.mw-prefs-buttons {
+ margin-top: 1em;
}
-/* The CSS below is also for JS enabled version, because we want to prevent FOUC */
+#prefcontrol {
+ margin-right: 0.5em;
+}
/*
* Hide, but keep accessible for screen-readers.
zoom: 1;
}
-.client-nojs #preftoc {
- display: none;
+/* Override OOUI styles so that dropdowns near the bottom of the form don't get clipped,
+ * e.g.'Appearance' / 'Threshold for stub link formatting'. This is hacky and bad, it would be
+ * better solved by setting overlays for the widgets, but we can't do it from PHP... */
+#preferences .oo-ui-panelLayout {
+ position: static;
+ overflow: visible;
+ -webkit-transform: none;
+ transform: none;
+}
+
+#preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+ border-color: #c8ccd1;
+ border-width: 1px 0 0;
+ border-radius: 0;
+ padding-left: 0;
+ padding-right: 0;
+ box-shadow: none;
+}
+
+/* Tweak the margins to reduce the shifting of form contents
+ * after JS code loads and rearranges the page */
+.client-js #preferences > .oo-ui-panelLayout {
+ margin: 1em 0;
+}
+
+.client-js #preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+ margin-left: 0.25em;
+}
+
+.client-js #preferences .oo-ui-tabPanelLayout {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
}
-.client-js #preferences > fieldset {
- display: none;
+.client-js #preferences .oo-ui-tabPanelLayout .oo-ui-panelLayout-framed {
+ margin-left: 0;
+ margin-bottom: 0;
+ border: 0;
+ padding-top: 0;
}
-/* Only the 1st tab is shown by default in JS mode */
-.client-js #preferences #mw-prefsection-personal {
+.client-js #preferences > .oo-ui-panelLayout > .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header {
+ margin-bottom: 1em;
+}
+
+/* Make the "Basic information" section more compact */
+/* OOUI's `align: 'left'` for FieldLayouts sucks, so we do our own */
+#mw-htmlform-info > .oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header {
+ width: 20%;
+ display: inline-block;
+ vertical-align: middle;
+ padding: 0;
+}
+
+#mw-htmlform-info > .oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-help {
+ margin-right: 0;
+}
+
+#mw-htmlform-info > .oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+ width: 80%;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+/* Expand the dropdown and textfield of "Time zone" field to the */
+/* usual maximum width and display them on separate lines. */
+#wpTimeCorrection .oo-ui-dropdownInputWidget,
+#wpTimeCorrection .oo-ui-textInputWidget {
display: block;
+ max-width: 50em;
+}
+
+#wpTimeCorrection .oo-ui-textInputWidget {
+ margin-top: 0.5em;
}
--- /dev/null
+/* Reuses colors from mediawiki.legacy/shared.css */
+.mw-email-not-authenticated .mw-input,
+.mw-email-none .mw-input {
+ border: 1px solid #fde29b;
+ background-color: #fdf1d1;
+ color: #000;
+}
+/* Authenticated email field has its own class too. Unstyled by default */
+/*
+.mw-email-authenticated .mw-input { }
+*/
+/* This breaks due to nolabel styling */
+#preferences > fieldset td.mw-label {
+ width: 20%;
+}
+
+#preferences > fieldset table {
+ width: 100%;
+}
+#preferences > fieldset table.mw-htmlform-matrix {
+ width: auto;
+}
+
+/* The CSS below is also for JS enabled version, because we want to prevent FOUC */
+
+/*
+ * Hide, but keep accessible for screen-readers.
+ * Like .mw-jump, #jump-to-nav from shared.css
+ */
+.client-js .mw-navigation-hint {
+ overflow: hidden;
+ height: 0;
+ zoom: 1;
+}
+
+.client-nojs #preftoc {
+ display: none;
+}
+
+.client-js #preferences > fieldset {
+ display: none;
+}
+
+/* Only the 1st tab is shown by default in JS mode */
+.client-js #preferences #mw-prefsection-personal {
+ display: block;
+}
*/
( function ( mw, $ ) {
$( function () {
- var $preftoc, $preferences, $fieldsets, labelFunc, previousTab;
+ var $preferences, tabs, wrapper, previousTab;
- labelFunc = function () {
- return this.id.replace( /^mw-prefsection/g, 'preftab' );
- };
-
- $preftoc = $( '#preftoc' );
$preferences = $( '#preferences' );
- $fieldsets = $preferences.children( 'fieldset' )
- .attr( {
- role: 'tabpanel',
- 'aria-labelledby': labelFunc
- } );
- $fieldsets.not( '#mw-prefsection-personal' )
- .hide()
- .attr( 'aria-hidden', 'true' );
-
- // T115692: The following is kept for backwards compatibility with older skins
- $preferences.addClass( 'jsprefs' );
- $fieldsets.addClass( 'prefsection' );
- $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
-
// Make sure the accessibility tip is selectable so that screen reader users take notice,
// but hide it per default to reduce interface clutter. Also make sure it becomes visible
// when selected. Similar to jquery.mw-jump
} else {
$( this ).css( 'height', 'auto' );
}
- } ).insertBefore( $preftoc );
+ } ).prependTo( '#mw-content-text' );
- /**
- * It uses document.getElementById for security reasons (HTML injections in $()).
- *
- * @ignore
- * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
- * @param {string} [mode] A hash will be set according to the current
- * open section. Set mode 'noHash' to surpress this.
- */
- function switchPrefTab( name, mode ) {
- var $tab, scrollTop;
+ tabs = new OO.ui.IndexLayout( {
+ expanded: false,
+ // Do not remove focus from the tabs menu after choosing a tab
+ autoFocus: false
+ } );
+
+ mw.config.get( 'wgPreferencesTabs' ).forEach( function ( tabConfig ) {
+ var panel, $panelContents;
+
+ panel = new OO.ui.TabPanelLayout( tabConfig.name, {
+ expanded: false,
+ label: tabConfig.label
+ } );
+ $panelContents = $( '#mw-prefsection-' + tabConfig.name );
+
+ // Hide the unnecessary PHP PanelLayouts
+ // (Do not use .remove(), as that would remove event handlers for everything inside them)
+ $panelContents.parent().detach();
+
+ panel.$element.append( $panelContents );
+ tabs.addTabPanels( [ panel ] );
+
+ // Remove duplicate labels
+ // (This must be after .addTabPanels(), otherwise the tab item doesn't exist yet)
+ $panelContents.children( 'legend' ).remove();
+ $panelContents.attr( 'aria-labelledby', panel.getTabItem().getElementId() );
+ } );
+
+ wrapper = new OO.ui.PanelLayout( {
+ expanded: false,
+ padded: false,
+ framed: true
+ } );
+ wrapper.$element.append( tabs.$element );
+ $preferences.prepend( wrapper.$element );
+
+ function updateHash( panel ) {
+ var scrollTop, active;
// Handle hash manually to prevent jumping,
// therefore save and restore scrollTop to prevent jumping.
scrollTop = $( window ).scrollTop();
- if ( mode !== 'noHash' ) {
- location.hash = '#mw-prefsection-' + name;
+ // Changing the hash apparently causes keyboard focus to be lost?
+ // Save and restore it. This makes no sense though.
+ active = document.activeElement;
+ location.hash = '#mw-prefsection-' + panel.getName();
+ if ( active ) {
+ active.focus();
}
$( window ).scrollTop( scrollTop );
-
- $preftoc.find( 'li' ).removeClass( 'selected' )
- .find( 'a' ).attr( {
- tabIndex: -1,
- 'aria-selected': 'false'
- } );
-
- $tab = $( document.getElementById( 'preftab-' + name ) );
- if ( $tab.length ) {
- $tab.attr( {
- tabIndex: 0,
- 'aria-selected': 'true'
- } ).focus()
- .parent().addClass( 'selected' );
-
- $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
- $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' );
- }
}
- // Enable keyboard users to use left and right keys to switch tabs
- $preftoc.on( 'keydown', function ( event ) {
- var keyLeft = 37,
- keyRight = 39,
- $el;
-
- if ( event.keyCode === keyLeft ) {
- $el = $( '#preftoc li.selected' ).prev().find( 'a' );
- } else if ( event.keyCode === keyRight ) {
- $el = $( '#preftoc li.selected' ).next().find( 'a' );
- } else {
- return;
+ tabs.on( 'set', updateHash );
+
+ /**
+ * @ignore
+ * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
+ * @param {string} [mode] A hash will be set according to the current
+ * open section. Set mode 'noHash' to supress this.
+ */
+ function switchPrefTab( name, mode ) {
+ if ( mode === 'noHash' ) {
+ tabs.off( 'set', updateHash );
}
- if ( $el.length > 0 ) {
- switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) );
+ tabs.setTabPanel( name );
+ if ( mode === 'noHash' ) {
+ tabs.on( 'set', updateHash );
}
- } );
+ }
// Jump to correct section as indicated by the hash.
// This function is called onload and onhashchange.
}
$( '#mw-prefs-form' ).on( 'submit', function () {
- var value = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' );
+ var value = tabs.getCurrentTabPanelName();
mw.storage.session.set( 'mwpreferences-prevTab', value );
} );
--- /dev/null
+/*!
+ * JavaScript for Special:Preferences: Tab navigation.
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var $preftoc, $preferences, $fieldsets, labelFunc, previousTab;
+
+ labelFunc = function () {
+ return this.id.replace( /^mw-prefsection/g, 'preftab' );
+ };
+
+ $preftoc = $( '#preftoc' );
+ $preferences = $( '#preferences' );
+
+ $fieldsets = $preferences.children( 'fieldset' )
+ .attr( {
+ role: 'tabpanel',
+ 'aria-labelledby': labelFunc
+ } );
+ $fieldsets.not( '#mw-prefsection-personal' )
+ .hide()
+ .attr( 'aria-hidden', 'true' );
+
+ // T115692: The following is kept for backwards compatibility with older skins
+ $preferences.addClass( 'jsprefs' );
+ $fieldsets.addClass( 'prefsection' );
+ $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
+
+ // Make sure the accessibility tip is selectable so that screen reader users take notice,
+ // but hide it per default to reduce interface clutter. Also make sure it becomes visible
+ // when selected. Similar to jquery.mw-jump
+ $( '<div>' ).addClass( 'mw-navigation-hint' )
+ .text( mw.msg( 'prefs-tabs-navigation-hint' ) )
+ .attr( 'tabIndex', 0 )
+ .on( 'focus blur', function ( e ) {
+ if ( e.type === 'blur' || e.type === 'focusout' ) {
+ $( this ).css( 'height', '0' );
+ } else {
+ $( this ).css( 'height', 'auto' );
+ }
+ } ).insertBefore( $preftoc );
+
+ /**
+ * It uses document.getElementById for security reasons (HTML injections in $()).
+ *
+ * @ignore
+ * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
+ * @param {string} [mode] A hash will be set according to the current
+ * open section. Set mode 'noHash' to surpress this.
+ */
+ function switchPrefTab( name, mode ) {
+ var $tab, scrollTop;
+ // Handle hash manually to prevent jumping,
+ // therefore save and restore scrollTop to prevent jumping.
+ scrollTop = $( window ).scrollTop();
+ if ( mode !== 'noHash' ) {
+ location.hash = '#mw-prefsection-' + name;
+ }
+ $( window ).scrollTop( scrollTop );
+
+ $preftoc.find( 'li' ).removeClass( 'selected' )
+ .find( 'a' ).attr( {
+ tabIndex: -1,
+ 'aria-selected': 'false'
+ } );
+
+ $tab = $( document.getElementById( 'preftab-' + name ) );
+ if ( $tab.length ) {
+ $tab.attr( {
+ tabIndex: 0,
+ 'aria-selected': 'true'
+ } ).focus()
+ .parent().addClass( 'selected' );
+
+ $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
+ $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' );
+ }
+ }
+
+ // Enable keyboard users to use left and right keys to switch tabs
+ $preftoc.on( 'keydown', function ( event ) {
+ var keyLeft = 37,
+ keyRight = 39,
+ $el;
+
+ if ( event.keyCode === keyLeft ) {
+ $el = $( '#preftoc li.selected' ).prev().find( 'a' );
+ } else if ( event.keyCode === keyRight ) {
+ $el = $( '#preftoc li.selected' ).next().find( 'a' );
+ } else {
+ return;
+ }
+ if ( $el.length > 0 ) {
+ switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) );
+ }
+ } );
+
+ // Jump to correct section as indicated by the hash.
+ // This function is called onload and onhashchange.
+ function detectHash() {
+ var hash = location.hash,
+ matchedElement, parentSection;
+ if ( hash.match( /^#mw-prefsection-[\w]+$/ ) ) {
+ mw.storage.session.remove( 'mwpreferences-prevTab' );
+ switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
+ } else if ( hash.match( /^#mw-[\w-]+$/ ) ) {
+ matchedElement = document.getElementById( hash.slice( 1 ) );
+ parentSection = $( matchedElement ).parent().closest( '[id^="mw-prefsection-"]' );
+ if ( parentSection.length ) {
+ mw.storage.session.remove( 'mwpreferences-prevTab' );
+ // Switch to proper tab and scroll to selected item.
+ switchPrefTab( parentSection.attr( 'id' ).replace( 'mw-prefsection-', '' ), 'noHash' );
+ matchedElement.scrollIntoView();
+ }
+ }
+ }
+
+ $( window ).on( 'hashchange', function () {
+ var hash = location.hash;
+ if ( hash.match( /^#mw-[\w-]+/ ) ) {
+ detectHash();
+ } else if ( hash === '' ) {
+ switchPrefTab( 'personal', 'noHash' );
+ }
+ } )
+ // Run the function immediately to select the proper tab on startup.
+ .trigger( 'hashchange' );
+
+ // Restore the active tab after saving the preferences
+ previousTab = mw.storage.session.get( 'mwpreferences-prevTab' );
+ if ( previousTab ) {
+ switchPrefTab( previousTab, 'noHash' );
+ // Deleting the key, the tab states should be reset until we press Save
+ mw.storage.session.remove( 'mwpreferences-prevTab' );
+ }
+
+ $( '#mw-prefs-form' ).on( 'submit', function () {
+ var value = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' );
+ mw.storage.session.set( 'mwpreferences-prevTab', value );
+ } );
+
+ } );
+}( mediaWiki, jQuery ) );
*/
( function ( mw, $ ) {
$( function () {
- var
- $tzSelect, $tzTextbox, $localtimeHolder, servertime;
+ var $tzSelect, $tzTextbox, timezoneWidget, $localtimeHolder, servertime,
+ oouiEnabled = $( '#mw-prefs-form' ).hasClass( 'mw-htmlform-ooui' );
// Timezone functions.
// Guesses Timezone from browser and updates fields onchange.
- $tzSelect = $( '#mw-input-wptimecorrection' );
- $tzTextbox = $( '#mw-input-wptimecorrection-other' );
+ if ( oouiEnabled ) {
+ // This is identical to OO.ui.infuse( ... ), but it makes the class name of the result known.
+ try {
+ timezoneWidget = mw.widgets.SelectWithInputWidget.static.infuse( $( '#wpTimeCorrection' ) );
+ } catch ( err ) {
+ // This preference could theoretically be disabled ($wgHiddenPrefs)
+ timezoneWidget = null;
+ }
+ } else {
+ $tzSelect = $( '#mw-input-wptimecorrection' );
+ $tzTextbox = $( '#mw-input-wptimecorrection-other' );
+ }
+
$localtimeHolder = $( '#wpLocalTime' );
servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 );
function updateTimezoneSelection() {
var minuteDiff, localTime,
- type = $tzSelect.val();
+ type = oouiEnabled ? timezoneWidget.dropdowninput.getValue() : $tzSelect.val(),
+ val = oouiEnabled ? timezoneWidget.textinput.getValue() : $tzTextbox.val();
if ( type === 'other' ) {
// User specified time zone manually in <input>
// Grab data from the textbox, parse it.
- minuteDiff = hoursToMinutes( $tzTextbox.val() );
+ minuteDiff = hoursToMinutes( val );
} else {
// Time zone not manually specified by user
if ( type === 'guess' ) {
// Get browser timezone & fill it in
minuteDiff = -( new Date().getTimezoneOffset() );
- $tzTextbox.val( minutesToHours( minuteDiff ) );
- $tzSelect.val( 'other' );
+ if ( oouiEnabled ) {
+ timezoneWidget.textinput.setValue( minutesToHours( minuteDiff ) );
+ timezoneWidget.dropdowninput.setValue( 'other' );
+ } else {
+ $tzTextbox.val( minutesToHours( minuteDiff ) );
+ $tzSelect.val( 'other' );
+ }
} else {
- // Grab data from the $tzSelect value
+ // Grab data from the dropdown value
minuteDiff = parseInt( type.split( '|' )[ 1 ], 10 ) || 0;
}
}
$localtimeHolder.text( mw.language.convertNumber( minutesToHours( localTime ) ) );
}
- if ( $tzSelect.length && $tzTextbox.length ) {
- $tzSelect.change( updateTimezoneSelection );
- $tzTextbox.blur( updateTimezoneSelection );
- updateTimezoneSelection();
+ if ( oouiEnabled ) {
+ if ( timezoneWidget ) {
+ timezoneWidget.dropdowninput.on( 'change', updateTimezoneSelection );
+ timezoneWidget.textinput.on( 'change', updateTimezoneSelection );
+ updateTimezoneSelection();
+ }
+ } else {
+ if ( $tzSelect.length && $tzTextbox.length ) {
+ $tzSelect.change( updateTimezoneSelection );
+ $tzTextbox.blur( updateTimezoneSelection );
+ updateTimezoneSelection();
+ }
}
} );
$configMock = new HashConfig( [
'HiddenPrefs' => []
] );
- $form = $this->getMockBuilder( PreferencesForm::class )
+ $form = $this->getMockBuilder( PreferencesFormLegacy::class )
->disableOriginalConstructor()
->getMock();