Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions release-notes.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
# 2.0.0 (26071)
[feature release]
# 2.0.1 (26072)
[bugfix release]

## Release Notes

- Refreshed the icons to reflect the new branding.
- Added support for OpenTubeX, Invidious and Piped, with the option to choose between them or FreeTube as the default redirection target.
- Added an optional keyboard shortcut to redirect the current tab, configurable to replace the tab or open a new one.
- Refined the options, popup, and introduction layouts with shared component styles.
- Added a direct Options button to the introduction screen and social links in the popup footer.
- Added Finnish (by Ricky-Tigg) localization and refreshed Estonian and Dutch strings for the updated URL rules UI.
- Improved YouTube URL and embed detection to correctly match subdomains instead of relying on substring checks.
- Made context menu redirects, icon updates, and URL rule loading more reliable on startup.
- Hardened the iframe placeholder against unsafe HTML in replacement text.
- Fixed the toolbar icon to update per tab instead of globally, so switching between YouTube and other tabs now immediately shows the correct allowed/blocked state.
- Fixed the RedirectTube-branded color icon option in Settings not saving due to a conflicting element ID with the FreeTube color icon.
- Reorganized the keyboard shortcut settings in Options and the introduction screen into a clearer, connected group with a proper label on the behavior dropdown.
- Corrected the Polish translation of the keyboard shortcut label.

