@@ -252,7 +252,8 @@ export async function initOverlays (environment, comms) {
252252 const OpenInDuckPlayer = {
253253 clickBoundElements : new Map ( ) ,
254254 enabled : false ,
255-
255+ /** @type {string|null } */
256+ lastMouseOver : null ,
256257 bindEventsToAll : ( ) => {
257258 if ( ! OpenInDuckPlayer . enabled ) {
258259 return
@@ -263,38 +264,69 @@ export async function initOverlays (environment, comms) {
263264 return VideoThumbnail . isSingleVideoURL ( element ?. getAttribute ( 'href' ) ) ||
264265 element . getAttribute ( 'id' ) === 'media-container-link'
265266 }
266- const excludeAlreadyBound = ( element ) => ! OpenInDuckPlayer . clickBoundElements . has ( element )
267-
268267 videoLinksAndPreview
269- . filter ( excludeAlreadyBound )
270- . forEach ( element => {
271- if ( isValidVideoLinkOrPreview ( element ) ) {
272- const onClickOpenDuckPlayer = ( event ) => {
273- event . preventDefault ( )
274- event . stopPropagation ( )
275-
276- const link = event . target . closest ( 'a' )
277-
278- if ( link ) {
279- const href = VideoParams . fromHref ( link . href ) ?. toPrivatePlayerUrl ( )
268+ . forEach ( ( /** @type {HTMLElement|HTMLAnchorElement } */ element ) => {
269+ // bail when this element was already seen
270+ if ( OpenInDuckPlayer . clickBoundElements . has ( element ) ) return
271+
272+ // bail if it's not a valid element
273+ if ( ! isValidVideoLinkOrPreview ( element ) ) return
274+
275+ // handle mouseover + click events
276+ const handler = {
277+ handleEvent ( event ) {
278+ switch ( event . type ) {
279+ case 'mouseover' : {
280+ /**
281+ * Store the element's link value on hover - this occurs just in time
282+ * before the youtube overlay take sover the event space
283+ */
284+ const href = element instanceof HTMLAnchorElement
285+ ? VideoParams . fromHref ( element . href ) ?. toPrivatePlayerUrl ( )
286+ : null
280287 if ( href ) {
281- comms . openDuckPlayer ( { href } )
288+ OpenInDuckPlayer . lastMouseOver = href
282289 }
290+ break
283291 }
292+ case 'click' : {
293+ /**
294+ * On click, the receiver might be the preview element - if
295+ * it is, we want to use the last hovered `a` tag instead
296+ */
297+ event . preventDefault ( )
298+ event . stopPropagation ( )
299+
300+ const link = event . target . closest ( 'a' )
301+ const fromClosest = VideoParams . fromHref ( link ?. href ) ?. toPrivatePlayerUrl ( )
302+
303+ if ( fromClosest ) {
304+ comms . openDuckPlayer ( { href : fromClosest } )
305+ } else if ( OpenInDuckPlayer . lastMouseOver ) {
306+ comms . openDuckPlayer ( { href : OpenInDuckPlayer . lastMouseOver } )
307+ } else {
308+ // could not navigate, doing nothing
309+ }
284310
285- return false
311+ break
312+ }
313+ }
286314 }
315+ }
287316
288- element . addEventListener ( 'click' , onClickOpenDuckPlayer , true )
317+ // register both handlers
318+ element . addEventListener ( 'mouseover' , handler , true )
319+ element . addEventListener ( 'click' , handler , true )
289320
290- OpenInDuckPlayer . clickBoundElements . set ( element , onClickOpenDuckPlayer )
291- }
321+ // store the handler for removal later (eg: if settings change )
322+ OpenInDuckPlayer . clickBoundElements . set ( element , handler )
292323 } )
293324 } ,
294325
295326 disable : ( ) => {
296- OpenInDuckPlayer . clickBoundElements . forEach ( ( functionToRemove , element ) => {
297- element . removeEventListener ( 'click' , functionToRemove , true )
327+ OpenInDuckPlayer . clickBoundElements . forEach ( ( handler , element ) => {
328+ element . removeEventListener ( 'mouseover' , handler , true )
329+ element . removeEventListener ( 'click' , handler , true )
298330 OpenInDuckPlayer . clickBoundElements . delete ( element )
299331 } )
300332
0 commit comments