Sidebar persistence for logged-in users in modern Vector.

- Creates a new user-preference called 'VectorSidebarVisible'
which stores the sidebar hidden/collapsed state for logged-in
users.
- Updates that user-preference on the client whenever the sidebar
is expanded or collapsed.
- Refactors the sidebar related javascript into a separate file.

Bug: T255727
Change-Id: Ib1ce934f3646cd8feebf0d3b15c38b5b969ec957
This commit is contained in:
Jan Drewniak 2020-07-01 00:39:09 +02:00
parent 36a1516f96
commit 1fac82f895
8 changed files with 139 additions and 29 deletions

View File

@ -77,6 +77,11 @@ final class Constants {
*/
public const PREF_KEY_SKIN_VERSION = 'VectorSkinVersion';
/**
* @var string
*/
public const PREF_KEY_SIDEBAR_VISIBLE = 'VectorSidebarVisible';
// These are used in the Feature Management System.
/**
* Also known as `$wgFullyInitialised`. Set to true in core/includes/Setup.php.

View File

@ -129,6 +129,10 @@ class Hooks {
// this state to determine whether to show or hide the whole section.
'hide-if' => [ '!==', 'wpskin', Constants::SKIN_NAME ]
],
Constants::PREF_KEY_SIDEBAR_VISIBLE => [
'type' => 'api',
'default' => self::getConfig( Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_AUTHORISED_USER )
],
];
// Seek the skin preference section to add Vector preferences just below it.

View File

@ -171,7 +171,6 @@ class VectorTemplate extends BaseTemplate {
'main-page-href' => $mainPageHref,
'data-sidebar' => $this->buildSidebar(),
// [todo] fetch user preference when logged in (T246427).
'sidebar-visible' => $this->isSidebarVisible(),
'msg-vector-action-toggle-sidebar' => $this->msg( 'vector-action-toggle-sidebar' )->text(),
] + $this->getMenuProps();
@ -269,9 +268,19 @@ class VectorTemplate extends BaseTemplate {
private function isSidebarVisible() {
$skin = $this->getSkin();
if ( $skin->getUser()->isLoggedIn() ) {
return $this->getConfig()->get(
$userPrefSidebarState = $skin->getUser()->getOption(
Constants::PREF_KEY_SIDEBAR_VISIBLE
);
$defaultLoggedinSidebarState = $this->getConfig()->get(
Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_AUTHORISED_USER
);
// If the sidebar user preference has been set, return that value,
// if not, then the default sidebar state for logged-in users.
return ( $userPrefSidebarState !== null )
? (bool)$userPrefSidebarState
: $defaultLoggedinSidebarState;
}
return $this->getConfig()->get(
Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_ANONYMOUS_USER

View File

@ -1,3 +1,9 @@
interface MwApi {
saveOption( name: string, value: unknown ): JQuery.Promise<any>;
}
type MwApiConstructor = new( options?: Object ) => MwApi;
interface MediaWiki {
util: {
/**
@ -15,6 +21,10 @@ interface MediaWiki {
*/
debounce(delay: number, callback: Function): () => void;
};
Api: MwApiConstructor;
config: {
get( configKey: string|null ): string;
}
}
declare const mw: MediaWiki;

View File

@ -0,0 +1,89 @@
/**
* Javascsript enhancement to the collapsible sidebar.
*
* The sidebar provides basic show/hide functionality with CSS
* but Javacript is used for progressive enhancements.
*
* JS sidebar enhancements include:
* - Update aria-roles based on expanded/collapsed state.
* - Update button icon based on expanded/collapsed state.
* - Persist the sidebar state for logged-in users.
*
*/
/** @interface MwApiConstructor */
/** @interface CheckboxHack */
/** @interface MwApi */
/** @type {CheckboxHack} */ var checkboxHack =
require( /** @type {string} */( 'mediawiki.page.ready' ) ).checkboxHack;
var SIDEBAR_BUTTON_ID = 'mw-sidebar-button',
SIDEBAR_CHECKBOX_ID = 'mw-sidebar-checkbox',
SIDEBAR_PREFERENCE_NAME = 'VectorSidebarVisible';
var debounce = require( /** @type {string} */ ( 'mediawiki.util' ) ).debounce;
/** @type {MwApi} */ var api;
/**
* Improve the interactivity of the sidebar panel by binding optional checkbox hack enhancements
* for focus and `aria-expanded`. Also, flip the icon image on click.
*
* @param {HTMLElement|null} checkbox
* @param {HTMLElement|null} button
* @return {void}
*/
function initCheckboxHack( checkbox, button ) {
if ( checkbox instanceof HTMLInputElement && button ) {
checkboxHack.bindToggleOnClick( checkbox, button );
checkboxHack.bindUpdateAriaExpandedOnInput( checkbox, button );
checkboxHack.updateAriaExpanded( checkbox, button );
}
}
/**
* Execute a debounced API request to save the sidebar user preference.
* The request is meant to fires 1 second after the last click on
* the sidebar button.
*
* @param {HTMLInputElement} checkbox
* @return {any}
*/
function saveSidebarState( checkbox ) {
return debounce( 1000, function () {
api = api || new mw.Api();
api.saveOption( SIDEBAR_PREFERENCE_NAME, checkbox.checked ? 1 : 0 );
} );
}
/**
* Bind the event handler that saves the sidebar state to the click event
* on the sidebar button.
*
* @param {HTMLElement|null} checkbox
* @param {HTMLElement|null} button
*/
function bindSidebarClickEvent( checkbox, button ) {
if ( checkbox instanceof HTMLInputElement && button ) {
button.addEventListener( 'click', saveSidebarState( checkbox ) );
}
}
/**
* Initialize all JS sidebar enhancements.
*
* @param {Window} window
*/
function init( window ) {
var checkbox = window.document.getElementById( SIDEBAR_CHECKBOX_ID ),
button = window.document.getElementById( SIDEBAR_BUTTON_ID );
initCheckboxHack( checkbox, button );
if ( mw.config.get( 'wgUserName' ) ) {
bindSidebarClickEvent( checkbox, button );
}
}
module.exports = {
init: init
};

