Point out the limited width control

The limited control will be pointed to on page load
When wgVectorPageLoadIndicator is set to true (defaults
to false)

Clicking the button should show the indicator.

Additional change:
* Update config.json to reflect new state
(Follow up to 28ada2dc)

Bug: T333601
Change-Id: I188ed7226b9a1530e54b1aaa80caa0830bf73633
This commit is contained in:
Jon Robson 2023-04-03 14:16:52 -07:00 committed by Jdlrobson
parent a0c7b58ec6
commit 2622472983
10 changed files with 138 additions and 27 deletions

View file

@ -50,6 +50,8 @@
"vector-site-nav-label": "Site",
"vector-main-menu-label": "Main menu",
"vector-limited-width-toggle": "Toggle limited content width",
"vector-limited-width-toggle-on-popup": "You have switched your layout to full width. To go back to limited width, press this button.",
"vector-limited-width-toggle-off-popup": "You can toggle between a limited width and full width by clicking this button.",
"vector-page-tools-label": "Tools",
"vector-page-tools-general-label": "General",
"vector-page-tools-actions-label": "Actions",

View file

@ -66,6 +66,8 @@
"vector-site-nav-label": "Accessible label for site (main menu) nav landmark",
"vector-main-menu-label": "Main menu label",
"vector-limited-width-toggle": "Toggle for control to limit content width.",
"vector-limited-width-toggle-on-popup": "Hint that points out the limited width toggle control.",
"vector-limited-width-toggle-off-popup": "Hint that points out the limited width toggle control.",
"vector-page-tools-label": "Label for the page tools pinnable dropdown\n{{identical|Tools}}",
"vector-page-tools-general-label": "Label for the page tools 'General' menu\n{{identical|General}}",
"vector-page-tools-actions-label": "Label for the page tools 'Actions' menu\n{{identical|Action}}",

View file

@ -29,10 +29,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 33,
functions: 41,
lines: 40,
statements: 40
branches: 32,
functions: 40,
lines: 39,
statements: 39
}
},

View file

@ -1,4 +1,5 @@
{
"@doc": "This file describes the shape of the AB config. It exists to support jest",
"wgVectorWebABTestEnrollment": {}
"@doc": "This file describes the shape of the AB config. It exists to support jest and TypeScript.",
"VectorLimitedWidthIndicator": true,
"VectorSearchApiUrl": ""
}

View file

