Add user menu to sticky header

- Remove unused button, data from sticky header.
- Simplify template to leave sticky user menu placeholder.
- Update js to clone user menu with new ids.
- Include gadget-injected items in sticky user menu.

Bug: T289816
Change-Id: I23fde537efc2a66a2df22cd2633fbab034b73eb6
This commit is contained in:
Clare Ming 2021-09-08 20:26:33 -06:00
parent 2cbaa646ac
commit 755f10cd0b
3 changed files with 79 additions and 18 deletions

View File

@ -312,7 +312,6 @@ class SkinVector extends SkinMustache {
'heading' => 'Introduction', 'heading' => 'Introduction',
'data-primary-action' => !$this->shouldHideLanguages() ? $this->getULSButtonData() : '', 'data-primary-action' => !$this->shouldHideLanguages() ? $this->getULSButtonData() : '',
'data-button-start' => self::NO_ICON, 'data-button-start' => self::NO_ICON,
'data-button-end' => self::NO_ICON,
'data-buttons' => [ 'data-buttons' => [
self::NO_ICON, self::NO_ICON, self::NO_ICON, self::NO_ICON self::NO_ICON, self::NO_ICON, self::NO_ICON, self::NO_ICON
] ]

View File

@ -21,9 +21,9 @@
{{>Button}} {{>Button}}
{{/data-primary-action}} {{/data-primary-action}}
<div class="vector-sticky-header-icon-end"> <div class="vector-sticky-header-icon-end">
{{#data-button-end}} <div class="vector-user-links">
{{>Button}} {{! User menu items with unique ids are cloned here from the fixed header in stickyHeader.js. }}
{{/data-button-end}} </div>
</div> </div>
</div> </div>
</header> </header>

View File

@ -1,28 +1,83 @@
var var
STICKY_HEADER_ID = 'vector-sticky-header', STICKY_HEADER_ID = 'vector-sticky-header',
STICKY_HEADER_APPENDED_ID = '-sticky-header',
STICKY_HEADER_VISIBLE_CLASS = 'vector-sticky-header-visible', STICKY_HEADER_VISIBLE_CLASS = 'vector-sticky-header-visible',
FIRST_HEADING_ID = 'firstHeading'; STICKY_HEADER_USER_MENU_CONTAINER_CLASS = 'vector-sticky-header-icon-end',
FIRST_HEADING_ID = 'firstHeading',
USER_MENU_ID = 'p-personal',
VECTOR_USER_LINKS_SELECTOR = '.vector-user-links',
VECTOR_MENU_CONTENT_LIST_SELECTOR = '.vector-menu-content-list';
/** /**
* Makes sticky header functional for modern Vector. * Makes sticky header functional for modern Vector.
* *
* @param {HTMLElement} header * @param {HTMLElement} header
* @param {HTMLElement} stickyIntersection * @param {HTMLElement} stickyIntersection
* @param {HTMLElement} userMenu
* @param {HTMLElement} userMenuStickyContainer
*/ */
function makeStickyHeaderFunctional( header, stickyIntersection ) { function makeStickyHeaderFunctional(
header,
stickyIntersection,
userMenu,
userMenuStickyContainer
) {
/* eslint-disable-next-line compat/compat */ /* eslint-disable-next-line compat/compat */
var stickyObserver = new IntersectionObserver( function ( entries ) { var
if ( !entries[ 0 ].isIntersecting && entries[ 0 ].boundingClientRect.top < 0 ) { stickyObserver = new IntersectionObserver( function ( entries ) {
// Viewport has crossed the bottom edge of firstHeading so show sticky header. if ( !entries[ 0 ].isIntersecting && entries[ 0 ].boundingClientRect.top < 0 ) {
// eslint-disable-next-line mediawiki/class-doc // Viewport has crossed the bottom edge of firstHeading so show sticky header.
header.classList.add( STICKY_HEADER_VISIBLE_CLASS ); // eslint-disable-next-line mediawiki/class-doc
} else { header.classList.add( STICKY_HEADER_VISIBLE_CLASS );
// Viewport is above the bottom edge of firstHeading so hide sticky header. } else {
// eslint-disable-next-line mediawiki/class-doc // Viewport is above the bottom edge of firstHeading so hide sticky header.
header.classList.remove( STICKY_HEADER_VISIBLE_CLASS ); // eslint-disable-next-line mediawiki/class-doc
header.classList.remove( STICKY_HEADER_VISIBLE_CLASS );
}
} ),
userMenuClone = /** @type {HTMLElement} */ ( userMenu.cloneNode( true ) ),
userMenuStickyElementsWithIds = userMenuClone.querySelectorAll( '[ id ], [ data-event-name ]' ),
userMenuStickyContainerInner = /** @type {HTMLElement} */ (
userMenuStickyContainer.querySelector( VECTOR_USER_LINKS_SELECTOR )
);
// Update all ids of the cloned user menu to make them unique.
userMenuClone.id += STICKY_HEADER_APPENDED_ID;
for ( var i = 0; i < userMenuStickyElementsWithIds.length; i++ ) {
userMenuStickyElementsWithIds[ i ].id += STICKY_HEADER_APPENDED_ID;
// Update data attributes that need to be unique for click tracking IDs.
var elementCloneDataEventName = userMenuStickyElementsWithIds[ i ].getAttribute( 'data-event-name' );
if ( elementCloneDataEventName ) {
userMenuStickyElementsWithIds[ i ].setAttribute( 'data-event-name', elementCloneDataEventName += STICKY_HEADER_APPENDED_ID );
}
}
// Add gadget-injected items of the fixed user menu into the sticky header user menu.
// Only applies to gadgets running after the code above and won't apply to existing items.
mw.hook( 'util.addPortletLink' ).add( function ( /** @type {HTMLElement} */ item ) {
// Get the nav tag parent of the gadget-injected menu item. We verify that .closest is
// available for use because of feature detection in init function.
var parentNav = /** @type {HTMLElement} */ ( item.closest( 'nav' ) );
// Check if a gadget is injecting an item into the user menu.
if ( parentNav.id === 'p-personal' ) {
var
itemClone = /** @type {HTMLElement} */ ( item.cloneNode( true ) ),
userMenuCloneUl = /** @type {HTMLElement} */ (
userMenuClone.querySelector( VECTOR_MENU_CONTENT_LIST_SELECTOR )
);
if ( userMenuCloneUl ) {
// Remove data-event-name attribute if it exists on the cloned item.
itemClone.removeAttribute( 'data-event-name' );
// Update id of the cloned user menu item and add it to the cloned user menu list.
itemClone.id += STICKY_HEADER_APPENDED_ID;
userMenuCloneUl.appendChild( itemClone );
}
} }
} ); } );
// Clone the updated user menu to the sticky header.
userMenuStickyContainerInner.appendChild( userMenuClone );
stickyObserver.observe( stickyIntersection ); stickyObserver.observe( stickyIntersection );
} }
@ -30,14 +85,21 @@ module.exports = function initStickyHeader() {
var header = /** @type {HTMLElement} */ ( document.getElementById( STICKY_HEADER_ID ) ), var header = /** @type {HTMLElement} */ ( document.getElementById( STICKY_HEADER_ID ) ),
stickyIntersection = /** @type {HTMLElement} */ ( document.getElementById( stickyIntersection = /** @type {HTMLElement} */ ( document.getElementById(
FIRST_HEADING_ID FIRST_HEADING_ID
) ); ) ),
userMenu = /** @type {HTMLElement} */ ( document.getElementById( USER_MENU_ID ) ),
userMenuStickyContainer = /** @type {HTMLElement} */ ( document.getElementsByClassName(
STICKY_HEADER_USER_MENU_CONTAINER_CLASS )[ 0 ]
);
if ( !( if ( !(
stickyIntersection &&
header && header &&
header.closest &&
stickyIntersection &&
userMenu &&
userMenuStickyContainer &&
'IntersectionObserver' in window ) ) { 'IntersectionObserver' in window ) ) {
return; return;
} }
makeStickyHeaderFunctional( header, stickyIntersection ); makeStickyHeaderFunctional( header, stickyIntersection, userMenu, userMenuStickyContainer );
}; };