From c570aff66df2c5e868b9ff55ebb9f6310241a94d Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 3 Nov 2023 09:00:39 -0700 Subject: [PATCH 1/6] Revert "delay buildChannelButton to account for UI refresh on YT" This reverts commit aaa04a43b5eaf14f8763390a2f6065c0ac87d8dc. --- .eslintrc.js | 1 - extension/script.js | 36 +++++++++++++++--------------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ccc033b..72398fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,6 @@ module.exports = { }, env: { browser: true, - es6: true, }, globals: { browser: 'readonly', diff --git a/extension/script.js b/extension/script.js index 72451a5..32bb357 100644 --- a/extension/script.js +++ b/extension/script.js @@ -124,13 +124,13 @@ function isElementVisible(element) { 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; - buildChannelButton(channelContainer).then(channelButton => { - channelContainer.appendChild(channelButton); - }); } let titleContainerNodes = getTitleContainers(); @@ -150,25 +150,19 @@ function adjustOwner(channelContainer) { } function buildChannelButton(channelContainer) { - return new Promise(resolve => { - let buttonDiv; - let channelSubButton; - let spacer; - let channelDownloadButton; + let channelHandle = getChannelHandle(channelContainer); + let buttonDiv = buildChannelButtonDiv(); - // Delayed execution for interface to refresh - setTimeout(() => { - const channelHandle = getChannelHandle(channelContainer); - buttonDiv = buildChannelButtonDiv(); - channelSubButton = buildChannelSubButton(channelHandle); - spacer = buildSpacer(); - channelDownloadButton = buildChannelDownloadButton(); - buttonDiv.appendChild(channelSubButton); - buttonDiv.appendChild(spacer); - buttonDiv.appendChild(channelDownloadButton); - resolve(buttonDiv); - }, 2000); - }); + let channelSubButton = buildChannelSubButton(channelHandle); + buttonDiv.appendChild(channelSubButton); + + let spacer = buildSpacer(); + buttonDiv.appendChild(spacer); + + let channelDownloadButton = buildChannelDownloadButton(); + buttonDiv.appendChild(channelDownloadButton); + + return buttonDiv; } function getChannelHandle(channelContainer) { From 72c94fbe99681f21e05b02a9eb7508327ac4e184 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 3 Nov 2023 09:02:35 -0700 Subject: [PATCH 2/6] better logic for updating the subscribe button on page navigation --- .eslintrc.js | 2 ++ extension/script.js | 66 +++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 72398fc..5d3c880 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,5 +17,7 @@ module.exports = { eqeqeq: ['error', 'always', { null: 'ignore' }], curly: ['error', 'multi-line'], 'no-var': 'error', + 'no-func-assign': 'off', + 'no-inner-declarations': 'off', }, }; diff --git a/extension/script.js b/extension/script.js index 32bb357..484d378 100644 --- a/extension/script.js +++ b/extension/script.js @@ -126,8 +126,8 @@ function ensureTALinks() { let channelContainerNodes = getChannelContainers(); for (let channelContainer of channelContainerNodes) { - channelContainer = adjustOwner(channelContainer); if (channelContainer.hasTA) continue; + channelContainer = adjustOwner(channelContainer); let channelButton = buildChannelButton(channelContainer); channelContainer.appendChild(channelButton); channelContainer.hasTA = true; @@ -143,6 +143,7 @@ function ensureTALinks() { titleContainer.hasTA = true; } } +ensureTALinks = throttled(ensureTALinks, 700); // fix positioning of #owner div to fit new button function adjustOwner(channelContainer) { @@ -151,16 +152,42 @@ function adjustOwner(channelContainer) { 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, + }); + } return buttonDiv; } @@ -230,10 +257,10 @@ function checkChannelSubscribed(channelSubButton) { console.log('Unknown state'); } } - function handleError() { + function handleError(e) { buttonError(channelSubButton); channelSubButton.innerText = 'Error'; - console.log('error'); + console.error('error', e); } let channelHandle = channelSubButton.dataset.id; @@ -389,10 +416,11 @@ function checkVideoExists(taButton) { } taButton.isChecked = true; } - function handleError() { + 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; @@ -449,9 +477,8 @@ function sendUrl(url, action, button) { } } - function handleError(error) { - console.log('error'); - console.log(JSON.stringify(error)); + function handleError(e) { + console.log('error', e); buttonError(button); } @@ -484,15 +511,20 @@ function cleanButtons() { } let oldHref = document.location.href; -let throttleBlock; -const throttle = (callback, time) => { - if (throttleBlock) return; - throttleBlock = true; - setTimeout(() => { - callback(); - throttleBlock = false; - }, time); -}; + +function throttled(callback, time) { + let throttleBlock = false; + let lastArgs; + return (...args) => { + lastArgs = args; + if (throttleBlock) return; + throttleBlock = true; + setTimeout(() => { + throttleBlock = false; + callback(...lastArgs); + }, time); + }; +} let observer = new MutationObserver(list => { const currentHref = document.location.href; @@ -501,7 +533,7 @@ let observer = new MutationObserver(list => { oldHref = currentHref; } if (list.some(i => i.type === 'childList' && i.addedNodes.length > 0)) { - throttle(ensureTALinks, 700); + ensureTALinks(); } }); From ee6db2595fce1c5c2487c148072ca259d93c8f33 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 3 Nov 2023 09:03:06 -0700 Subject: [PATCH 3/6] restore .eslintrc.js --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 5d3c880..cd14b64 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,7 @@ module.exports = { }, env: { browser: true, + es6: true, }, globals: { browser: 'readonly', From 75848ad4ebb3cbc51c7f1e18f2c2a8838a759730 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Sat, 4 Nov 2023 21:39:03 -0700 Subject: [PATCH 4/6] fix infinite buttons --- extension/script.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extension/script.js b/extension/script.js index 484d378..eb48335 100644 --- a/extension/script.js +++ b/extension/script.js @@ -126,8 +126,8 @@ function ensureTALinks() { let channelContainerNodes = getChannelContainers(); for (let channelContainer of channelContainerNodes) { - if (channelContainer.hasTA) continue; channelContainer = adjustOwner(channelContainer); + if (channelContainer.hasTA) continue; let channelButton = buildChannelButton(channelContainer); channelContainer.appendChild(channelButton); channelContainer.hasTA = true; @@ -145,7 +145,6 @@ function ensureTALinks() { } ensureTALinks = throttled(ensureTALinks, 700); -// fix positioning of #owner div to fit new button function adjustOwner(channelContainer) { return channelContainer.querySelector('#buttons') || channelContainer; } From 4f54e1f863f271cc10a52e5e2d4d6a33fe4524d6 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 8 Nov 2023 18:01:50 +0700 Subject: [PATCH 5/6] flip getChannelHandle logic, remove unvisible title containers --- extension/script.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/extension/script.js b/extension/script.js index eb48335..2011ed8 100644 --- a/extension/script.js +++ b/extension/script.js @@ -192,13 +192,20 @@ function buildChannelButton(channelContainer) { } function getChannelHandle(channelContainer) { - const channelHandleContainer = document.querySelector('#channel-handle'); - let channelHandle = channelHandleContainer ? channelHandleContainer.innerText : null; - if (!channelHandle) { - let href = channelContainer.querySelector('.ytd-video-owner-renderer').href; - const urlObj = new URL(href); - channelHandle = urlObj.pathname.split('/')[1]; + let channelHandle; + const videoOwnerRenderer = channelContainer.querySelector('.ytd-video-owner-renderer'); + + if (!videoOwnerRenderer) { + const channelHandleContainer = document.querySelector('#channel-handle'); + channelHandle = channelHandleContainer ? channelHandleContainer.innerText : null; + } else { + const href = videoOwnerRenderer.href; + if (href) { + const urlObj = new URL(href); + channelHandle = urlObj.pathname.split('/')[1]; + } } + return channelHandle; } @@ -309,8 +316,14 @@ function buildChannelDownloadButton() { } function getTitleContainers() { - let nodes = document.querySelectorAll('#video-title'); - return nodes; + let elements = document.querySelectorAll('#video-title'); + let videoNodes = []; + elements.forEach(element => { + if (isElementVisible(element)) { + videoNodes.push(element); + } + }); + return elements; } function buildVideoButton(titleContainer) { From f8d69f58830e178b3eb34e386e6adaee6298ec3f Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 9 Nov 2023 18:21:43 +0700 Subject: [PATCH 6/6] implement extract video id on hover --- extension/script.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/extension/script.js b/extension/script.js index 2011ed8..42d1e61 100644 --- a/extension/script.js +++ b/extension/script.js @@ -326,12 +326,9 @@ function getTitleContainers() { return elements; } -function buildVideoButton(titleContainer) { +function getVideoId(titleContainer) { let href = getNearestLink(titleContainer); if (!href) return; - const dlButton = document.createElement('a'); - dlButton.classList.add('ta-button'); - dlButton.href = '#'; let videoId; if (href.startsWith('/watch?v')) { @@ -340,11 +337,16 @@ function buildVideoButton(titleContainer) { } else if (href.startsWith('/shorts/')) { videoId = href.split('/')[2]; } + return videoId; +} + +function buildVideoButton(titleContainer) { + let videoId = getVideoId(titleContainer); if (!videoId) return; - dlButton.setAttribute('data-id', videoId); - dlButton.setAttribute('data-type', 'video'); - dlButton.title = `TA download video: ${titleContainer.innerText} [${videoId}]`; + const dlButton = document.createElement('a'); + dlButton.classList.add('ta-button'); + dlButton.href = '#'; Object.assign(dlButton.style, { display: 'flex', @@ -435,7 +437,12 @@ function checkVideoExists(taButton) { console.error(e); } - let videoId = taButton.dataset.id; + if (!taButton.parentElement) return; + let videoId = getVideoId(taButton.parentElement); + 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);