@ -1,5 +1,9 @@
const features = require( './features.js' );
const popupNotification = require( './popupNotification.js' );
const config = require( './config.json' );
const LIMITED_WIDTH_FEATURE_NAME = 'limited-width';
const AWARE_COOKIE_NAME = `${LIMITED_WIDTH_FEATURE_NAME}-aware`;
const TOGGLE_ID = 'toggleWidth';
/**
* Sets data attribute for click tracking purposes.
@ -20,12 +24,72 @@ function init() {
toggle.textContent = mw.msg( 'vector-limited-width-toggle' );
toggle.classList.add( 'mw-ui-icon', 'mw-ui-icon-element', 'mw-ui-button', 'vector-limited-width-toggle' );
setDataAttribute( toggle );
document.body.appendChild( toggle );
const toggleMenu = document.createElement( 'div' );
toggleMenu.setAttribute( 'class', 'vector-settings' );
toggleMenu.appendChild( toggle );
document.body.appendChild( toggleMenu );
// @ts-ignore https://github.com/wikimedia/typescript-types/pull/39
const userMayNotKnowTheyAreInExpandedMode = !mw.cookie.get( AWARE_COOKIE_NAME );
const dismiss = () => {
mw.cookie.set( AWARE_COOKIE_NAME, '1' );
};
/**
* check user has not disabled cookies by
* reading the cookie and unsetting the cookie.
*
* @return {boolean}
*/
const areCookiesEnabled = () => {
dismiss();
// @ts-ignore https://github.com/wikimedia/typescript-types/pull/39
const savedSuccessfully = mw.cookie.get( AWARE_COOKIE_NAME ) === '1';
mw.cookie.set( AWARE_COOKIE_NAME, null );
return savedSuccessfully;
};
/**
* @param {string} id this allows us to group notifications making sure only one is visible
* at any given time. All existing popups associated with ID will be removed.
* @param {number|false} timeout
*/
const showPopup = ( id, timeout = 4000 ) => {
if ( !config.VectorLimitedWidthIndicator ) {
return;
}
const label = features.isEnabled( LIMITED_WIDTH_FEATURE_NAME ) ?
'vector-limited-width-toggle-off-popup' : 'vector-limited-width-toggle-on-popup';
// possible messages:
// * vector-limited-width-toggle-off-popup
// * vector-limited-width-toggle-on-popup
popupNotification.add( toggleMenu, mw.msg( label ), id, [], timeout, dismiss );
};
/**
* FIXME: This currently loads OOUI on page load. It should be swapped out
* for a more performance friendly version before being deployed.
* See T334366.
*/
const showPageLoadPopups = () => {
showPopup( TOGGLE_ID, false );
};
toggle.addEventListener( 'click', function () {
features.toggle( LIMITED_WIDTH_FEATURE_NAME );
setDataAttribute( toggle );
window.dispatchEvent( new Event( 'resize' ) );
if ( !features.isEnabled( LIMITED_WIDTH_FEATURE_NAME ) ) {
showPopup( TOGGLE_ID );
}
if ( !features.isEnabled( LIMITED_WIDTH_FEATURE_NAME ) ) {
dismiss();
}
} );
if ( userMayNotKnowTheyAreInExpandedMode ) {
if ( areCookiesEnabled() ) {
showPageLoadPopups();
}
}
}
module.exports = init;

View file

