diff --git a/content.js b/content.js index e8a566f..d6164ba 100644 --- a/content.js +++ b/content.js @@ -95,6 +95,7 @@ const locales = { STREAMED_TITLE: 'views Streamed', TELL_US_WHY: 'Tell us why', THANKS: 'Thanks', + UNHIDE_CHANNEL: 'Unhide channel', }, 'ja-JP': { CLIP: 'クリップ', @@ -110,6 +111,7 @@ const locales = { SHORTS: 'ショート', STREAMED_TITLE: '前 に配信済み', TELL_US_WHY: '理由を教えてください', + UNHIDE_CHANNEL: 'チャンネルの再表示', } } @@ -135,6 +137,7 @@ const Classes = { const Svgs = { DELETE: '', + RESTORE: '', } //#region State @@ -255,6 +258,13 @@ function getElement(selector, { }) } +/** @param {import("./types").Channel} channel */ +function isChannelHidden(channel) { + return config.hiddenChannels.some((hiddenChannel) => + channel.url && hiddenChannel.url ? channel.url == hiddenChannel.url : hiddenChannel.name == channel.name + ) +} + let logObserverDisconnects = true /** @@ -1201,6 +1211,7 @@ function handleCurrentUrl() { } } +/** @param {HTMLElement} $menu */ function addDownloadTranscriptToDesktopMenu($menu) { if (!isVideoPage()) return @@ -1225,7 +1236,7 @@ function addDownloadTranscriptToDesktopMenu($menu) { document.querySelector('#content')?.click() } $item.addEventListener('click', download) - $item.addEventListener('keydown', (e) => { + $item.addEventListener('keydown', /** @param {KeyboardEvent} e */ (e) => { if (e.key == ' ' || e.key == 'Enter') { e.preventDefault() download() @@ -1233,24 +1244,103 @@ function addDownloadTranscriptToDesktopMenu($menu) { }) } -function handleDesktopChannelMenu($menu) { +/** @param {HTMLElement} $menu */ +function handleDesktopWatchChannelMenu($menu) { if (!isVideoPage()) return let $channelMenuRenderer = $lastClickedElement.closest('ytd-menu-renderer.ytd-watch-metadata') if (!$channelMenuRenderer) return - let $menuItems = /** @type {NodeListOf} */ ($menu.querySelectorAll('ytd-menu-service-item-renderer')) - let testLabels = new Set([getString('SHARE'), getString('THANKS'), getString('CLIP')]) - for (let $menuItem of $menuItems) { - if (testLabels.has($menuItem.querySelector('yt-formatted-string')?.textContent)) { - log('tagging Share/Thanks/Clip menu item') - $menuItem.classList.add(Classes.HIDE_SHARE_THANKS_CLIP) + if (config.hideShareThanksClip) { + let $menuItems = /** @type {NodeListOf} */ ($menu.querySelectorAll('ytd-menu-service-item-renderer')) + let testLabels = new Set([getString('SHARE'), getString('THANKS'), getString('CLIP')]) + for (let $menuItem of $menuItems) { + if (testLabels.has($menuItem.querySelector('yt-formatted-string')?.textContent)) { + log('tagging Share/Thanks/Clip menu item') + $menuItem.classList.add(Classes.HIDE_SHARE_THANKS_CLIP) + } + } + } + + if (config.hideChannels) { + let $channelLink = /** @type {HTMLAnchorElement} */ (document.querySelector('#channel-name a')) + if (!$channelLink) { + warn('channel link not found in video page') + return + } + + let channel = { + name: $channelLink.textContent, + url: $channelLink.pathname, + } + lastClickedChannel = channel + + let $item = $menu.querySelector('#cpfyt-hide-channel-menu-item') + + function configureMenuItem(channel) { + let hidden = isChannelHidden(channel) + $item.querySelector('.cpfyt-menu-icon').innerHTML = hidden ? Svgs.RESTORE : Svgs.DELETE + $item.querySelector('.cpfyt-menu-text').textContent = getString(hidden ? 'UNHIDE_CHANNEL' : 'HIDE_CHANNEL') + } + + // The same menu can be reused, so we reconfigure it if it exists. If the + // menu item is reused, we're just changing [lastClickedChannel], which is + // why [toggleHideChannel] uses it. + if (!$item) { + let hidden = isChannelHidden(channel) + + function toggleHideChannel() { + let hidden = isChannelHidden(lastClickedChannel) + if (hidden) { + log('unhiding channel', lastClickedChannel) + config.hiddenChannels = config.hiddenChannels.filter((hiddenChannel) => + hiddenChannel.url ? lastClickedChannel.url != hiddenChannel.url : hiddenChannel.name != lastClickedChannel.name + ) + } else { + log('hiding channel', lastClickedChannel) + config.hiddenChannels.unshift(lastClickedChannel) + } + configureMenuItem(lastClickedChannel) + storeConfigChanges({hiddenChannels: config.hiddenChannels}) + configureCss() + handleCurrentUrl() + // Dismiss the menu + let $popupContainer = /** @type {HTMLElement} */ ($menu.closest('ytd-popup-container')) + $popupContainer.click() + // XXX Menu isn't dismissing on iPad Safari + if ($menu.style.display != 'none') { + $menu.style.display = 'none' + $menu.setAttribute('aria-hidden', 'true') + } + } + + let $menuItems = $menu.querySelector('#items') + $menuItems.insertAdjacentHTML('beforeend', ` + + `.trim()) + $item = $menuItems.lastElementChild + $item.addEventListener('click', toggleHideChannel) + $item.addEventListener('keydown', /** @param {KeyboardEvent} e */ (e) => { + if (e.key == ' ' || e.key == 'Enter') { + e.preventDefault() + toggleHideChannel() + } + }) + } else { + configureMenuItem(channel) } } } /** @param {HTMLElement} $menu */ -function addHideChannelToDesktopMenu($menu) { +function addHideChannelToDesktopVideoMenu($menu) { let videoContainerElement if (isSearchPage()) { videoContainerElement = 'ytd-video-renderer' @@ -1272,7 +1362,7 @@ function addHideChannelToDesktopMenu($menu) { if (!channel) return lastClickedChannel = channel - if ($menu.querySelector('.cpfyt-menu-item')) return + if ($menu.querySelector('#cpfyt-hide-channel-menu-item')) return let $menuItems = $menu.querySelector('#items') $menuItems.insertAdjacentHTML('beforeend', ` @@ -1310,10 +1400,8 @@ function addHideChannelToDesktopMenu($menu) { }) } -/** - * @param {HTMLElement} $menu - */ -async function addHideChannelToMobileMenu($menu) { +/** @param {HTMLElement} $menu */ +async function addHideChannelToMobileVideoMenu($menu) { if (!(isHomePage() || isSearchPage() || isVideoPage())) return /** @type {HTMLElement} */ @@ -1532,13 +1620,13 @@ function onDesktopMenuAppeared($menu) { addDownloadTranscriptToDesktopMenu($menu) } if (config.hideChannels) { - addHideChannelToDesktopMenu($menu) + addHideChannelToDesktopVideoMenu($menu) } if (config.hideHiddenVideos) { observeVideoHiddenState() } - if (config.hideShareThanksClip) { - handleDesktopChannelMenu($menu) + if (config.hideChannels || config.hideShareThanksClip) { + handleDesktopWatchChannelMenu($menu) } } @@ -2176,7 +2264,7 @@ function onMobileMenuAppeared($menu) { } if (config.hideChannels) { - addHideChannelToMobileMenu($menu) + addHideChannelToMobileVideoMenu($menu) } if (config.hideHiddenVideos) { observeVideoHiddenState() @@ -2231,9 +2319,7 @@ function manuallyHideVideo($video) { let channel = getChannelDetailsFromVideo($video) let hide = false if (channel) { - hide = config.hiddenChannels.some((hiddenChannel) => - channel.url && hiddenChannel.url ? channel.url == hiddenChannel.url : hiddenChannel.name == channel.name - ) + hide = isChannelHidden(channel) } $video.classList.toggle(Classes.HIDE_CHANNEL, hide) } @@ -2596,22 +2682,24 @@ function onConfigChange(storageChanges) { .filter(([key]) => config.hasOwnProperty(key) && key != 'version') .map(([key, {newValue}]) => [key, newValue]) ) - if (Object.keys(configChanges).length > 0) { - if ('debug' in configChanges) { - log('disabling debug mode') - debug = configChanges.debug - log('enabled debug mode') - return - } - if ('debugManualHiding' in configChanges) { - debugManualHiding = configChanges.debugManualHiding - log(`${debugManualHiding ? 'en' : 'dis'}abled debugging manual hiding`) - configureCss() - return - } - Object.assign(config, configChanges) - configChanged(configChanges) + if (Object.keys(configChanges).length == 0) return + + if ('debug' in configChanges) { + log('disabling debug mode') + debug = configChanges.debug + log('enabled debug mode') + return + } + + if ('debugManualHiding' in configChanges) { + debugManualHiding = configChanges.debugManualHiding + log(`${debugManualHiding ? 'en' : 'dis'}abled debugging manual hiding`) + configureCss() + return } + + Object.assign(config, configChanges) + configChanged(configChanges) } /** @param {Partial} configChanges */ diff --git a/types.d.ts b/types.d.ts index 400b3cf..dfd6bf8 100644 --- a/types.d.ts +++ b/types.d.ts @@ -33,6 +33,7 @@ export type LocaleKey = | 'STREAMED_TITLE' | 'TELL_US_WHY' | 'THANKS' + | 'UNHIDE_CHANNEL' export type OptionsConfig = EmbedConfig & SiteConfig