diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44e3b4eb..e600e89b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,8 @@ name: Release - -on: +"on": push: branches: - main - jobs: build-and-release: runs-on: ubuntu-latest @@ -12,32 +10,28 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Node.js uses: actions/setup-node@v2 with: node-version: 20 - - name: Install dependencies run: npm ci - - name: Run Semantic Release run: | touch .env echo CROWDIN_TOKEN=$CROWDIN_TOKEN >> .env npx semantic-release env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }} - + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + CROWDIN_TOKEN: "${{ secrets.CROWDIN_TOKEN }}" - name: Push changes to main branch run: | # Ensure we are on the main branch git checkout main - + git add ./src/i18n/index.ts + git commit -m "Update i18n" # Push local changes to the main branch git push origin main - - name: Merge changes from main into dev run: | # Switch to the dev branch diff --git a/bun.lockb b/bun.lockb index 133ff841..b2f4fadf 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/public/locales/it-IT.json b/public/locales/it-IT.json index e8c36e4d..67391e7d 100644 --- a/public/locales/it-IT.json +++ b/public/locales/it-IT.json @@ -258,8 +258,8 @@ "title": "Scorri automaticamente al prossimo blocco" }, "skipContinueWatching": { - "label": "Skip \"Video is paused. Continue watching?\"", - "title": "Skips the idle dialog that pauses video playback" + "label": "Salta \"Video in pausa. Continuare a guardare?\"", + "title": "Salta la finestra che mette in pausa la riproduzione video" } }, "title": "Impostazioni varie" diff --git a/public/locales/ja-JP.json b/public/locales/ja-JP.json index 66309143..46eef3bb 100644 --- a/public/locales/ja-JP.json +++ b/public/locales/ja-JP.json @@ -258,8 +258,8 @@ "title": "次のショートを自動にスクロールする" }, "skipContinueWatching": { - "label": "Skip \"Video is paused. Continue watching?\"", - "title": "Skips the idle dialog that pauses video playback" + "label": "「動画が一時停止されました。続きを視聴しますか?」を抜かする", + "title": "動画一時休止する休眠メッセージを抜かされます" } }, "title": "他の設定" @@ -306,7 +306,7 @@ "type": { "label": "OSD型", "options": { - "circle": "Circle", + "circle": "円形", "line": "ライン", "no_display": "見せない", "text": "テキスト" diff --git a/public/locales/zh-CN.json b/public/locales/zh-CN.json index 134214c5..a77320bd 100644 --- a/public/locales/zh-CN.json +++ b/public/locales/zh-CN.json @@ -39,10 +39,10 @@ "playbackSpeedButtons": { "buttons": { "decreasePlaybackSpeedButton": { - "label": "降低 {{SPEED}} 播放速度" + "label": "降低播放速度至 {{SPEED}}" }, "increasePlaybackSpeedButton": { - "label": "增加 {{SPEED}} 播放速度" + "label": "增加播放速度至 {{SPEED}}" } } }, diff --git a/public/locales/zh-TW.json b/public/locales/zh-TW.json index c4be87e2..484ad8e0 100644 --- a/public/locales/zh-TW.json +++ b/public/locales/zh-TW.json @@ -15,10 +15,10 @@ }, "loopButton": { "button": { - "label": "Loop", + "label": "循環播放", "toggle": { - "off": "Loop off", - "on": "Loop on" + "off": "關閉循環播放", + "on": "開啟循環播放" } } }, @@ -48,7 +48,7 @@ }, "screenshotButton": { "button": { - "label": "Screenshot" + "label": "螢幕截圖" }, "copiedToClipboard": "螢幕截圖已複製剪貼簿" }, diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx index caa51f98..3b2ccef0 100644 --- a/src/components/Settings/Settings.tsx +++ b/src/components/Settings/Settings.tsx @@ -12,6 +12,7 @@ import { deepDarkPreset } from "@/src/deepDarkPresets"; import { availableLocales, type i18nInstanceType, i18nService, localeDirection, localePercentages } from "@/src/i18n"; import { buttonNames, youtubePlaybackSpeedButtonsRates, youtubePlayerSpeedRates } from "@/src/types"; import { configurationImportSchema, defaultConfiguration as defaultSettings } from "@/src/utils/constants"; +import { updateStoredSettings } from "@/src/utils/updateStoredSettings"; import { cn, deepMerge, formatDateForFileName, getPathValue, parseStoredValue } from "@/src/utils/utilities"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Suspense, createContext, useContext, useEffect, useRef, useState } from "react"; @@ -423,8 +424,10 @@ export default function Settings() { void chrome.storage.local.set({ [key]: castSettings[key] as string }); } } + await updateStoredSettings(); + const storedSettings = await getSettings(); // Set the imported settings in your state. - settingsMutate.mutate(castSettings); + settingsMutate.mutate(storedSettings); // Show a success notification. addNotification("success", "settings.sections.importExportSettings.importButton.success"); } @@ -957,7 +960,7 @@ export default function Settings() { type="select" /> - - - - { - if (value !== undefined) { - setValueOption("custom_css_code")({ currentTarget: { value } } as ChangeEvent); - } - }} - type="css-editor" - value={settings.custom_css_code} - /> - @@ -1082,9 +1064,30 @@ export default function Settings() { value={settings.deep_dark_custom_theme_colors.colorShadow} /> + + + + { + if (value !== undefined) { + setValueOption("custom_css_code")({ currentTarget: { value } } as ChangeEvent); + } + }} + type="css-editor" + value={settings.custom_css_code} + /> +
{isPopup && ( )} {notifications.filter((n) => n.action === "reset_settings").length > 0 ? { const notificationToRemove = notifications.find((n) => n.action === "reset_settings"); @@ -1143,7 +1146,7 @@ export default function Settings() { value={t("settings.sections.bottomButtons.confirm.value")} /> : >((resolve) => canvas.toBlob(resolve, "image/png")); if (!blob) return; @@ -50,7 +46,6 @@ async function takeScreenshot(videoElement: HTMLVideoElement) { listener(); const clipboardImage = new ClipboardItem({ "image/png": blob }); void navigator.clipboard.write([clipboardImage]); - void navigator.clipboard.writeText(dataUrl); setTimeout(() => { remove(); }, 1200); diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 7d6d30d5..2faa7811 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -2,46 +2,46 @@ import { type Resource, createInstance } from "i18next"; import { waitForSpecificMessage } from "../utils/utilities"; export const availableLocales = [ - "ca-ES", - "cs-CZ", - "de-DE", - "en-GB", - "en-US", - "es-ES", - "fa-IR", - "fr-FR", - "he-IL", - "hi-IN", - "it-IT", - "ja-JP", - "pl-PL", - "pt-BR", - "ru-RU", - "sv-SE", - "tr-TR", - "zh-CN", - "zh-TW" + "ca-ES", + "cs-CZ", + "de-DE", + "en-GB", + "en-US", + "es-ES", + "fa-IR", + "fr-FR", + "he-IL", + "hi-IN", + "it-IT", + "ja-JP", + "pl-PL", + "pt-BR", + "ru-RU", + "sv-SE", + "tr-TR", + "zh-CN", + "zh-TW" ] as const; export const localePercentages: Record = { - "ca-ES": 0, - "cs-CZ": 0, - "de-DE": 36, - "en-GB": 2, - "en-US": 100, - "es-ES": 63, - "fa-IR": 0, - "fr-FR": 67, - "he-IL": 0, - "hi-IN": 0, - "it-IT": 98, - "ja-JP": 98, - "pl-PL": 0, - "pt-BR": 74, - "ru-RU": 100, - "sv-SE": 98, - "tr-TR": 75, - "zh-CN": 100, - "zh-TW": 94 + "en-US": 100, + "ca-ES": 0, + "cs-CZ": 0, + "de-DE": 36, + "en-GB": 2, + "es-ES": 63, + "fa-IR": 0, + "fr-FR": 67, + "he-IL": 0, + "hi-IN": 0, + "it-IT": 100, + "ja-JP": 100, + "pl-PL": 0, + "pt-BR": 74, + "ru-RU": 100, + "sv-SE": 98, + "tr-TR": 75, + "zh-CN": 100, + "zh-TW": 95 }; export const localeDirection: Record = { "ca-ES": "ltr", diff --git a/src/manifest.ts b/src/manifest.ts index b8034413..bc335cf4 100755 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -60,7 +60,7 @@ const manifestV3: Manifest.WebExtensionManifest = { const manifestV2: Manifest.WebExtensionManifest = { author: pkg.author.name, background: { - scripts: ["src/pages/background/index.js"] + page: "src/pages/background/index.html" }, browser_action: action, browser_specific_settings: { diff --git a/src/pages/background/index.html b/src/pages/background/index.html new file mode 100644 index 00000000..b1937ff4 --- /dev/null +++ b/src/pages/background/index.html @@ -0,0 +1,11 @@ + + + + + + + YouTube Enhancer Background + + + + diff --git a/src/pages/background/index.ts b/src/pages/background/index.ts index 02cd4daa..25d484f3 100644 --- a/src/pages/background/index.ts +++ b/src/pages/background/index.ts @@ -1,9 +1,8 @@ -import type { ContentToBackgroundSendOnlyMessageMappings, configuration, configurationKeys } from "@/src/types"; +import type { ContentToBackgroundSendOnlyMessageMappings } from "@/src/types"; -import { defaultConfiguration } from "@/src/utils/constants"; -import { parseStoredValue } from "@/src/utils/utilities"; +import { updateStoredSettings } from "@/src/utils/updateStoredSettings"; -import pkg from "../../../package.json"; +import { version } from "../../../package.json"; chrome.runtime.onInstalled.addListener((details) => { const { previousVersion, reason } = details; @@ -15,7 +14,6 @@ chrome.runtime.onInstalled.addListener((details) => { break; } case chrome.runtime.OnInstalledReason.UPDATE: { - const { version } = pkg; if ( isNewMajorVersion(previousVersion as VersionString, version as VersionString) || isNewMinorVersion(previousVersion as VersionString, version as VersionString) @@ -89,52 +87,3 @@ chrome.runtime.onMessage.addListener((message: ContentToBackgroundSendOnlyMessag } } }); -const changedKeys = Object.keys({ - osd_display_type: "" -} satisfies Partial>); -async function updateStoredSettings() { - try { - const settings = await getStoredSettings(); - const removedKeys = Object.keys(settings).filter((key) => !Object.keys(defaultConfiguration).includes(key)); - for (const changedKey of changedKeys) { - switch (changedKey) { - case "osd_display_type": { - if ((settings.osd_display_type as unknown as string) === "round") { - settings.osd_display_type = "circle"; - } - break; - } - } - } - for (const key of removedKeys) { - delete settings[key]; - } - await setModifiedSettings(settings); - } catch (error) { - console.error("Failed to update stored settings:", error); - } -} -async function setModifiedSettings(settings: Partial) { - const updates: Record = {}; - for (const [key, value] of Object.entries(settings)) { - updates[key] = typeof value !== "string" ? JSON.stringify(value) : value; - } - await chrome.storage.local.set(updates); -} -async function getStoredSettings(): Promise { - return new Promise((resolve, reject) => { - chrome.storage.local.get((settings) => { - try { - const storedSettings: Partial = ( - Object.keys(settings) - .filter((key) => typeof key === "string") - .filter((key) => Object.keys(defaultConfiguration).includes(key as unknown as string)) as configurationKeys[] - ).reduce((acc, key) => Object.assign(acc, { [key]: parseStoredValue(settings[key] as string) }), {}); - const castedSettings = storedSettings as configuration; - resolve(castedSettings); - } catch (error) { - reject(error); - } - }); - }); -} diff --git a/src/pages/content/index.ts b/src/pages/content/index.ts index 8a9eb621..54b256bb 100644 --- a/src/pages/content/index.ts +++ b/src/pages/content/index.ts @@ -396,7 +396,9 @@ const storageChangeHandler = async (changes: StorageChanges, areaName: string) = }, volume_boost_amount: (newValue) => { sendExtensionOnlyMessage("volumeBoostAmountChange", { - volumeBoostAmount: newValue + volumeBoostAmount: newValue, + volumeBoostEnabled: options.enable_volume_boost, + volumeBoostMode: options.volume_boost_mode }); }, volume_boost_mode: (__oldValue, newValue) => { diff --git a/src/pages/embedded/index.ts b/src/pages/embedded/index.ts index 32d25265..d318819a 100644 --- a/src/pages/embedded/index.ts +++ b/src/pages/embedded/index.ts @@ -9,7 +9,7 @@ import { customCSSExists, updateCustomCSS } from "@/src/features/customCSS/utils import { disableDeepDarkCSS, enableDeepDarkCSS } from "@/src/features/deepDarkCSS"; import { deepDarkCSSExists, getDeepDarkCustomThemeStyle, updateDeepDarkCSS } from "@/src/features/deepDarkCSS/utils"; import { enableFeatureMenu, setupFeatureMenuEventListeners } from "@/src/features/featureMenu"; -import { featuresInMenu, updateFeatureMenuItemLabel, updateFeatureMenuTitle } from "@/src/features/featureMenu/utils"; +import { featuresInMenu, getFeatureMenuItem, 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"; @@ -220,9 +220,24 @@ window.addEventListener("DOMContentLoaded", function () { } case "volumeBoostAmountChange": { const { - data: { volumeBoostAmount } + data: { volumeBoostAmount, volumeBoostEnabled, volumeBoostMode } } = message; - applyVolumeBoost(volumeBoostAmount); + + switch (volumeBoostMode) { + case "global": { + if (!volumeBoostEnabled) return; + applyVolumeBoost(volumeBoostAmount); + break; + } + case "per_video": { + const volumeBoostButton = getFeatureMenuItem("volumeBoostButton") ?? getFeatureButton("volumeBoostButton"); + console.log(volumeBoostButton); + if (!volumeBoostButton) return; + const volumeBoostForVideoEnabled = volumeBoostButton.ariaChecked === "true"; + console.log(volumeBoostForVideoEnabled, volumeBoostButton.ariaChecked); + if (volumeBoostForVideoEnabled) applyVolumeBoost(volumeBoostAmount); + } + } break; } case "playerSpeedChange": { diff --git a/src/types/index.ts b/src/types/index.ts index 5cd2c1cc..d16bdff7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -306,7 +306,10 @@ export type ExtensionSendOnlyMessageMappings = { >; skipContinueWatchingChange: DataResponseMessage<"skipContinueWatchingChange", { skipContinueWatchingEnabled: boolean }>; videoHistoryChange: DataResponseMessage<"videoHistoryChange", { videoHistoryEnabled: boolean }>; - volumeBoostAmountChange: DataResponseMessage<"volumeBoostAmountChange", { volumeBoostAmount: number }>; + volumeBoostAmountChange: DataResponseMessage< + "volumeBoostAmountChange", + { volumeBoostAmount: number; volumeBoostEnabled: boolean; volumeBoostMode: VolumeBoostMode } + >; volumeBoostChange: DataResponseMessage<"volumeBoostChange", { volumeBoostEnabled: boolean; volumeBoostMode: VolumeBoostMode }>; }; export type FilterMessagesBySource = { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index f590ed0c..d48d8fa7 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -89,7 +89,7 @@ export const defaultConfiguration = { speed_adjustment_steps: 0.25, video_history_resume_type: "prompt", volume_adjustment_steps: 5, - volume_boost_amount: 1, + volume_boost_amount: 5, volume_boost_mode: "global" } satisfies configuration; export const configurationImportSchema: TypeToPartialZodSchema< diff --git a/src/utils/updateStoredSettings.ts b/src/utils/updateStoredSettings.ts new file mode 100644 index 00000000..98306528 --- /dev/null +++ b/src/utils/updateStoredSettings.ts @@ -0,0 +1,68 @@ +import type { configuration, configurationKeys } from "@/src/types"; + +import { defaultConfiguration } from "@/src/utils/constants"; +import { parseStoredValue } from "@/src/utils/utilities"; + +const changedKeys = Object.keys({ + osd_display_type: "" +} satisfies Partial>); + +export async function updateStoredSettings() { + try { + const settings = await getStoredSettings(); + + const removedKeys = Object.keys(settings).filter((key) => !Object.keys(defaultConfiguration).includes(key)); + + for (const changedKey of changedKeys) { + switch (changedKey) { + case "osd_display_type": { + if ((settings.osd_display_type as unknown as string) === "round") { + settings.osd_display_type = "circle"; + } + + break; + } + } + } + + for (const key of removedKeys) { + delete settings[key]; + } + + await setModifiedSettings(settings); + } catch (error) { + console.error("Failed to update stored settings:", error); + } +} + +async function setModifiedSettings(settings: Partial) { + const updates: Record = {}; + + for (const [key, value] of Object.entries(settings)) { + updates[key] = typeof value !== "string" ? JSON.stringify(value) : value; + } + + await chrome.storage.local.set(updates); +} + +async function getStoredSettings(): Promise { + return new Promise((resolve, reject) => { + chrome.storage.local.get((settings) => { + try { + const storedSettings: Partial = ( + Object.keys(settings) + + .filter((key) => typeof key === "string") + + .filter((key) => Object.keys(defaultConfiguration).includes(key as unknown as string)) as configurationKeys[] + ).reduce((acc, key) => Object.assign(acc, { [key]: parseStoredValue(settings[key] as string) }), {}); + + const castedSettings = storedSettings as configuration; + + resolve(castedSettings); + } catch (error) { + reject(error); + } + }); + }); +} diff --git a/vite.config.ts b/vite.config.ts index de1309b8..703075b1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,10 +26,11 @@ export default function build() { return defineConfig({ build: { emptyOutDir: false, + modulePreload: false, outDir: resolve(outDir, "temp"), rollupOptions: { input: { - background: resolve(pagesDir, "background", "index.ts"), + background: resolve(pagesDir, "background", "index.html"), options: resolve(pagesDir, "options", "index.html"), popup: resolve(pagesDir, "popup", "index.html") },