diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1edbc0a0..bc31c481 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [ 17, 'latest' ] + node: [ 21 ] steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 105b065a..d1f115e4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules/ .DS_Store storybook-static coverage/ +cypress/screenshots diff --git a/cypress/e2e/active.cy.js b/cypress/e2e/active.cy.js index 550b8d47..c734902e 100644 --- a/cypress/e2e/active.cy.js +++ b/cypress/e2e/active.cy.js @@ -10,7 +10,10 @@ describe( 'Check the Hover and Click Event', () => { it( 'Check the Preview by hovering over the Span and then leaving', () => { - preview.getPreviewSpan().first().trigger( 'mouseenter', 'right' ) + preview.getPreviewSpan().first().trigger( 'pointerenter', { + position: 'right', + pointerType: 'mouse' + } ) preview.checkPreview() diff --git a/cypress/e2e/mobile-gallery.cy.js b/cypress/e2e/mobile-gallery.cy.js index ac24c69b..b4f2dd79 100644 --- a/cypress/e2e/mobile-gallery.cy.js +++ b/cypress/e2e/mobile-gallery.cy.js @@ -12,7 +12,7 @@ describe( 'Check the Gallery Pages in Mobile View', () => { it( 'Check the Gallery Pages Movement by Swiping', () => { // Open the Preview - preview.getPreviewSpan().first().click() + preview.getPreviewSpan().first().trigger( 'pointerenter', { pointerType: 'touch' } ) const i = 0 // Check if the Images exist preview.getBodyGalleryImages().its( 'length' ).then( () => { diff --git a/cypress/e2e/mobile-layout.cy.js b/cypress/e2e/mobile-layout.cy.js index 0c041fe3..6a2db1b4 100644 --- a/cypress/e2e/mobile-layout.cy.js +++ b/cypress/e2e/mobile-layout.cy.js @@ -11,7 +11,7 @@ describe( 'Check the different Layout in Mobile Wikipedia Preview ', () => { it( 'Check the Wikipedia Preview in Standard Layout', () => { // Opens Mobile Preview - preview.getPreviewSpan().eq( 0 ).click() + preview.getPreviewSpan().eq( 0 ).trigger( 'pointerenter', { pointerType: 'touch' } ) // Checks the preview preview.checkMobilePreview() preview.checkPreview() @@ -23,7 +23,7 @@ describe( 'Check the different Layout in Mobile Wikipedia Preview ', () => { it.skip( 'Check the Wikipedia Preview in Offline Layout', () => { goOffline() // Opens Mobile Preview - preview.getPreviewSpan().first().click() + preview.getPreviewSpan().first().trigger( 'pointerenter', { pointerType: 'touch' } ) // Checks the preview preview.checkMobilePreview() preview.checkPreviewOffline() @@ -35,7 +35,7 @@ describe( 'Check the different Layout in Mobile Wikipedia Preview ', () => { it( 'Check the Wikipedia Preview in Error Layout', () => { // Opens Mobile Preview - preview.getPreviewSpan().eq( 1 ).click() + preview.getPreviewSpan().eq( 1 ).trigger( 'pointerenter', { pointerType: 'touch' } ) // Checks the preview preview.checkMobilePreview() preview.checkPreviewError() @@ -46,7 +46,7 @@ describe( 'Check the different Layout in Mobile Wikipedia Preview ', () => { it( 'Check the Wikipedia Preview in Disambiguation Layout', () => { // Opens Mobile Preview - preview.getPreviewSpan().eq( 2 ).click() + preview.getPreviewSpan().eq( 2 ).trigger( 'pointerenter', { pointerType: 'touch' } ) // Checks the preview preview.checkMobilePreview() preview.checkPreview() diff --git a/src/event.js b/src/event.js index 657472f9..81f84a3c 100644 --- a/src/event.js +++ b/src/event.js @@ -1,4 +1,4 @@ -import { isTouch, isVerticallyScrollable } from './utils' +import { isMobile, isVerticallyScrollable } from './utils' import { showFullscreenGallery } from './gallery/fullscreen' export const customEvents = ( popup ) => { @@ -84,13 +84,13 @@ export const customEvents = ( popup ) => { } ) } - if ( isTouch ) { + if ( element.component.closeBtn ) { addEventListener( element.component.closeBtn, 'click', popup.hide ) } - if ( isTouch ) { + if ( isMobile ) { const darkScreen = document.querySelector( '.wp-dark-screen' ) - addEventListener( darkScreen, 'click', popup.hide, true ) + addEventListener( darkScreen, 'pointerup', popup.hide, true ) } else { addEventListener( element, 'mouseleave', onMouseLeave ) addEventListener( element.currentTargetElement, 'mouseleave', onMouseLeave ) diff --git a/src/index.js b/src/index.js index 56d0442f..e6272d40 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,8 @@ import { createPopup } from './popup' import { createTouchPopup } from './touchPopup' import { renderPreview, renderLoading, renderError, renderDisambiguation, renderOffline } from './preview' import { - getWikipediaAttrFromUrl, buildWikipediaUrl, isTouch, getDir, isOnline, - version, getAnalyticsQueryParam, getElement + getWikipediaAttrFromUrl, buildWikipediaUrl, getDir, isOnline, + version, getAnalyticsQueryParam, getElement, isMobile } from './utils' const invokeCallback = ( events, name, params ) => { @@ -23,7 +23,7 @@ const invokeCallback = ( events, name, params ) => { // getPreviewHtml is meant to be used by the Wordpress plugin only const getPreviewHtml = ( title, lang, callback ) => { requestPagePreviewWithMedia( lang, title, ( data ) => { - callback( renderPreview( lang, data, isTouch ) ) + callback( renderPreview( lang, data, isMobile ) ) } ) } @@ -74,7 +74,7 @@ function init( { } ) { popupContainer = getElement( popupContainer ) || document.body const globalLang = lang - const popup = isTouch ? + const popup = isMobile ? createTouchPopup( popupContainer ) : createPopup( popupContainer ) const popupEvents = customEvents( popup ) @@ -85,6 +85,7 @@ function init( { const showPopup = ( e, refresh = false ) => { e.preventDefault() + e.stopPropagation() const popupId = Date.now() const { currentTarget } = refresh ? last : e @@ -107,7 +108,7 @@ function init( { popup.loading = true popup.dir = dir popup.show( - renderLoading( isTouch, localLang, dir, currentColorScheme ), + renderLoading( isMobile, localLang, dir, currentColorScheme ), currentTarget, pointerPosition ) @@ -123,7 +124,7 @@ function init( { popup.title = title if ( data.type === 'standard' ) { popup.show( - renderPreview( localLang, data, isTouch, currentColorScheme ), + renderPreview( localLang, data, isMobile, currentColorScheme ), currentTarget, pointerPosition ) @@ -131,10 +132,10 @@ function init( { invokeCallback( events, 'onShow', [ title, localLang, 'standard' ] ) } else if ( data.type === 'disambiguation' ) { const content = data.extractHtml ? - renderPreview( localLang, data, isTouch, currentColorScheme ) : + renderPreview( localLang, data, isMobile, currentColorScheme ) : // fallback message when no extract is found on disambiguation page renderDisambiguation( - isTouch, + isMobile, localLang, data.title, data.dir, @@ -150,14 +151,14 @@ function init( { } else { if ( isOnline() ) { popup.show( - renderError( isTouch, localLang, title, dir, currentColorScheme ), + renderError( isMobile, localLang, title, dir, currentColorScheme ), currentTarget, pointerPosition ) invokeCallback( events, 'onShow', [ title, localLang, 'error' ] ) } else { popup.show( - renderOffline( isTouch, localLang, dir, currentColorScheme ), + renderOffline( isMobile, localLang, dir, currentColorScheme ), currentTarget, pointerPosition ) @@ -189,18 +190,34 @@ function init( { } ) } - popup.subscribe( popupEvents ) + const onPointerEnter = ( pointerEvent ) => { + showPopup( pointerEvent ) + } + + const registerPreviewEvents = ( node ) => { + node.addEventListener( 'pointerenter', onPointerEnter ) + } + + const preventTapFromNavigatingLink = ( node ) => { + // The click event still receives a MouseEvent instead of the newer PointerEvent + // in some browsers so we have to grab the pointerType from the preceding pointerdown event. + let currentPointerType = null + node.addEventListener( 'pointerdown', ( e ) => { + currentPointerType = e.pointerType + } ) + node.addEventListener( 'click', ( e ) => { + if ( currentPointerType === 'touch' ) { + e.preventDefault() + e.stopPropagation() + } + } ) + } forEachRoot( root, ( localRoot ) => { Array.prototype.forEach.call( localRoot.querySelectorAll( selector ), ( node ) => { - if ( isTouch ) { - node.addEventListener( 'click', showPopup ) - } else { - node.addEventListener( 'mouseenter', showPopup ) - } - + registerPreviewEvents( node ) foundSelectorLinks.push( { text: node.textContent, title: node.getAttribute( 'data-wp-title' ) || node.textContent, @@ -219,11 +236,8 @@ function init( { if ( matches ) { node.setAttribute( 'data-wp-title', matches.title ) node.setAttribute( 'data-wp-lang', matches.lang ) - if ( isTouch ) { - node.addEventListener( 'click', showPopup ) - } else { - node.addEventListener( 'mouseenter', showPopup ) - } + registerPreviewEvents( node ) + preventTapFromNavigatingLink( node ) foundDetectLinks.push( { text: node.textContent, @@ -236,18 +250,20 @@ function init( { } ) } + popup.subscribe( popupEvents ) + if ( debug ) { /* eslint-disable no-console */ console.group( 'Wikipedia Preview [debug mode]' ) console.group( `Searching for "${ selector }" inside ${ root }, Total links found: ${ foundSelectorLinks.length }` ) foundSelectorLinks.forEach( ( link, index ) => { - console.log( index + 1, `${ link.text } -> ${ decodeURI( buildWikipediaUrl( link.lang, link.title, isTouch, false ) ) }` ) + console.log( index + 1, `${ link.text } -> ${ decodeURI( buildWikipediaUrl( link.lang, link.title, isMobile, false ) ) }` ) } ) console.groupEnd() if ( detectLinks ) { console.group( `Searching for links to Wikipedia, Total links found: ${ foundDetectLinks.length }` ) foundDetectLinks.forEach( ( link, index ) => { - console.log( index + 1, `${ link.text } -> ${ decodeURI( buildWikipediaUrl( link.lang, link.title, isTouch, false ) ) }` ) + console.log( index + 1, `${ link.text } -> ${ decodeURI( buildWikipediaUrl( link.lang, link.title, isMobile, false ) ) }` ) } ) console.groupEnd() } diff --git a/src/preview.js b/src/preview.js index dcfb2199..a7f47fb4 100644 --- a/src/preview.js +++ b/src/preview.js @@ -3,14 +3,14 @@ import { buildWikipediaUrl, getLinkIconSvg } from './utils' import { getGallery } from './gallery' import '../style/preview.less' -const getPreviewHeader = ( lang, isTouch, imageUrl = '', media = [] ) => { +const getPreviewHeader = ( lang, imageUrl = '', media = [] ) => { const showThumbnail = imageUrl !== '' && media.length > 0 && media.length < 3 const thumbnail = imageUrl || media[ 0 ] && media[ 0 ].thumb return `
${ showThumbnail ? `
` : '' }
- ${ isTouch ? '
' : '' } +
`.trim() } @@ -29,23 +29,23 @@ const getPreviewBody = ( type, message, cta ) => { `.trim() } -const getReadOnWikiCta = ( lang, title, isTouch ) => { - return `${ msg( lang, 'read-on-wiki' ) }` +const getReadOnWikiCta = ( lang, title, isMobile ) => { + return `${ msg( lang, 'read-on-wiki' ) }` } const render = ( - lang, isTouch, dir, headerContent, bodyContent, prefersColorScheme + lang, isMobile, dir, headerContent, bodyContent, prefersColorScheme ) => { const colorScheme = prefersColorScheme === 'detect' ? '' : `wikipediapreview-${ prefersColorScheme }-theme` return ` -
+
${ headerContent } ${ bodyContent }
`.trim() } -const renderPreview = ( lang, data, isTouch, prefersColorScheme ) => { +const renderPreview = ( lang, data, isMobile, prefersColorScheme ) => { const imageUrl = data.imgUrl, bodyContent = ` ${ getGallery( data.media ) } @@ -53,7 +53,7 @@ const renderPreview = ( lang, data, isTouch, prefersColorScheme ) => { ${ data.extractHtml }