From f2c60983bb415dceb9484d63e388a593f530fdbb Mon Sep 17 00:00:00 2001 From: Clare Ming Date: Thu, 24 Mar 2022 14:49:22 -0600 Subject: [PATCH] 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 --- resources/skins.vector.es6/main.js | 40 +++++++++---- resources/skins.vector.es6/scrollObserver.js | 59 +++++++++++++++++--- 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/resources/skins.vector.es6/main.js b/resources/skins.vector.es6/main.js index a2975628..070d6f32 100644 --- a/resources/skins.vector.es6/main.js +++ b/resources/skins.vector.es6/main.js @@ -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 ( !( diff --git a/resources/skins.vector.es6/scrollObserver.js b/resources/skins.vector.es6/scrollObserver.js index 6fb2f276..bd17267e 100644 --- a/resources/skins.vector.es6/scrollObserver.js +++ b/resources/skins.vector.es6/scrollObserver.js @@ -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 } ); } }