@ -9,15 +9,18 @@
display: none;
}
.vector-settings {
position: fixed;
bottom: 8px;
right: 8px;
}
// Note on certain pages the control will have no effect e.g. Special:RecentChanges
// Defining this at 1400px is a product decision so do not change it
// (more context at https://phabricator.wikimedia.org/T326887#8540889)
@media ( min-width: 1400px ) {
.vector-limited-width-toggle {
display: block;
position: fixed;
bottom: 8px;
right: 8px;
}
//NOTE: enabled/disabled class on body.

View file

@ -70,7 +70,7 @@ function togglePinnableClasses( header ) {
/**
* Create the indicators for the pinnable element
*
* @param {string|undefined} pinnableElementId
* @param {string} pinnableElementId
*/
function addPinnableElementIndicator( pinnableElementId ) {
const dropdownSelector = document.querySelector( `#${pinnableElementId}-dropdown` );
@ -80,7 +80,7 @@ function addPinnableElementIndicator( pinnableElementId ) {
// * vector-page-tools-unpinned-popup
// * vector-main-menu-unpinned-popup
const message = mw.msg( `${pinnableElementId}-unpinned-popup` );
popupNotification.add( container, message );
popupNotification.add( container, message, pinnableElementId );
}
}

View file

@ -1,23 +1,34 @@
// Store active notifications to only show one at a time, for use inside clearHints and showHint
const /** @type {Record<string,OoUiPopupWidget>} */ activeNotification = {};
/**
* Adds and show a popup to the user to point them to the new location of the element
*
* @param {Element} container
* @param {HTMLElement} container
* @param {string} message
* @param {string} id
* @param {string[]} [classes]
* @param {number} [timeout]
* @param {boolean} [autoClose]
* @param {number|false} [timeout]
* @param {Function} [onDismiss]
* @return {JQuery.Promise<OoUiPopupWidget|undefined>}
*/
function add( container, message, classes = [], timeout = 4000, autoClose = true ) {
function add( container, message, id, classes = [], timeout = 4000, onDismiss = () => {} ) {
/**
* @type {any}
* @type {OoUiPopupWidget}
*/
let popupWidget;
// clear existing hints.
if ( id && activeNotification[ id ] ) {
remove( activeNotification[ id ] );
delete activeNotification[ id ];
}
// load oojs-ui if it's not already loaded
mw.loader.using( 'oojs-ui-core' ).then( () => {
return mw.loader.using( 'oojs-ui-core' ).then( () => {
popupWidget = new OO.ui.PopupWidget( {
$content: $( '<p>' ).text( message ),
autoClose,
padded: true,
autoClose: timeout !== false,
head: timeout === false,
anchor: true,
align: 'center',
position: 'below',
@ -25,24 +36,43 @@ function add( container, message, classes = [], timeout = 4000, autoClose = true
container
} );
popupWidget.$element.appendTo( container );
if ( popupWidget && id ) {
activeNotification[ id ] = popupWidget;
}
popupWidget.on( 'closing', () => {
onDismiss();
} );
show( popupWidget, timeout );
return popupWidget;
} );
}
/**
* Toggle the popup widget
*
* @param {any} popupWidget popupWidget from oojs-ui
* @param {OoUiPopupWidget} popupWidget popupWidget from oojs-ui
* cannot use type because it's not loaded yet
* @param {number} [timeout]
*/
function remove( popupWidget ) {
popupWidget.toggle( false );
popupWidget.$element.remove();
}
/**
* Toggle the popup widget
*
* @param {OoUiPopupWidget} popupWidget popupWidget from oojs-ui
* cannot use type because it's not loaded yet
* @param {number|false} [timeout] use false if user must dismiss it themselves.
*/
function show( popupWidget, timeout = 4000 ) {
popupWidget.toggle( true );
// @ts-ignore https://github.com/wikimedia/typescript-types/pull/40
popupWidget.toggleClipping( true );
// hide the popup after timeout ms
if ( timeout === false ) {
return;
}
setTimeout( () => {
popupWidget.toggle( false );
popupWidget.$element.remove();
remove( popupWidget );
}, timeout );
}
@ -59,5 +89,6 @@ function removeAll( selector = '.vector-popup-notification' ) {
module.exports = {
add,
remove,
removeAll
};

View file

@ -209,9 +209,11 @@ const setupTableOfContents = ( tocElement, bodyContent, initSectionObserverFn )
.contains( STICKY_HEADER_VISIBLE_CLASS );
const containerSelector = !isStickyHeaderVisible ?
'.vector-page-titlebar .vector-toc-landmark' : '#vector-sticky-header .vector-toc-landmark';
const container = document.querySelector( containerSelector );
const container = /** @type {HTMLElement} */(
document.querySelector( containerSelector )
);
if ( container ) {
popupNotification.add( container, mw.message( 'vector-toc-unpinned-popup' ).text() );
popupNotification.add( container, mw.message( 'vector-toc-unpinned-popup' ).text(), TOC_ID );
}
}

View file

@ -359,7 +359,7 @@
},
{
"name": "resources/skins.vector.js/config.json",
"config": [ "VectorSearchApiUrl" ]
"config": [ "VectorSearchApiUrl", "VectorLimitedWidthIndicator" ]
},
{
"name": "resources/skins.vector.js/tableOfContentsConfig.json",
@ -396,6 +396,8 @@
"mediawiki.util"
],
"messages": [
"vector-limited-width-toggle-on-popup",
"vector-limited-width-toggle-off-popup",
"vector-search-loader",
"vector-limited-width-toggle",
"vector-toc-beginning",
@ -485,6 +487,10 @@
}
},
"config": {
"VectorLimitedWidthIndicator": {
"value": false,
"description": "Temporary configuration flag that determines whether Vector skins can load indicators for limited width."
},
"VectorShareUserScripts": {
"value": true,
"description": "Temporary configuration flag that determines whether Vector skins should share user scripts and styles."