@ -2,24 +2,7 @@
content script running on youtube . com
* /
let browserType = getBrowser ( ) ;
// boilerplate to dedect browser type api
function getBrowser ( ) {
if ( typeof chrome !== "undefined" ) {
if ( typeof browser !== "undefined" ) {
console . log ( "detected firefox" ) ;
return browser ;
} else {
console . log ( "detected chrome" ) ;
return chrome ;
}
} else {
console . log ( "failed to dedect browser" ) ;
throw "browser detection error"
} ;
}
'use strict' ;
const downloadIcon = ` <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox = "0 0 500 500" style = "enable-background:new 0 0 500 500;" xml : space = "preserve" >
@ -100,308 +83,515 @@ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="pres
< / g >
< / g >
< / g >
< / s v g > `
function buildButtonDiv ( ) {
var buttonDiv = document . createElement ( "div" ) ;
buttonDiv . setAttribute ( "id" , "ta-channel-button" ) ;
Object . assign ( buttonDiv . style , {
display : "flex" ,
alignItems : "center" ,
backgroundColor : "#00202f" ,
color : "#fff" ,
fontSize : "14px" ,
padding : "5px" ,
margin : "0 5px" ,
borderRadius : "8px" ,
} ) ;
return buttonDiv
}
< / s v g > ` ;
function buildSubLink ( channelContainer ) {
var subLink = document . createElement ( "span" ) ;
subLink . innerText = "Subscribe" ;
subLink . addEventListener ( 'click' , e => {
e . preventDefault ( ) ;
var currentLocation = window . location . href ;
console . log ( "subscribe to: " + currentLocation ) ;
sendUrl ( currentLocation , "subscribe" , subLink ) ;
} ) ;
subLink . addEventListener ( "mouseover" , e => {
let subText
if ( window . location . pathname == "/watch" ) {
var currentLocation = window . location . href ;
subText = currentLocation ;
} else {
subText = channelContainer . querySelector ( "#text" ) . textContent ;
} ;
e . target . title = "TA Subscribe: " + subText ;
} ) ;
Object . assign ( subLink . style , {
padding : "5px" ,
cursor : "pointer" ,
} ) ;
return subLink
}
const defaultIcon = ` <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>minus-thick</title><path d="M20 14H4V10H20" /></svg> ` ;
function buildSpacer ( ) {
var spacer = document . createElement ( "span" ) ;
spacer . innerText = "|" ;
let browserType = getBrowser ( ) ;
return spacer
// boilerplate to dedect browser type api
function getBrowser ( ) {
if ( typeof chrome !== 'undefined' ) {
if ( typeof browser !== 'undefined' ) {
console . log ( 'detected firefox' ) ;
return browser ;
} else {
console . log ( 'detected chrome' ) ;
return chrome ;
}
} else {
console . log ( 'failed to dedect browser' ) ;
throw 'browser detection error' ;
}
}
function buildDlLink ( channelContainer ) {
var dlLink = document . createElement ( "span" ) ;
dlLink . innerHTML = downloadIcon ;
function getChannelContainers ( ) {
const elements = document . querySelectorAll ( '.yt-flexible-actions-view-model-wiz, #owner' ) ;
const channelContainerNodes = [ ] ;
dlLink . addEventListener ( 'click' , e => {
e . preventDefault ( ) ;
var currentLocation = window . location . href ;
console . log ( "download: " + currentLocation )
sendUrl ( currentLocation , "download" , dlLink ) ;
} ) ;
dlLink . addEventListener ( "mouseover" , e => {
let subText
if ( window . location . pathname == "/watch" ) {
var currentLocation = window . location . href ;
subText = currentLocation ;
} else {
subText = channelContainer . querySelector ( "#text" ) . textContent ;
} ;
e . target . title = "TA Download: " + subText ;
} ) ;
Object . assign ( dlLink . style , {
filter : "invert()" ,
width : "20px" ,
padding : "0 5px" ,
cursor : "pointer" ,
} ) ;
elements . forEach ( element => {
if ( isElementVisible ( element ) ) {
channelContainerNodes . push ( element ) ;
}
} ) ;
return dlLink
return channelContainerNodes ;
}
function isElementVisible ( element ) {
return element . offsetWidth > 0 || element . offsetHeight > 0 || element . getClientRects ( ) . length > 0 ;
}
function buildChannelButton ( channelContainer ) {
function ensureTALinks ( ) {
let channelContainerNodes = getChannelContainers ( ) ;
for ( let channelContainer of channelContainerNodes ) {
channelContainer = adjustOwner ( channelContainer ) ;
if ( channelContainer . hasTA ) continue ;
let channelButton = buildChannelButton ( channelContainer ) ;
channelContainer . appendChild ( channelButton ) ;
channelContainer . hasTA = true ;
}
let titleContainerNodes = getTitleContainers ( ) ;
for ( let titleContainer of titleContainerNodes ) {
let parent = getNearestH3 ( titleContainer ) ;
if ( ! parent ) continue ;
if ( parent . hasTA ) continue ;
let videoButton = buildVideoButton ( titleContainer ) ;
if ( videoButton == null ) continue ;
processTitle ( parent ) ;
parent . appendChild ( videoButton ) ;
parent . hasTA = true ;
}
}
ensureTALinks = throttled ( ensureTALinks , 700 ) ;
var buttonDiv = buildButtonDiv ( )
var subLink = buildSubLink ( channelContainer ) ;
buttonDiv . appendChild ( subLink )
function adjustOwner ( channelContainer ) {
return channelContainer . querySelector ( '#buttons' ) || channelContainer ;
}
var spacer = buildSpacer ( )
buttonDiv . appendChild ( spacer ) ;
function buildChannelButton ( channelContainer ) {
let channelHandle = getChannelHandle ( channelContainer ) ;
channelContainer . taDerivedHandle = channelHandle ;
let buttonDiv = buildChannelButtonDiv ( ) ;
let channelSubButton = buildChannelSubButton ( channelHandle ) ;
buttonDiv . appendChild ( channelSubButton ) ;
channelContainer . taSubButton = channelSubButton ;
let spacer = buildSpacer ( ) ;
buttonDiv . appendChild ( spacer ) ;
let channelDownloadButton = buildChannelDownloadButton ( ) ;
buttonDiv . appendChild ( channelDownloadButton ) ;
channelContainer . taDownloadButton = channelDownloadButton ;
if ( ! channelContainer . taObserver ) {
function updateButtonsIfNecessary ( ) {
let newHandle = getChannelHandle ( channelContainer ) ;
if ( channelContainer . taDerivedHandle === newHandle ) return ;
console . log ( ` updating handle from ${ channelContainer . taDerivedHandle } to ${ newHandle } ` ) ;
channelContainer . taDerivedHandle = newHandle ;
let channelSubButton = buildChannelSubButton ( newHandle ) ;
channelContainer . taSubButton . replaceWith ( channelSubButton ) ;
channelContainer . taSubButton = channelSubButton ;
let channelDownloadButton = buildChannelDownloadButton ( ) ;
channelContainer . taDownloadButton . replaceWith ( channelDownloadButton ) ;
channelContainer . taDownloadButton = channelDownloadButton ;
}
channelContainer . taObserver = new MutationObserver ( throttled ( updateButtonsIfNecessary , 100 ) ) ;
channelContainer . taObserver . observe ( channelContainer , {
attributes : true ,
childList : true ,
subtree : true ,
} ) ;
}
var dlLink = buildDlLink ( channelContainer )
buttonDiv . appendChild ( dlLink ) ;
return buttonDiv ;
}
return buttonDiv
function getChannelHandle ( channelContainer ) {
let channelHandle ;
const videoOwnerRenderer = channelContainer . querySelector ( '.ytd-video-owner-renderer' ) ;
if ( ! videoOwnerRenderer ) {
const channelHandleContainer = document . querySelector (
'.yt-content-metadata-view-model-wiz__metadata-text'
) ;
channelHandle = channelHandleContainer ? channelHandleContainer . innerText : null ;
} else {
const href = videoOwnerRenderer . href ;
if ( href ) {
const urlObj = new URL ( href ) ;
channelHandle = urlObj . pathname . split ( '/' ) [ 1 ] ;
}
}
return channelHandle ;
}
function getChannelContainers ( ) {
var nodes = document . querySelectorAll ( "#inner-header-container, #owner" ) ;
return nodes
function buildChannelButtonDiv ( ) {
let buttonDiv = document . createElement ( 'div' ) ;
buttonDiv . classList . add ( 'ta-channel-button' ) ;
Object . assign ( buttonDiv . style , {
display : 'flex' ,
alignItems : 'center' ,
backgroundColor : '#00202f' ,
color : '#fff' ,
fontSize : '14px' ,
padding : '5px' ,
'margin-left' : '8px' ,
borderRadius : '18px' ,
} ) ;
return buttonDiv ;
}
function getThubnailContainers ( ) {
var nodes = document . querySelectorAll ( '#thumbnail' ) ;
return nodes
function buildChannelSubButton ( channelHandle ) {
let channelSubButton = document . createElement ( 'span' ) ;
channelSubButton . innerText = 'Checking...' ;
channelSubButton . title = ` TA Subscribe: ${ channelHandle } ` ;
channelSubButton . setAttribute ( 'data-id' , channelHandle ) ;
channelSubButton . setAttribute ( 'data-type' , 'channel' ) ;
channelSubButton . addEventListener ( 'click' , e => {
e . preventDefault ( ) ;
if ( channelSubButton . innerText === 'Subscribe' ) {
console . log ( ` subscribe to: ${ channelHandle } ` ) ;
sendUrl ( channelHandle , 'subscribe' , channelSubButton ) ;
} else if ( channelSubButton . innerText === 'Unsubscribe' ) {
console . log ( ` unsubscribe from: ${ channelHandle } ` ) ;
sendUrl ( channelHandle , 'unsubscribe' , channelSubButton ) ;
} else {
console . log ( 'Unknown state' ) ;
}
} ) ;
Object . assign ( channelSubButton . style , {
padding : '5px' ,
cursor : 'pointer' ,
} ) ;
checkChannelSubscribed ( channelSubButton ) ;
return channelSubButton ;
}
function buildVideoButton ( thumbContainer ) {
var thumbLink = thumbContainer ? . href ;
if ( ! thumbLink ) return ;
if ( thumbLink . includes ( 'list=' ) || thumbLink . includes ( '/shorts/' ) ) return ;
function checkChannelSubscribed ( channelSubButton ) {
function handleResponse ( message ) {
if ( ! message || ( typeof message === 'object' && message . channel _subscribed === false ) ) {
channelSubButton . innerText = 'Subscribe' ;
} else if ( typeof message === 'object' && message . channel _subscribed === true ) {
channelSubButton . innerText = 'Unsubscribe' ;
} else {
console . log ( 'Unknown state' ) ;
}
}
function handleError ( e ) {
buttonError ( channelSubButton ) ;
channelSubButton . innerText = 'Error' ;
console . error ( 'error' , e ) ;
}
let channelHandle = channelSubButton . dataset . id ;
let message = { type : 'getChannel' , channelHandle } ;
let sending = sendMessage ( message ) ;
sending . then ( handleResponse , handleError ) ;
}
var dlButton = document . createElement ( "a" ) ;
dlButton . setAttribute ( "id" , "ta-video-button" ) ;
dlButton . href = '#'
function buildSpacer ( ) {
let spacer = document . createElement ( 'span' ) ;
spacer . innerText = '|' ;
dlButton . addEventListener ( 'click' , e => {
e . preventDefault ( ) ;
let videoLink = thumbContainer . href ;
console . log ( "download: " + videoLink ) ;
sendUrl ( videoLink , "download" , dlButton )
} ) ;
dlButton . addEventListener ( 'mouseover' , e => {
Object . assign ( dlButton . style , {
opacity : 1 ,
} ) ;
let videoTitle = thumbContainer . href ;
e . target . title = "TA download: " + videoTitle ;
} )
dlButton . addEventListener ( 'mouseout' , e => {
Object . assign ( dlButton . style , {
opacity : 0 ,
} ) ;
} )
Object . assign ( dlButton . style , {
display : "flex" ,
position : "absolute" ,
top : "5px" ,
left : "5px" ,
alignItems : "center" ,
backgroundColor : "#00202f" ,
color : "#fff" ,
fontSize : "1.4rem" ,
textDecoration : "none" ,
borderRadius : "8px" ,
cursor : "pointer" ,
opacity : 0 ,
transition : "all 0.3s ease 0.3s" ,
} ) ;
var dlIcon = document . createElement ( "span" ) ;
dlIcon . innerHTML = downloadIcon ;
Object . assign ( dlIcon . style , {
filter : "invert()" ,
width : "20px" ,
padding : "10px 13px" ,
} ) ;
dlButton . appendChild ( dlIcon ) ;
return spacer ;
}
return dlButton
function buildChannelDownloadButton ( ) {
let channelDownloadButton = document . createElement ( 'span' ) ;
let currentLocation = window . location . href ;
let urlObj = new URL ( currentLocation ) ;
if ( urlObj . pathname . startsWith ( '/watch' ) ) {
let params = new URLSearchParams ( document . location . search ) ;
let videoId = params . get ( 'v' ) ;
channelDownloadButton . setAttribute ( 'data-type' , 'video' ) ;
channelDownloadButton . setAttribute ( 'data-id' , videoId ) ;
channelDownloadButton . title = ` TA download video: ${ videoId } ` ;
checkVideoExists ( channelDownloadButton ) ;
} else {
channelDownloadButton . setAttribute ( 'data-id' , currentLocation ) ;
channelDownloadButton . setAttribute ( 'data-type' , 'channel' ) ;
channelDownloadButton . title = ` TA download channel ${ currentLocation } ` ;
}
channelDownloadButton . innerHTML = downloadIcon ;
channelDownloadButton . addEventListener ( 'click' , e => {
e . preventDefault ( ) ;
console . log ( ` download: ${ currentLocation } ` ) ;
sendDownload ( channelDownloadButton ) ;
} ) ;
Object . assign ( channelDownloadButton . style , {
filter : 'invert()' ,
width : '20px' ,
padding : '0 5px' ,
cursor : 'pointer' ,
} ) ;
return channelDownloadButton ;
}
function getTitleContainers ( ) {
let elements = document . querySelectorAll ( '#video-title' ) ;
let videoNodes = [ ] ;
elements . forEach ( element => {
if ( isElementVisible ( element ) ) {
videoNodes . push ( element ) ;
}
} ) ;
return elements ;
}
function getVideoId ( titleContainer ) {
if ( ! titleContainer ) return undefined ;
// fix positioning of #owner div to fit new button
function adjustOwner ( channelContainer ) {
let sponsorButton = channelContainer . querySelector ( '#sponsor-button' ) ;
if ( sponsorButton === null ) {
return channelContainer
}
let href = getNearestLink ( titleContainer ) ;
if ( ! href ) return ;
let variableMinWidth
if ( sponsorButton . hasChildNodes ( ) ) {
variableMinWidth = '140px' ;
} else {
variableMinWidth = '45px' ;
}
let videoId ;
if ( href . startsWith ( '/watch?v' ) ) {
let params = new URLSearchParams ( href ) ;
videoId = params . get ( '/watch?v' ) ;
} else if ( href . startsWith ( '/shorts/' ) ) {
videoId = href . split ( '/' ) [ 2 ] ;
}
return videoId ;
}
Object . assign ( channelContainer . firstElementChild . style , {
minWidth : variableMinWidth
} )
Object . assign ( channelContainer . style , {
minWidth : 'calc(40% + 50px)'
} )
return channelContainer
function buildVideoButton ( titleContainer ) {
let videoId = getVideoId ( titleContainer ) ;
if ( ! videoId ) return ;
const dlButton = document . createElement ( 'a' ) ;
dlButton . classList . add ( 'ta-button' ) ;
dlButton . href = '#' ;
Object . assign ( dlButton . style , {
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
backgroundColor : '#00202f' ,
color : '#fff' ,
fontSize : '1.4rem' ,
textDecoration : 'none' ,
borderRadius : '8px' ,
cursor : 'pointer' ,
height : 'fit-content' ,
opacity : 0 ,
} ) ;
let dlIcon = document . createElement ( 'span' ) ;
dlIcon . innerHTML = defaultIcon ;
Object . assign ( dlIcon . style , {
filter : 'invert()' ,
width : '15px' ,
height : '15px' ,
padding : '7px 8px' ,
} ) ;
dlButton . appendChild ( dlIcon ) ;
dlButton . addEventListener ( 'click' , e => {
e . preventDefault ( ) ;
sendDownload ( dlButton ) ;
e . stopPropagation ( ) ;
} ) ;
return dlButton ;
}
function getNearestLink ( element ) {
// Check siblings
let sibling = element ;
while ( sibling ) {
sibling = sibling . previousElementSibling ;
if ( sibling && sibling . tagName === 'A' && sibling . getAttribute ( 'href' ) !== '#' ) {
return sibling . getAttribute ( 'href' ) ;
}
}
function ensureTALinks ( ) {
sibling = element ;
while ( sibling ) {
sibling = sibling . nextElementSibling ;
if ( sibling && sibling . tagName === 'A' && sibling . getAttribute ( 'href' ) !== '#' ) {
return sibling . getAttribute ( 'href' ) ;
}
}
var channelContainerNodes = getChannelContainers ( )
// Check parent elements
for ( let i = 0 ; i < 5 && element && element !== document ; i ++ ) {
if ( element . tagName === 'A' && element . getAttribute ( 'href' ) !== '#' ) {
return element . getAttribute ( 'href' ) ;
}
element = element . parentNode ;
}
return null ;
}
for ( var channelContainer of channelContainerNodes ) {
channelContainer = adjustOwner ( channelContainer ) ;
if ( channelContainer . hasTA ) continue ;
var channelButton = buildChannelButton ( channelContainer ) ;
channelContainer . appendChild ( channelButton ) ;
channelContainer . hasTA = true ;
function getNearestH3 ( element ) {
for ( let i = 0 ; i < 5 && element && element !== document ; i ++ ) {
if ( element . tagName === 'H3' ) {
return element ;
}
element = element . parentNode ;
}
return null ;
}
var thumbContainerNodes = getThubnailContainers ( ) ;
function processTitle ( titleContainer ) {
if ( titleContainer . hasListener ) return ;
Object . assign ( titleContainer . style , {
display : 'flex' ,
gap : '15px' ,
} ) ;
titleContainer . classList . add ( 'title-container' ) ;
titleContainer . addEventListener ( 'mouseenter' , ( ) => {
const taButton = titleContainer . querySelector ( '.ta-button' ) ;
if ( ! taButton ) return ;
if ( ! taButton . isChecked ) checkVideoExists ( taButton ) ;
taButton . style . opacity = 1 ;
} ) ;
titleContainer . addEventListener ( 'mouseleave' , ( ) => {
const taButton = titleContainer . querySelector ( '.ta-button' ) ;
if ( ! taButton ) return ;
taButton . style . opacity = 0 ;
} ) ;
titleContainer . hasListener = true ;
}
for ( var thumbContainer of thumbContainerNodes ) {
if ( thumbContainer . hasTA ) continue ;
var videoButton = buildVideoButton ( thumbContainer ) ;
if ( videoButton == null ) continue ;
thumbContainer . parentElement . appendChild ( videoButton ) ;
thumbContainer . hasTA = true ;
function checkVideoExists ( taButton ) {
function handleResponse ( message ) {
let buttonSpan = taButton . querySelector ( 'span' ) || taButton ;
if ( message !== false ) {
buttonSpan . innerHTML = checkmarkIcon ;
buttonSpan . title = 'Open in TA' ;
buttonSpan . addEventListener ( 'click' , ( ) => {
let win = window . open ( message , '_blank' ) ;
win . focus ( ) ;
} ) ;
} else {
buttonSpan . innerHTML = downloadIcon ;
}
taButton . isChecked = true ;
}
function handleError ( e ) {
buttonError ( taButton ) ;
let videoId = taButton . dataset . id ;
console . log ( ` error: failed to get info from TA for video ${ videoId } ` ) ;
console . error ( e ) ;
}
let videoId = taButton . dataset . id ;
if ( ! videoId ) {
videoId = getVideoId ( taButton ) ;
if ( videoId ) {
taButton . setAttribute ( 'data-id' , videoId ) ;
taButton . setAttribute ( 'data-type' , 'video' ) ;
taButton . title = ` TA download video: ${ taButton . parentElement . innerText } [ ${ videoId } ] ` ;
}
}
let message = { type : 'videoExists' , videoId } ;
let sending = sendMessage ( message ) ;
sending . then ( handleResponse , handleError ) ;
}
function sendDownload ( button ) {
let url = button . dataset . id ;
if ( ! url ) return ;
sendUrl ( url , 'download' , button ) ;
}
function buttonError ( button ) {
let buttonSpan = button . querySelector ( "span" ) ;
if ( buttonSpan === null ) {
buttonSpan = button
}
buttonSpan . style . filter = "invert(19%) sepia(93%) saturate(7472%) hue-rotate(359deg) brightness(105%) contrast(113%)" ;
buttonSpan . style . color = "red" ;
button . style . opacity = 1 ;
button . addEventListener ( 'mouseout' , e => {
Object . assign ( button . style , {
opacity : 1 ,
} ) ;
} )
let buttonSpan = button . querySelector ( 'span' ) ;
if ( buttonSpan === null ) {
buttonSpan = button ;
}
buttonSpan . style . filter =
'invert(19%) sepia(93%) saturate(7472%) hue-rotate(359deg) brightness(105%) contrast(113%)' ;
buttonSpan . style . color = 'red' ;
button . style . opacity = 1 ;
button . addEventListener ( 'mouseout' , ( ) => {
Object . assign ( button . style , {
opacity : 1 ,
} ) ;
} ) ;
}
function buttonSuccess ( button ) {
let buttonSpan = button . querySelector ( "span" ) ;
if ( buttonSpan === null ) {
buttonSpan = button ;
}
if ( buttonSpan . innerHTML === "Subscribe" ) {
buttonSpan . innerHTML = "Success" ;
setTimeout ( ( ) => {
buttonSpan . innerHTML = "Subscribe" ;
} , 2000 ) ;
} else {
buttonSpan . innerHTML = checkmarkIcon ;
setTimeout ( ( ) => {
buttonSpan . innerHTML = downloadIcon ;
} , 2000 ) ;
}
let buttonSpan = button . querySelector ( 'span' ) ;
if ( buttonSpan === null ) {
buttonSpan = button ;
}
if ( buttonSpan . innerHTML === 'Subscribe' ) {
buttonSpan . innerHTML = 'Success' ;
setTimeout ( ( ) => {
buttonSpan . innerHTML = 'Unsubscribe' ;
} , 2000 ) ;
} else {
buttonSpan . innerHTML = checkmarkIcon ;
}
}
function sendUrl ( url , action , button ) {
function handleResponse ( message ) {
console . log ( "sendUrl response: " + JSON . stringify ( message ) ) ;
if ( message === null || message . detail === "Invalid token." ) {
buttonError ( button ) ;
} else {
buttonSuccess ( button ) ;
}
function handleResponse ( message ) {
console . log ( 'sendUrl response: ' + JSON . stringify ( message ) ) ;
if ( message === null || message . detail === 'Invalid token.' ) {
buttonError ( button ) ;
} else {
buttonSuccess ( button ) ;
}
}
function handleError ( e rror ) {
console . log ( "error" ) ;
console . log ( JSON . stringify ( error ) ) ;
}
function handleError ( e ) {
console . log ( 'error' , e ) ;
buttonError ( button ) ;
}
let payload = {
"youtube" : {
"url" : url ,
"action" : action ,
}
}
let message = { type : action , url } ;
console . log ( 'youtube link: ' + JSON . stringify ( message ) ) ;
console . log ( "youtube link: " + JSON . stringify ( payload ) ) ;
let sending = sendMessage ( message ) ;
sending . then ( handleResponse , handleError ) ;
}
let sending = browserType . runtime . sendMessage ( payload ) ;
sending . then ( handleResponse , handleError ) ;
async function sendMessage ( message ) {
let { success , value } = await browserType . runtime . sendMessage ( message ) ;
if ( ! success ) {
throw value ;
}
return value ;
}
} ;
function cleanButtons ( ) {
console . log ( 'trigger clean buttons' ) ;
document . querySelectorAll ( '.ta-button' ) . forEach ( button => {
button . parentElement . hasTA = false ;
button . remove ( ) ;
} ) ;
document . querySelectorAll ( '.ta-channel-button' ) . forEach ( button => {
button . parentElement . hasTA = false ;
button . remove ( ) ;
} ) ;
}
let oldHref = document . location . href ;
let throttleBlock ;
const throttle = ( callback , time ) => {
function throttled ( callback , time ) {
let throttleBlock = false ;
let lastArgs ;
return ( ... args ) => {
lastArgs = args ;
if ( throttleBlock ) return ;
throttleBlock = true ;
setTimeout ( ( ) => {
callback ( ) ;
throttleBlock = false ;
throttleBlock = false ;
callback ( ... lastArgs ) ;
} , time ) ;
} ;
} ;
}
let observer = new MutationObserver ( list => {
if ( list . some ( i => i . type === 'childList' && i . addedNodes . length > 0 ) ) {
throttle ( ensureTALinks , 700 ) ;
}
const currentHref = document . location . href ;
if ( currentHref !== oldHref ) {
cleanButtons ( ) ;
oldHref = currentHref ;
}
if ( list . some ( i => i . type === 'childList' && i . addedNodes . length > 0 ) ) {
ensureTALinks ( ) ;
}
} ) ;
observer . observe ( document . body , { attributes : false , childList : true , subtree : true } ) ;