diff --git a/public/contentStyle.css b/public/contentStyle.css index 22073d1e..ba0f4d38 100644 --- a/public/contentStyle.css +++ b/public/contentStyle.css @@ -63,4 +63,8 @@ body.no-scroll .yte-button-tooltip { .ytp-right-controls { display: flex !important; +} + +.yte-hide-shorts { + display: none !important; } \ No newline at end of file diff --git a/public/locales/en-US.json b/public/locales/en-US.json index 0afd8d0a..9dfbe38c 100644 --- a/public/locales/en-US.json +++ b/public/locales/en-US.json @@ -186,6 +186,10 @@ "label": "Hide scrollbar", "title": "Hides the pages scrollbar" }, + "hideShorts": { + "label": "Hide shorts", + "title": "Hides all shorts" + }, "loopButton": { "label": "Loop button", "title": "Adds a button to the feature menu to loop the video you're watching" diff --git a/public/locales/en-US.json.d.ts b/public/locales/en-US.json.d.ts index 7d766d06..0ea41e80 100644 --- a/public/locales/en-US.json.d.ts +++ b/public/locales/en-US.json.d.ts @@ -130,6 +130,7 @@ interface EnUS { title: "Automatically enables theater mode when you load a video"; }; hideScrollbar: { label: "Hide scrollbar"; title: "Hides the pages scrollbar" }; + hideShorts: { label: "Hide shorts"; title: "Hides all shorts" }; loopButton: { label: "Loop button"; title: "Adds a button to the feature menu to loop the video you're watching"; diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx index 143b1e36..8fd36948 100644 --- a/src/components/Settings/Settings.tsx +++ b/src/components/Settings/Settings.tsx @@ -587,6 +587,14 @@ export default function Settings() { title={t("settings.sections.miscellaneous.features.shortsAutoScroll.title")} type="checkbox" /> + diff --git a/src/features/hideShorts/index.ts b/src/features/hideShorts/index.ts new file mode 100644 index 00000000..e4246d22 --- /dev/null +++ b/src/features/hideShorts/index.ts @@ -0,0 +1,26 @@ +import { hideShorts, observeShortsElements, showShorts } from "@/src/features/hideShorts/utils"; +import { waitForSpecificMessage } from "@/src/utils/utilities"; +let shortsObserver: MutationObserver | null = null; +export async function enableHideShorts() { + // Wait for the "options" message from the content script + const optionsData = await waitForSpecificMessage("options", "request_data", "content"); + const { + data: { + options: { enable_hide_shorts } + } + } = optionsData; + // If the hide shorts option is disabled, return + if (!enable_hide_shorts) return; + hideShorts(); + // Observe changes to the short sections and hides them + shortsObserver = observeShortsElements(); +} + +export function disableHideShorts() { + showShorts(); + // Disconnect the observer + if (shortsObserver) { + shortsObserver.disconnect(); + shortsObserver = null; + } +} diff --git a/src/features/hideShorts/utils.ts b/src/features/hideShorts/utils.ts new file mode 100644 index 00000000..d7a887d0 --- /dev/null +++ b/src/features/hideShorts/utils.ts @@ -0,0 +1,129 @@ +const sideBarOpenedShortsButtonSelector = "ytd-guide-entry-renderer:has(a[title=Shorts])"; +const sideBarClosedShortsButtonSelector = "ytd-guide-entry-renderer:has(a[title=Shorts])"; +const homePageShortsSectionSelector = "ytd-rich-section-renderer:has(#rich-shelf-header)"; +const channelHomePageShortsSectionSelector = "ytd-reel-shelf-renderer:has(#title-container)"; +const channelPageShortsTabSelector = "yt-tab-shape[tab-title=Shorts]"; +const searchResultsShortsTabSelector = "yt-chip-cloud-chip-renderer:has(yt-formatted-string[title=Shorts])"; +const shortsVideoRendererSelector = "ytd-video-renderer:has([overlay-style=SHORTS])"; + +type ElementVisibilityAction = (element: HTMLElement) => void; + +function toggleElementVisibility(selector: string, action: ElementVisibilityAction) { + const elements = document.querySelectorAll(selector); + if (elements.length === 0) return; + elements.forEach((element) => action(element)); +} + +function hideElement(element: HTMLElement) { + element.classList.add("yte-hide-shorts"); +} + +function showElement(element: HTMLElement) { + element.classList.remove("yte-hide-shorts"); +} + +function hideSideBarShortsButton() { + toggleElementVisibility(sideBarOpenedShortsButtonSelector, hideElement); + toggleElementVisibility(sideBarClosedShortsButtonSelector, hideElement); +} + +function showSideBarShortsButton() { + toggleElementVisibility(sideBarOpenedShortsButtonSelector, showElement); + toggleElementVisibility(sideBarClosedShortsButtonSelector, showElement); +} +function hideShortsTabOnSearchResultsPage() { + toggleElementVisibility(searchResultsShortsTabSelector, hideElement); +} +function showShortsTabOnSearchResultsPage() { + toggleElementVisibility(searchResultsShortsTabSelector, showElement); +} +function hideShortsSectionOnHomePage() { + toggleElementVisibility(homePageShortsSectionSelector, hideElement); +} + +function showShortsSectionOnHomePage() { + toggleElementVisibility(homePageShortsSectionSelector, showElement); +} + +function hideShortsSectionOnChannelHomePage() { + toggleElementVisibility(channelHomePageShortsSectionSelector, hideElement); +} + +function showShortsSectionOnChannelHomePage() { + toggleElementVisibility(channelHomePageShortsSectionSelector, showElement); +} + +function hideShortsTabOnChannelPage() { + toggleElementVisibility(channelPageShortsTabSelector, hideElement); +} + +function showShortsTabOnChannelPage() { + toggleElementVisibility(channelPageShortsTabSelector, showElement); +} +function hideShortsVideoRenderers() { + toggleElementVisibility(shortsVideoRendererSelector, hideElement); +} +function showShortsVideoRenderers() { + toggleElementVisibility(shortsVideoRendererSelector, showElement); +} +export function hideShorts() { + // Hide the shorts tab on the channel page + hideShortsTabOnChannelPage(); + // Hide the shorts tab on the search results page + hideShortsTabOnSearchResultsPage(); + // Hide the shorts section on the homepage + hideShortsSectionOnHomePage(); + // Hide the shorts section on the channel home page + hideShortsSectionOnChannelHomePage(); + // Hide the shorts sidebar items + hideSideBarShortsButton(); + // Hide the shorts video renderers + hideShortsVideoRenderers(); +} +export function showShorts() { + // Show the shorts sidebar items + showSideBarShortsButton(); + // Show the shorts section on the homepage + showShortsSectionOnHomePage(); + // Show the shorts section on the channel home page + showShortsSectionOnChannelHomePage(); + // Show the shorts tab on the channel page + showShortsTabOnChannelPage(); + // Show the shorts tab on the search results page + showShortsTabOnSearchResultsPage(); + // Show the shorts video renderers + showShortsVideoRenderers(); +} + +export function observeShortsElements() { + const observerOptions: MutationObserverInit = { + childList: true, + subtree: true + }; + + const observer = new MutationObserver((mutations) => { + // Check if any mutation contains one of the specified selectors + const containsShortsSelector = mutations.some((mutation) => { + return ( + mutation.target instanceof Element && + (mutation.target.matches(sideBarOpenedShortsButtonSelector) || + mutation.target.matches(sideBarClosedShortsButtonSelector) || + mutation.target.matches(homePageShortsSectionSelector) || + mutation.target.matches(channelHomePageShortsSectionSelector) || + mutation.target.matches(channelPageShortsTabSelector) || + mutation.target.matches(searchResultsShortsTabSelector) || + mutation.target.matches(shortsVideoRendererSelector)) + ); + }); + + // Only call hideShorts if one of the mutations contains one of the selectors + if (containsShortsSelector) { + hideShorts(); + } + }); + + observer.observe(document.body, observerOptions); + + // Return the observer so it can be disconnected later + return observer; +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 39c9ff5a..08baf625 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -21,10 +21,10 @@ export const availableLocales = [ "zh-TW" ] as const; export const localePercentages: Record = { - "en-US": 100, "ca-ES": 0, "cs-CZ": 0, "de-DE": 44, + "en-US": 100, "es-ES": 77, "fa-IR": 0, "fr-FR": 81, diff --git a/src/pages/content/index.tsx b/src/pages/content/index.tsx index 86f2d295..5b78930a 100644 --- a/src/pages/content/index.tsx +++ b/src/pages/content/index.tsx @@ -10,6 +10,7 @@ import { enableFeatureMenu, setupFeatureMenuEventListeners } from "@/src/feature import { featuresInMenu, updateFeatureMenuItemLabel, updateFeatureMenuTitle } from "@/src/features/featureMenu/utils"; import { enableHideScrollBar } from "@/src/features/hideScrollBar"; import { hideScrollBar, showScrollBar } from "@/src/features/hideScrollBar/utils"; +import { disableHideShorts, enableHideShorts } from "@/src/features/hideShorts"; import { addLoopButton, removeLoopButton } from "@/src/features/loopButton"; import { addMaximizePlayerButton, removeMaximizePlayerButton } from "@/src/features/maximizePlayerButton"; import { maximizePlayer } from "@/src/features/maximizePlayerButton/utils"; @@ -108,6 +109,7 @@ window.addEventListener("DOMContentLoaded", function () { eventManager.removeAllEventListeners(["featureMenu"]); void enableFeatureMenu(); void enableOpenYouTubeSettingsOnHover(); + void enableHideShorts(); void enableShortsAutoScroll(); void removeRedirect(); void enableShareShortener(); @@ -313,6 +315,17 @@ window.addEventListener("DOMContentLoaded", function () { } break; } + case "hideShortsChange": { + const { + data: { hideShortsEnabled } + } = message; + if (hideShortsEnabled) { + await enableHideShorts(); + } else { + disableHideShorts(); + } + break; + } case "languageChange": { const { data: { language } diff --git a/src/pages/inject/index.tsx b/src/pages/inject/index.tsx index 9bf6daec..777b5c02 100644 --- a/src/pages/inject/index.tsx +++ b/src/pages/inject/index.tsx @@ -241,6 +241,11 @@ const storageChangeHandler = async (changes: StorageChanges, areaName: string) = hideScrollBarEnabled: newValue }); }, + enable_hide_shorts: (__oldValue, newValue) => { + sendExtensionOnlyMessage("hideShortsChange", { + hideShortsEnabled: newValue + }); + }, enable_loop_button: (__oldValue, newValue) => { sendExtensionOnlyMessage("loopButtonChange", { loopButtonEnabled: newValue diff --git a/src/types/index.ts b/src/types/index.ts index 332af6ae..cebbb0a4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -71,6 +71,7 @@ export type configuration = { enable_custom_css: boolean; enable_forced_playback_speed: boolean; enable_hide_scrollbar: boolean; + enable_hide_shorts: boolean; enable_loop_button: boolean; enable_maximize_player_button: boolean; enable_open_transcript_button: boolean; @@ -163,6 +164,7 @@ export type ExtensionSendOnlyMessageMappings = { customCSSChange: DataResponseMessage<"customCSSChange", { customCSSCode: string; customCSSEnabled: boolean }>; featureMenuOpenTypeChange: DataResponseMessage<"featureMenuOpenTypeChange", { featureMenuOpenType: FeatureMenuOpenType }>; hideScrollBarChange: DataResponseMessage<"hideScrollBarChange", { hideScrollBarEnabled: boolean }>; + hideShortsChange: DataResponseMessage<"hideShortsChange", { hideShortsEnabled: boolean }>; languageChange: DataResponseMessage<"languageChange", { language: AvailableLocales }>; loopButtonChange: DataResponseMessage<"loopButtonChange", { loopButtonEnabled: boolean }>; maximizeButtonChange: DataResponseMessage<"maximizeButtonChange", { maximizePlayerButtonEnabled: boolean }>; diff --git a/src/utils/EventManager.ts b/src/utils/EventManager.ts index 5a07f539..6d7a6c63 100644 --- a/src/utils/EventManager.ts +++ b/src/utils/EventManager.ts @@ -2,6 +2,7 @@ export type FeatureName = | "automaticTheaterMode" | "featureMenu" | "hideScrollBar" + | "hideShorts" | "loopButton" | "maximizePlayerButton" | "openTranscriptButton" diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 596306e5..3d3fb2c8 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -33,6 +33,7 @@ export const defaultConfiguration = { enable_custom_css: false, enable_forced_playback_speed: false, enable_hide_scrollbar: false, + enable_hide_shorts: false, enable_loop_button: false, enable_maximize_player_button: false, enable_open_transcript_button: false, @@ -95,6 +96,7 @@ export const configurationImportSchema: TypeToPartialZodSchema< enable_custom_css: z.boolean().optional(), enable_forced_playback_speed: z.boolean().optional(), enable_hide_scrollbar: z.boolean().optional(), + enable_hide_shorts: z.boolean().optional(), enable_loop_button: z.boolean().optional(), enable_maximize_player_button: z.boolean().optional(), enable_open_transcript_button: z.boolean().optional(),