Update scroll instrument for TOC

- Leverage scrollObserver to use for TOC scroll events.
- Add new hooks to target legacy TOC intersection.
- See corresponding changes (not strict dependency) in related
WME patch 773628 for capturing scroll events from Vector TOC:
https://gerrit.wikimedia.org/r/c/mediawiki/extensions/WikimediaEvents/+/773628
- Dependency on https://github.com/wikimedia/typescript-types/pull/30
which updates mwHookInstance interface with fire property to prevent
TS error on fire method of mw.hook in scrollObserver.

Bug: T303297
Change-Id: I5c2dd5f3a25ffcb0ed03b76ae28e65eb18ad8d33
This commit is contained in:
Clare Ming 2022-03-24 14:49:22 -06:00
parent cda8df1cb2
commit f2c60983bb
2 changed files with 79 additions and 20 deletions

View File

@ -8,10 +8,13 @@ const
initTableOfContents = require( './tableOfContents.js' ),
deferUntilFrame = require( './deferUntilFrame.js' ),
TOC_ID = 'mw-panel-toc',
LEGACY_TOC_ID = 'toc',
TOC_ID_LEGACY = 'toc',
BODY_CONTENT_ID = 'bodyContent',
HEADLINE_SELECTOR = '.mw-headline',
TOC_SECTION_ID_PREFIX = 'toc-',
TOC_LEGACY_PLACEHOLDER_TAG = 'mw:tocplace',
TOC_SCROLL_HOOK = 'table_of_contents',
PAGE_TITLE_SCROLL_HOOK = 'page_title',
ABTestConfig = require( /** @type {string} */ ( './config.json' ) ).wgVectorWebABTestEnrollment || {};
/**
@ -45,38 +48,53 @@ const main = () => {
isStickyHeaderAllowed = stickyHeaderExperiment ?
stickyHeaderExperiment.isInTreatmentBucket() : stickyHeader.isStickyHeaderAllowed();
// Set up intersection observer for sticky header functionality and firing scroll event hooks
// for event logging if AB test is enabled.
// Table of contents
const tocElement = document.getElementById( TOC_ID );
const tocElementLegacy = document.getElementById( TOC_ID_LEGACY );
const bodyContent = document.getElementById( BODY_CONTENT_ID );
const tocLegacyPlaceholder = document.getElementsByTagName( TOC_LEGACY_PLACEHOLDER_TAG )[ 0 ];
const tocLegacyTargetIntersection = tocElementLegacy || tocLegacyPlaceholder;
// Set up intersection observer for sticky header and table of contents functionality
// and to fire scroll event hooks for event logging if AB tests are enabled for
// either feature.
const observer = scrollObserver.initScrollObserver(
() => {
if ( targetElement && isStickyHeaderAllowed ) {
stickyHeader.show();
}
scrollObserver.fireScrollHook( 'down' );
scrollObserver.fireScrollHook( 'down', PAGE_TITLE_SCROLL_HOOK );
if ( tocLegacyTargetIntersection ) {
scrollObserver.fireScrollHook( 'down', TOC_SCROLL_HOOK );
}
},
() => {
if ( targetElement && isStickyHeaderAllowed ) {
stickyHeader.hide();
}
scrollObserver.fireScrollHook( 'up' );
scrollObserver.fireScrollHook( 'up', PAGE_TITLE_SCROLL_HOOK );
if ( tocLegacyTargetIntersection ) {
scrollObserver.fireScrollHook( 'up', TOC_SCROLL_HOOK );
}
}
);
// Initiate observer for sticky header.
if ( isStickyHeaderAllowed ) {
stickyHeader.initStickyHeader( observer );
} else if ( targetIntersection ) {
observer.observe( targetIntersection );
}
// Table of contents
const tocElement = document.getElementById( TOC_ID );
const legacyTOCElement = document.getElementById( LEGACY_TOC_ID );
const bodyContent = document.getElementById( BODY_CONTENT_ID );
// Initiate observer for table of contents in main content.
if ( tocLegacyTargetIntersection ) {
observer.observe( tocLegacyTargetIntersection );
}
// Add event data attributes to legacy TOC
if ( legacyTOCElement ) {
legacyTOCElement.setAttribute( 'data-event-name', 'ui.toc' );
if ( tocElementLegacy ) {
tocElementLegacy.setAttribute( 'data-event-name', 'ui.toc' );
}
if ( !(

View File

@ -1,21 +1,62 @@
const
SCROLL_HOOK = 'vector.page_title_scroll',
SCROLL_CONTEXT_ABOVE = 'scrolled-above-page-title',
SCROLL_CONTEXT_BELOW = 'scrolled-below-page-title',
SCROLL_ACTION = 'scroll-to-top';
SCROLL_TITLE_HOOK = 'vector.page_title_scroll',
SCROLL_TITLE_CONTEXT_ABOVE = 'scrolled-above-page-title',
SCROLL_TITLE_CONTEXT_BELOW = 'scrolled-below-page-title',
SCROLL_TITLE_ACTION = 'scroll-to-top',
SCROLL_TOC_HOOK = 'vector.table_of_contents_scroll',
SCROLL_TOC_CONTEXT_ABOVE = 'scrolled-above-table-of-contents',
SCROLL_TOC_CONTEXT_BELOW = 'scrolled-below-table-of-contents',
SCROLL_TOC_ACTION = 'scroll-to-toc';
/**
* @typedef {Object} scrollVariables
* @property {string} scrollHook
* @property {string} scrollContextBelow
* @property {string} scrollContextAbove
* @property {string} scrollAction
*/
/**
* Return the correct variables based on hook type.
*
* @param {string} hook the type of hook
* @return {scrollVariables}
*/
function getScrollVariables( hook ) {
const scrollVariables = {};
if ( hook === 'page_title' ) {
scrollVariables.scrollHook = SCROLL_TITLE_HOOK;
scrollVariables.scrollContextBelow = SCROLL_TITLE_CONTEXT_BELOW;
scrollVariables.scrollContextAbove = SCROLL_TITLE_CONTEXT_ABOVE;
scrollVariables.scrollAction = SCROLL_TITLE_ACTION;
} else if ( hook === 'table_of_contents' ) {
scrollVariables.scrollHook = SCROLL_TOC_HOOK;
scrollVariables.scrollContextBelow = SCROLL_TOC_CONTEXT_BELOW;
scrollVariables.scrollContextAbove = SCROLL_TOC_CONTEXT_ABOVE;
scrollVariables.scrollAction = SCROLL_TOC_ACTION;
}
return scrollVariables;
}
/**
* Fire a hook to be captured by WikimediaEvents for scroll event logging.
*
* @param {string} direction the scroll direction
* @param {string} hook the hook to fire
*/
function fireScrollHook( direction ) {
function fireScrollHook( direction, hook ) {
const scrollVariables = getScrollVariables( hook );
if ( Object.keys( scrollVariables ).length === 0 && scrollVariables.constructor === Object ) {
return;
}
if ( direction === 'down' ) {
mw.hook( SCROLL_HOOK ).fire( { context: SCROLL_CONTEXT_BELOW } );
mw.hook( scrollVariables.scrollHook ).fire( {
context: scrollVariables.scrollContextBelow
} );
} else {
mw.hook( SCROLL_HOOK ).fire( {
context: SCROLL_CONTEXT_ABOVE,
action: SCROLL_ACTION
mw.hook( scrollVariables.scrollHook ).fire( {
context: scrollVariables.scrollContextAbove,
action: scrollVariables.scrollAction
} );
}
}