@@ -245,7 +245,8 @@ export async function initOverlays (environment, comms) {
245245 const OpenInDuckPlayer = {
246246 clickBoundElements : new Map ( ) ,
247247 enabled : false ,
248-
248+ /** @type {string|null } */
249+ lastMouseOver : null ,
249250 bindEventsToAll : ( ) => {
250251 if ( ! OpenInDuckPlayer . enabled ) {
251252 return
@@ -256,38 +257,69 @@ export async function initOverlays (environment, comms) {
256257 return VideoThumbnail . isSingleVideoURL ( element ?. getAttribute ( 'href' ) ) ||
257258 element . getAttribute ( 'id' ) === 'media-container-link'
258259 }
259- const excludeAlreadyBound = ( element ) => ! OpenInDuckPlayer . clickBoundElements . has ( element )
260-
261260 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
273280 if ( href ) {
274- comms . openInDuckPlayerViaMessage ( { href } )
281+ OpenInDuckPlayer . lastMouseOver = href
275282 }
283+ break
276284 }
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+ }
277303
278- return false
304+ break
305+ }
306+ }
279307 }
308+ }
280309
281- element . addEventListener ( 'click' , onClickOpenDuckPlayer , true )
310+ // register both handlers
311+ element . addEventListener ( 'mouseover' , handler , true )
312+ element . addEventListener ( 'click' , handler , true )
282313
283- OpenInDuckPlayer . clickBoundElements . set ( element , onClickOpenDuckPlayer )
284- }
314+ // store the handler for removal later (eg: if settings change )
315+ OpenInDuckPlayer . clickBoundElements . set ( element , handler )
285316 } )
286317 } ,
287318
288319 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 )
291323 OpenInDuckPlayer . clickBoundElements . delete ( element )
292324 } )
293325
0 commit comments