Merge "Point out the limited width control"

This commit is contained in:
jenkins-bot 2023-04-14 18:00:14 +00:00 committed by Gerrit Code Review
commit 1f95d60531
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."