View File

@ -1,11 +1,6 @@
/**
* @external CheckboxHack
*/
/** @type {CheckboxHack} */ var checkboxHack =
require( /** @type {string} */ ( 'mediawiki.page.ready' ) ).checkboxHack;
var collapsibleTabs = require( '../skins.vector.legacy.js/collapsibleTabs.js' );
var vector = require( '../skins.vector.legacy.js/vector.js' );
var collapsibleTabs = require( '../skins.vector.legacy.js/collapsibleTabs.js' ),
vector = require( '../skins.vector.legacy.js/vector.js' ),
sidebar = require( './sidebar.js' );
/**
* Wait for first paint before calling this function. That's its whole purpose.
@ -39,24 +34,6 @@ function enableCssAnimations( document ) {
document.documentElement.classList.add( 'vector-animations-ready' );
}
/**
* Improve the interactivity of the sidebar panel by binding optional checkbox hack enhancements
* for focus and `aria-expanded`. Also, flip the icon image on click.
*
* @param {Document} document
* @return {void}
*/
function initSidebar( document ) {
var checkbox = document.getElementById( 'mw-sidebar-checkbox' );
var button = document.getElementById( 'mw-sidebar-button' );
if ( checkbox instanceof HTMLInputElement && button ) {
checkboxHack.bindToggleOnClick( checkbox, button );
checkboxHack.bindUpdateAriaExpandedOnInput( checkbox, button );
checkboxHack.updateAriaExpanded( checkbox, button );
}
}
/**
* @param {Window} window
* @return {void}
@ -64,8 +41,8 @@ function initSidebar( document ) {
function main( window ) {
enableCssAnimations( window.document );
collapsibleTabs.init();
sidebar.init( window );
$( vector.init );
initSidebar( window.document );
}
main( window );

View File

@ -82,6 +82,7 @@
"skins.vector.js": {
"packageFiles": [
"resources/skins.vector.js/skin.js",
"resources/skins.vector.js/sidebar.js",
"resources/skins.vector.legacy.js/collapsibleTabs.js",
"resources/skins.vector.legacy.js/vector.js"
],

View File

@ -37,6 +37,7 @@ class VectorHooksTest extends \MediaWikiTestCase {
'VectorShowSkinPreferences' => true,
// '1' is Legacy.
'VectorDefaultSkinVersionForExistingAccounts' => '1',
'VectorDefaultSidebarVisibleForAuthorisedUser' => true
] );
$this->setService( 'Vector.Config', $config );
@ -60,6 +61,10 @@ class VectorHooksTest extends \MediaWikiTestCase {
'default' => '1',
'hide-if' => [ '!==', 'wpskin', 'vector' ]
],
'VectorSidebarVisible' => [
'type' => 'api',
'default' => true
],
'bar' => []
],
'Preferences are inserted directly after skin.'
@ -74,6 +79,7 @@ class VectorHooksTest extends \MediaWikiTestCase {
'VectorShowSkinPreferences' => true,
// '1' is Legacy.
'VectorDefaultSkinVersionForExistingAccounts' => '1',
'VectorDefaultSidebarVisibleForAuthorisedUser' => true
] );
$this->setService( 'Vector.Config', $config );
@ -96,6 +102,10 @@ class VectorHooksTest extends \MediaWikiTestCase {
'default' => '1',
'hide-if' => [ '!==', 'wpskin', 'vector' ]
],
'VectorSidebarVisible' => [
'type' => 'api',
'default' => true
],
],
'Preferences are appended.'
);
@ -109,6 +119,7 @@ class VectorHooksTest extends \MediaWikiTestCase {
'VectorShowSkinPreferences' => true,
// '2' is latest.
'VectorDefaultSkinVersionForExistingAccounts' => '2',
'VectorDefaultSidebarVisibleForAuthorisedUser' => true
] );
$this->setService( 'Vector.Config', $config );
@ -131,6 +142,10 @@ class VectorHooksTest extends \MediaWikiTestCase {
'default' => '0',
'hide-if' => [ '!==', 'wpskin', 'vector' ]
],
'VectorSidebarVisible' => [
'type' => 'api',
'default' => true
],
],
'Legacy skin version is disabled.'
);