@@ -252,7 +252,8 @@ export async function initOverlays (environment, comms) {
252
252
const OpenInDuckPlayer = {
253
253
clickBoundElements : new Map ( ) ,
254
254
enabled : false ,
255
-
255
+ /** @type {string|null } */
256
+ lastMouseOver : null ,
256
257
bindEventsToAll : ( ) => {
257
258
if ( ! OpenInDuckPlayer . enabled ) {
258
259
return
@@ -263,38 +264,69 @@ export async function initOverlays (environment, comms) {
263
264
return VideoThumbnail . isSingleVideoURL ( element ?. getAttribute ( 'href' ) ) ||
264
265
element . getAttribute ( 'id' ) === 'media-container-link'
265
266
}
266
- const excludeAlreadyBound = ( element ) => ! OpenInDuckPlayer . clickBoundElements . has ( element )
267
-
268
267
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
280
287
if ( href ) {
281
- comms . openDuckPlayer ( { href } )
288
+ OpenInDuckPlayer . lastMouseOver = href
282
289
}
290
+ break
283
291
}
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
+ }
284
310
285
- return false
311
+ break
312
+ }
313
+ }
286
314
}
315
+ }
287
316
288
- element . addEventListener ( 'click' , onClickOpenDuckPlayer , true )
317
+ // register both handlers
318
+ element . addEventListener ( 'mouseover' , handler , true )
319
+ element . addEventListener ( 'click' , handler , true )
289
320
290
- OpenInDuckPlayer . clickBoundElements . set ( element , onClickOpenDuckPlayer )
291
- }
321
+ // store the handler for removal later (eg: if settings change )
322
+ OpenInDuckPlayer . clickBoundElements . set ( element , handler )
292
323
} )
293
324
} ,
294
325
295
326
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 )
298
330
OpenInDuckPlayer . clickBoundElements . delete ( element )
299
331
} )
300
332
0 commit comments