> [!WARNING]
> For Firefox, the `-unsigned.xpi` artifact will most likely not install. Use the signed version (`-signed.xpi`) or download from the [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/redirecttube/). For Chromium-based browsers, download the `-chromium-unsigned.zip` and load it unpacked in Developer Mode or download from the [Chrome Web Store](https://chromewebstore.google.com/detail/jpbaggklodpddjcadlebabhiopjkjfjh/).
58 changes: 47 additions & 11 deletions src/browser/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let isDarkThemePreferred = false;
let currentMenuLang = null;
let urlRulesConfig = getDefaultUrlRulesConfig();
let urlRulesConfigLoaded = false;
const tabAllowedState = new Map();

extensionApi.runtime.onInstalled.addListener((details) => {
if (details.reason === "install") {
Expand Down Expand Up @@ -74,27 +75,49 @@ extensionApi.runtime.onStartup.addListener(() => {
createContextMenu();
});

extensionApi.runtime.onMessage.addListener((request) => {
extensionApi.runtime.onMessage.addListener((request, sender) => {
const tabId = sender && sender.tab && typeof sender.tab.id === "number"
? sender.tab.id
: undefined;

if (request.message === RUNTIME_MESSAGES.currentUrlChanged && request.url) {
// Ensure urlRulesConfig is loaded before evaluating the URL (avoid race with startup)
if (!urlRulesConfigLoaded) {
extensionApi.storage.local.get(STORAGE_KEYS.urlRulesConfig, (result = {}) => {
urlRulesConfig = normalizeUrlRulesConfig(result[STORAGE_KEYS.urlRulesConfig]);
urlRulesConfigLoaded = true;
handleUrlChange(request.url);
handleUrlChange(request.url, tabId);
});
} else {
handleUrlChange(request.url);
handleUrlChange(request.url, tabId);
}
return;
}

if (request.message === RUNTIME_MESSAGES.redirecttubeTheme) {
isDarkThemePreferred = Boolean(request.isDark);
updateActionIcon();
updateActionIcon(tabId);
}
});

extensionApi.tabs.onActivated.addListener(({ tabId }) => {
if (tabAllowedState.has(tabId)) {
updateActionIcon(tabId);
return;
}
extensionApi.tabs.get(tabId, (tab) => {
if (extensionApi.runtime.lastError || !tab || !tab.url) {
return;
}
tabAllowedState.set(tabId, isRedirectableYoutubeUrl(tab.url, urlRulesConfig));
updateActionIcon(tabId);
});
});

extensionApi.tabs.onRemoved.addListener((tabId) => {
tabAllowedState.delete(tabId);
});

extensionApi.storage.onChanged.addListener((changes, areaName) => {
if (areaName !== "local") {
return;
Expand All @@ -106,6 +129,7 @@ extensionApi.storage.onChanged.addListener((changes, areaName) => {
if (changes[STORAGE_KEYS.extensionIcon]) {
cachedExtensionIcon = changes[STORAGE_KEYS.extensionIcon].newValue || "redirecttube-color";
updateActionIcon();
tabAllowedState.forEach((_isAllowed, tabId) => updateActionIcon(tabId));
}

if (changes[STORAGE_KEYS.selectedPlayer]) {
Expand Down Expand Up @@ -166,28 +190,40 @@ extensionApi.commands.onCommand.addListener((command) => {
});
});

function handleUrlChange(url) {
function handleUrlChange(url, tabId) {
if (!url) {
return;
}
lastUrlAllowed = isRedirectableYoutubeUrl(url, urlRulesConfig);
updateActionIcon();
const isAllowed = isRedirectableYoutubeUrl(url, urlRulesConfig);
lastUrlAllowed = isAllowed;
if (typeof tabId === "number") {
tabAllowedState.set(tabId, isAllowed);
}
updateActionIcon(tabId);
}

function updateActionIcon() {
function updateActionIcon(tabId) {
const isAllowed =
typeof tabId === "number" && tabAllowedState.has(tabId)
? tabAllowedState.get(tabId)
: lastUrlAllowed;
const path = getIconPath(
cachedExtensionIcon,
lastUrlAllowed,
isAllowed,
isDarkThemePreferred
);
try {
const resolvedPath =
extensionApi && extensionApi.runtime && typeof extensionApi.runtime.getURL === "function"
? extensionApi.runtime.getURL(path)
: path;
extensionApi.action.setIcon({ path: resolvedPath });
const details = { path: resolvedPath };
if (typeof tabId === "number") {
details.tabId = tabId;
}
extensionApi.action.setIcon(details);
} catch (err) {
console.error("Failed to set action icon:", err, { path });
console.error("Failed to set action icon:", err, { path, tabId });
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/browser/i18n/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@
"behaviorLabel": "Działanie skrótu klawiaturowego",
"replaceTab": "Zastąp bieżącą kartę",
"newTab": "Otwórz w nowej karcie",
"changeLabel": "Skrót klawiszowy",
"change": "Zmień skrót klawiszowy…",
"changeLabel": "Skrót klawiaturowy",
"change": "Zmień skrót klawiaturowy…",
"firefoxHint": "Otwórz menu przeglądarki Firefox (☰) → „Rozszerzenia i motywy”, a następnie kliknij ikonę koła zębatego (⚙) i wybierz opcję „Zarządzaj skrótami rozszerzeń”."
}
},
Expand Down
21 changes: 21 additions & 0 deletions src/browser/introduction.css
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,27 @@ body {
margin: 0;
}

.flex-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
cursor: pointer;
}

#shortcutOptions {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}

#shortcutOptions select,
#shortcutOptions button {
width: min(100%, 320px);
}

.instance-row {
display: flex;
flex-wrap: wrap;
Expand Down
25 changes: 14 additions & 11 deletions src/browser/introduction.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,20 @@ <h2 data-i18n="introduction.step2.opentubex.title">OpenTubeX requires the deskto
</div>
<div class="section" id="introduction-step-3" hidden>
<h1 data-i18n="introduction.step3.title">Keyboard shortcut</h1>
<label class="flex-row">
<input type="checkbox" id="shortcutEnabled">
<span data-i18n="introduction.step3.enable">Enable keyboard shortcut redirect</span>
</label>
<div id="shortcutOptions">
<select id="shortcutBehavior">
<option value="replaceTab" data-i18n="options.shortcut.replaceTab">Replace current tab</option>
<option value="newTab" data-i18n="options.shortcut.newTab">Open in a new tab</option>
</select>
<button id="changeShortcutButton" type="button" data-i18n="options.shortcut.change">Change keyboard shortcut…</button>
<p class="note" id="shortcutFirefoxHint" hidden data-i18n="introduction.step3.firefoxHint">Open the Firefox menu (☰) → Add-ons and Themes, then click the gear icon (⚙) and choose "Manage Extension Shortcuts".</p>
<div class="setup-panel" id="shortcutPanel">
<label class="flex-row" for="shortcutEnabled">
<input type="checkbox" id="shortcutEnabled">
<span data-i18n="introduction.step3.enable">Enable keyboard shortcut redirect</span>
</label>
<div id="shortcutOptions">
<label for="shortcutBehavior" data-i18n="options.shortcut.behaviorLabel">Keyboard shortcut behavior</label>
<select id="shortcutBehavior">
<option value="replaceTab" data-i18n="options.shortcut.replaceTab">Replace current tab</option>
<option value="newTab" data-i18n="options.shortcut.newTab">Open in a new tab</option>
</select>
<button id="changeShortcutButton" type="button" data-i18n="options.shortcut.change">Change keyboard shortcut…</button>
<p class="note" id="shortcutFirefoxHint" hidden data-i18n="introduction.step3.firefoxHint">Open the Firefox menu (☰) → Add-ons and Themes, then click the gear icon (⚙) and choose "Manage Extension Shortcuts".</p>
</div>
</div>
<div class="buttons">
<button id="step3Back" type="button" data-i18n="introduction.step2.back">Back</button>
Expand Down
2 changes: 1 addition & 1 deletion src/browser/manifest.chromium.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "__MSG_extension_name__",
"description": "__MSG_extension_description__",
"author": "Michał Stankiewicz",
"version": "2.0.0",
"version": "2.0.1",
"manifest_version": 3,
"default_locale": "en",
"icons": {
Expand Down
2 changes: 1 addition & 1 deletion src/browser/manifest.gecko.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "__MSG_extension_name__",
"description": "__MSG_extension_description__",
"author": "Michał Stankiewicz",
"version": "2.0.0",
"version": "2.0.1",
"manifest_version": 3,
"default_locale": "en",
"icons": {
Expand Down
18 changes: 18 additions & 0 deletions src/browser/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,24 @@ h1 {
margin-top: 0;
}

.shortcut-group-connected {
border-radius: 24px 24px 0 0;
margin-bottom: 0;
}

.shortcut-group-middle {
border-top: 1px solid var(--background-element);
border-radius: 0;
margin-top: 0;
margin-bottom: 0;
}

.shortcut-group-bottom {
border-top: 1px solid var(--background-element);
border-radius: 0 0 24px 24px;
margin-top: 0;
}

.player-choice {
display: inline-flex;
align-items: center;
Expand Down
10 changes: 5 additions & 5 deletions src/browser/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ <h1><span data-i18n="options.options.title">Options</span> / <span id="categoryN
<option value="redirect" data-i18n="options.popupBehavior.redirect">Redirect to FreeTube</option>
</select>
</div>
<div class="section flex-row">
<div class="section flex-row" id="shortcutEnabledSection">
<div class="section-label">
<label for="shortcutEnabled" data-i18n="options.shortcut.enable">Enable keyboard shortcut redirect</label>
</div>
<input type="checkbox" id="shortcutEnabled">
</div>
<div class="section flex-row" id="shortcutOptions">
<div class="section flex-row shortcut-group-middle" id="shortcutOptions">
<div class="section-label">
<label for="shortcutBehavior" data-i18n="options.shortcut.behaviorLabel">Keyboard shortcut behavior</label>
</div>
Expand All @@ -101,13 +101,13 @@ <h1><span data-i18n="options.options.title">Options</span> / <span id="categoryN
<option value="newTab" data-i18n="options.shortcut.newTab">Open in a new tab</option>
</select>
</div>
<div class="section flex-row">
<div class="section flex-row shortcut-group-bottom" id="shortcutKeySection">
<div class="section-label">
<label data-i18n="options.shortcut.changeLabel">Keyboard shortcut key</label>
<p class="note" id="shortcutFirefoxHint" hidden data-i18n="options.shortcut.firefoxHint">Open the Firefox menu (☰) → Add-ons and Themes, then click the gear icon (⚙) and choose "Manage Extension Shortcuts".</p>
</div>
<button id="changeShortcutButton" type="button" data-i18n="options.shortcut.change">Change keyboard shortcut…</button>
</div>
<p class="note" id="shortcutFirefoxHint" hidden data-i18n="options.shortcut.firefoxHint">Open the Firefox menu (☰) → Add-ons and Themes, then click the gear icon (⚙) and choose "Manage Extension Shortcuts".</p>
<div class="section flex-row" id="playerPrefferedSection">
<div class="section-label">
<label data-i18n="options.playerSettings.label">Preferred player setup</label>
Expand Down Expand Up @@ -183,7 +183,7 @@ <h1><span data-i18n="options.options.title">Options</span> / <span id="categoryN
</div>
</label>
<label class="extension-icon">
<input type="radio" name="extensionIcon" value="color" id="colorIcon">
<input type="radio" name="extensionIcon" value="color" id="freetubeColorIcon">
<div class="extension-icons">
<img src="img/icns/freetube/color/example.png" alt="Color icons">
</div>
Expand Down
13 changes: 11 additions & 2 deletions src/browser/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,17 @@ function setPreferredInstanceVisibility(player) {

function setShortcutOptionsVisibility(enabled) {
const shortcutOptions = document.getElementById("shortcutOptions");
const shortcutKeySection = document.getElementById("shortcutKeySection");
const shortcutEnabledSection = document.getElementById("shortcutEnabledSection");
if (shortcutOptions) {
shortcutOptions.hidden = !enabled;
}
if (shortcutKeySection) {
shortcutKeySection.hidden = !enabled;
}
if (shortcutEnabledSection) {
shortcutEnabledSection.classList.toggle("shortcut-group-connected", enabled);
}
}

function isFirefox() {
Expand Down Expand Up @@ -594,8 +602,9 @@ document.querySelector("#iframeBehavior")?.addEventListener("change", function()
document
.querySelector("#iframeEnhancedPreview")
?.addEventListener("change", saveOptions);
document.querySelector("#colorIcon")?.addEventListener("click", saveOptions);
document.querySelector("#monoIcon")?.addEventListener("click", saveOptions);
document.querySelectorAll('input[name="extensionIcon"]').forEach((input) => {
input.addEventListener("change", saveOptions);
});
document.querySelectorAll('input[name="selectedPlayer"]').forEach((input) => {
input.addEventListener("change", saveOptions);
input.addEventListener("change", syncPreferredInstanceVisibility);
Expand Down
Loading