@@ -245,7 +245,8 @@ export async function initOverlays (environment, comms) {
245
245
const OpenInDuckPlayer = {
246
246
clickBoundElements : new Map ( ) ,
247
247
enabled : false ,
248
-
248
+ /** @type {string|null } */
249
+ lastMouseOver : null ,
249
250
bindEventsToAll : ( ) => {
250
251
if ( ! OpenInDuckPlayer . enabled ) {
251
252
return
@@ -256,38 +257,69 @@ export async function initOverlays (environment, comms) {
256
257
return VideoThumbnail . isSingleVideoURL ( element ?. getAttribute ( 'href' ) ) ||
257
258
element . getAttribute ( 'id' ) === 'media-container-link'
258
259
}
259
- const excludeAlreadyBound = ( element ) => ! OpenInDuckPlayer . clickBoundElements . has ( element )
260
-
261
260
videoLinksAndPreview
262
- . filter ( excludeAlreadyBound )
263
- . forEach ( element => {
264
- if ( isValidVideoLinkOrPreview ( element ) ) {
265
- const onClickOpenDuckPlayer = ( event ) => {
266
- event . preventDefault ( )
267
- event . stopPropagation ( )
268
-
269
- const link = event . target . closest ( 'a' )
270
-
271
- if ( link ) {
272
- const href = VideoParams . fromHref ( link . href ) ?. toPrivatePlayerUrl ( )
261
+ . forEach ( ( /** @type {HTMLElement|HTMLAnchorElement } */ element ) => {
262
+ // bail when this element was already seen
263
+ if ( OpenInDuckPlayer . clickBoundElements . has ( element ) ) return
264
+
265
+ // bail if it's not a valid element
266
+ if ( ! isValidVideoLinkOrPreview ( element ) ) return
267
+
268
+ // handle mouseover + click events
269
+ const handler = {
270
+ handleEvent ( event ) {
271
+ switch ( event . type ) {
272
+ case 'mouseover' : {
273
+ /**
274
+ * Store the element's link value on hover - this occurs just in time
275
+ * before the youtube overlay take sover the event space
276
+ */
277
+ const href = element instanceof HTMLAnchorElement
278
+ ? VideoParams . fromHref ( element . href ) ?. toPrivatePlayerUrl ( )
279
+ : null
273
280
if ( href ) {
274
- comms . openInDuckPlayerViaMessage ( { href } )
281
+ OpenInDuckPlayer . lastMouseOver = href
275
282
}
283
+ break
276
284
}
285
+ case 'click' : {
286
+ /**
287
+ * On click, the receiver might be the preview element - if
288
+ * it is, we want to use the last hovered `a` tag instead
289
+ */
290
+ event . preventDefault ( )
291
+ event . stopPropagation ( )
292
+
293
+ const link = event . target . closest ( 'a' )
294
+ const fromClosest = VideoParams . fromHref ( link ?. href ) ?. toPrivatePlayerUrl ( )
295
+
296
+ if ( fromClosest ) {
297
+ comms . openInDuckPlayerViaMessage ( { href : fromClosest } )
298
+ } else if ( OpenInDuckPlayer . lastMouseOver ) {
299
+ comms . openInDuckPlayerViaMessage ( { href : OpenInDuckPlayer . lastMouseOver } )
300
+ } else {
301
+ // could not navigate, doing nothing
302
+ }
277
303
278
- return false
304
+ break
305
+ }
306
+ }
279
307
}
308
+ }
280
309
281
- element . addEventListener ( 'click' , onClickOpenDuckPlayer , true )
310
+ // register both handlers
311
+ element . addEventListener ( 'mouseover' , handler , true )
312
+ element . addEventListener ( 'click' , handler , true )
282
313
283
- OpenInDuckPlayer . clickBoundElements . set ( element , onClickOpenDuckPlayer )
284
- }
314
+ // store the handler for removal later (eg: if settings change )
315
+ OpenInDuckPlayer . clickBoundElements . set ( element , handler )
285
316
} )
286
317
} ,
287
318
288
319
disable : ( ) => {
289
- OpenInDuckPlayer . clickBoundElements . forEach ( ( functionToRemove , element ) => {
290
- element . removeEventListener ( 'click' , functionToRemove , true )
320
+ OpenInDuckPlayer . clickBoundElements . forEach ( ( handler , element ) => {
321
+ element . removeEventListener ( 'mouseover' , handler , true )
322
+ element . removeEventListener ( 'click' , handler , true )
291
323
OpenInDuckPlayer . clickBoundElements . delete ( element )
292
324
} )
293
325
0 commit comments