Compare commits

..

No commits in common. "f04c0b64ccc077e9c0676f532885808f828da488" and "83647c264be007988d453e69840dbe9fc9eac5c2" have entirely different histories.

8 changed files with 4 additions and 74 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.5.16", "version": "4.5.15",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.5.16", "version": "4.5.15",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.5.16", "version": "4.5.15",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",

View File

@ -39,7 +39,6 @@
<div class="modal-actions update-modal-actions"> <div class="modal-actions update-modal-actions">
<button class="btn-secondary" id="updateModalDismissBtn" type="button" onclick="dismissUpdateModal()">Nein</button> <button class="btn-secondary" id="updateModalDismissBtn" type="button" onclick="dismissUpdateModal()">Nein</button>
<button class="btn-secondary" id="updateModalSkipBtn" type="button" onclick="skipUpdateVersion()">Diese Version ueberspringen</button>
<button class="btn-primary" id="updateModalConfirmBtn" type="button" onclick="confirmUpdateModal()">Ja, herunterladen</button> <button class="btn-primary" id="updateModalConfirmBtn" type="button" onclick="confirmUpdateModal()">Ja, herunterladen</button>
</div> </div>
</div> </div>

View File

@ -49,8 +49,6 @@ const UI_TEXT_DE = {
performanceModeBalanced: 'Ausgewogen', performanceModeBalanced: 'Ausgewogen',
performanceModeSpeed: 'Max Geschwindigkeit', performanceModeSpeed: 'Max Geschwindigkeit',
smartSchedulerLabel: 'Smart Queue Scheduler aktivieren', smartSchedulerLabel: 'Smart Queue Scheduler aktivieren',
smartSchedulerHint: 'Bevorzugt kuerzere VODs und aeltere Queue-Eintraege zuerst, damit der Durchsatz gleichmaessig bleibt. Deaktivieren = strikte Einfuegereihenfolge.',
streamerInvalid: 'Twitch-Username ungueltig (4-25 Zeichen, Buchstaben/Zahlen/Unterstrich).',
duplicatePreventionLabel: 'Duplikate in Queue verhindern', duplicatePreventionLabel: 'Duplikate in Queue verhindern',
persistQueueLabel: 'Queue zwischen App-Starts speichern', persistQueueLabel: 'Queue zwischen App-Starts speichern',
metadataCacheMinutesLabel: 'Metadata-Cache (Minuten)', metadataCacheMinutesLabel: 'Metadata-Cache (Minuten)',
@ -247,7 +245,6 @@ const UI_TEXT_DE = {
modalDismiss: 'Nein', modalDismiss: 'Nein',
modalDownloadConfirm: 'Ja, herunterladen', modalDownloadConfirm: 'Ja, herunterladen',
modalInstallConfirm: 'Ja, installieren', modalInstallConfirm: 'Ja, installieren',
modalSkipVersion: 'Diese Version ueberspringen',
changelogLabel: 'Changelog', changelogLabel: 'Changelog',
showChangelog: 'Changelog anzeigen', showChangelog: 'Changelog anzeigen',
hideChangelog: 'Changelog ausblenden', hideChangelog: 'Changelog ausblenden',

View File

@ -49,8 +49,6 @@ const UI_TEXT_EN = {
performanceModeBalanced: 'Balanced', performanceModeBalanced: 'Balanced',
performanceModeSpeed: 'Max Speed', performanceModeSpeed: 'Max Speed',
smartSchedulerLabel: 'Enable smart queue scheduler', smartSchedulerLabel: 'Enable smart queue scheduler',
smartSchedulerHint: 'Prefers shorter VODs and older queue entries first so the queue throughput stays steady. Disable to drain in strict insertion order.',
streamerInvalid: 'Invalid Twitch username (4-25 chars, letters/digits/underscore).',
duplicatePreventionLabel: 'Prevent duplicate queue entries', duplicatePreventionLabel: 'Prevent duplicate queue entries',
persistQueueLabel: 'Keep queue between app restarts', persistQueueLabel: 'Keep queue between app restarts',
metadataCacheMinutesLabel: 'Metadata Cache (Minutes)', metadataCacheMinutesLabel: 'Metadata Cache (Minutes)',
@ -247,7 +245,6 @@ const UI_TEXT_EN = {
modalDismiss: 'No', modalDismiss: 'No',
modalDownloadConfirm: 'Yes, download', modalDownloadConfirm: 'Yes, download',
modalInstallConfirm: 'Yes, install', modalInstallConfirm: 'Yes, install',
modalSkipVersion: 'Skip this version',
changelogLabel: 'Changelog', changelogLabel: 'Changelog',
showChangelog: 'Show changelog', showChangelog: 'Show changelog',
hideChangelog: 'Hide changelog', hideChangelog: 'Hide changelog',

View File

@ -210,19 +210,7 @@ function renderStreamers(): void {
async function addStreamer(): Promise<void> { async function addStreamer(): Promise<void> {
const input = byId<HTMLInputElement>('newStreamer'); const input = byId<HTMLInputElement>('newStreamer');
const name = input.value.trim().toLowerCase(); const name = input.value.trim().toLowerCase();
if (!name) { if (!name || (config.streamers ?? []).includes(name)) {
return;
}
// Twitch usernames: 4-25 characters, alphanumeric + underscore.
// Catch typos / invalid input before it hits the API and silently
// returns "streamer not found".
if (!/^[a-zA-Z0-9_]{4,25}$/.test(name)) {
showAppToast(UI_TEXT.static.streamerInvalid, 'warn');
return;
}
if ((config.streamers ?? []).includes(name)) {
return; return;
} }

View File

@ -91,8 +91,6 @@ function applyLanguageToStaticUI(): void {
setText('performanceModeBalanced', UI_TEXT.static.performanceModeBalanced); setText('performanceModeBalanced', UI_TEXT.static.performanceModeBalanced);
setText('performanceModeSpeed', UI_TEXT.static.performanceModeSpeed); setText('performanceModeSpeed', UI_TEXT.static.performanceModeSpeed);
setText('smartSchedulerLabel', UI_TEXT.static.smartSchedulerLabel); setText('smartSchedulerLabel', UI_TEXT.static.smartSchedulerLabel);
setTitle('smartSchedulerLabel', UI_TEXT.static.smartSchedulerHint);
setTitle('smartSchedulerToggle', UI_TEXT.static.smartSchedulerHint);
setText('duplicatePreventionLabel', UI_TEXT.static.duplicatePreventionLabel); setText('duplicatePreventionLabel', UI_TEXT.static.duplicatePreventionLabel);
setText('persistQueueLabel', UI_TEXT.static.persistQueueLabel); setText('persistQueueLabel', UI_TEXT.static.persistQueueLabel);
setText('metadataCacheMinutesLabel', UI_TEXT.static.metadataCacheMinutesLabel); setText('metadataCacheMinutesLabel', UI_TEXT.static.metadataCacheMinutesLabel);
@ -141,7 +139,6 @@ function applyLanguageToStaticUI(): void {
setText('updateModalTitle', UI_TEXT.updates.modalAvailableTitle); setText('updateModalTitle', UI_TEXT.updates.modalAvailableTitle);
setText('updateModalDismissBtn', UI_TEXT.updates.modalDismiss); setText('updateModalDismissBtn', UI_TEXT.updates.modalDismiss);
setText('updateModalConfirmBtn', UI_TEXT.updates.modalDownloadConfirm); setText('updateModalConfirmBtn', UI_TEXT.updates.modalDownloadConfirm);
setText('updateModalSkipBtn', UI_TEXT.updates.modalSkipVersion);
setText('updateChangelogLabel', UI_TEXT.updates.changelogLabel); setText('updateChangelogLabel', UI_TEXT.updates.changelogLabel);
setText('updateChangelogToggle', UI_TEXT.updates.showChangelog); setText('updateChangelogToggle', UI_TEXT.updates.showChangelog);
setText('updateChangelogEmpty', UI_TEXT.updates.noChangelog); setText('updateChangelogEmpty', UI_TEXT.updates.noChangelog);

View File

@ -9,20 +9,6 @@ let updateBannerState: 'idle' | 'available' | 'downloading' | 'ready' = 'idle';
let updateChangelogExpanded = false; let updateChangelogExpanded = false;
let shouldOpenUpdateModalOnAvailable = false; let shouldOpenUpdateModalOnAvailable = false;
const SKIPPED_UPDATE_VERSION_KEY = 'twitch-vod-manager:skipped-update-version';
function getSkippedUpdateVersion(): string {
try { return localStorage.getItem(SKIPPED_UPDATE_VERSION_KEY) || ''; } catch { return ''; }
}
function persistSkippedUpdateVersion(version: string): void {
try { localStorage.setItem(SKIPPED_UPDATE_VERSION_KEY, version); } catch { /* localStorage may be unavailable */ }
}
function clearSkippedUpdateVersion(): void {
try { localStorage.removeItem(SKIPPED_UPDATE_VERSION_KEY); } catch { /* localStorage may be unavailable */ }
}
function notifyUpdate(message: string, type: 'info' | 'warn' = 'info'): void { function notifyUpdate(message: string, type: 'info' | 'warn' = 'info'): void {
const toastFn = (window as unknown as { showAppToast?: (msg: string, kind?: 'info' | 'warn') => void }).showAppToast; const toastFn = (window as unknown as { showAppToast?: (msg: string, kind?: 'info' | 'warn') => void }).showAppToast;
if (typeof toastFn === 'function') { if (typeof toastFn === 'function') {
@ -292,11 +278,6 @@ function refreshUpdateModalTexts(): void {
byId('updateModalConfirmBtn').textContent = isReady byId('updateModalConfirmBtn').textContent = isReady
? UI_TEXT.updates.modalInstallConfirm ? UI_TEXT.updates.modalInstallConfirm
: UI_TEXT.updates.modalDownloadConfirm; : UI_TEXT.updates.modalDownloadConfirm;
// Skip-version only makes sense before the download. Once the .exe is
// already on disk and ready to install, hide the button.
const skipBtn = byId<HTMLButtonElement>('updateModalSkipBtn');
skipBtn.textContent = UI_TEXT.updates.modalSkipVersion;
skipBtn.style.display = isReady ? 'none' : '';
byId('updateChangelogLabel').textContent = UI_TEXT.updates.changelogLabel; byId('updateChangelogLabel').textContent = UI_TEXT.updates.changelogLabel;
byId('updateChangelogEmpty').textContent = UI_TEXT.updates.noChangelog; byId('updateChangelogEmpty').textContent = UI_TEXT.updates.noChangelog;
@ -320,19 +301,6 @@ function dismissUpdateModal(): void {
byId('updateModal').classList.remove('show'); byId('updateModal').classList.remove('show');
} }
function skipUpdateVersion(): void {
const v = (latestUpdateInfo?.version || latestUpdateVersion || '').trim();
if (v) {
persistSkippedUpdateVersion(v);
}
dismissUpdateModal();
hideUpdateBanner();
updateBannerState = 'idle';
// Note: latestUpdateInfo is intentionally kept so a manual "Check for
// updates" can still re-surface the same version if the user changes
// their mind (manual checks bypass the skip-version filter).
}
function confirmUpdateModal(): void { function confirmUpdateModal(): void {
dismissUpdateModal(); dismissUpdateModal();
@ -527,22 +495,11 @@ window.api.onUpdateAvailable((info: UpdateInfo) => {
updateCheckInProgress = false; updateCheckInProgress = false;
updateReady = false; updateReady = false;
updateDownloadInProgress = false; updateDownloadInProgress = false;
const wasManual = manualUpdateCheckPending;
manualUpdateCheckPending = false; manualUpdateCheckPending = false;
manualUpdateOutcomeHandled = true; manualUpdateOutcomeHandled = true;
latestDownloadProgress = null; latestDownloadProgress = null;
setCheckButtonCheckingState(false); setCheckButtonCheckingState(false);
// If the user explicitly skipped this exact version, suppress the auto
// notification entirely — banner stays hidden, no modal popup. A manual
// "Check for updates" click overrides the skip so the user can change
// their mind.
const isSkipped = getSkippedUpdateVersion() === activeInfo.version;
if (isSkipped && !wasManual) {
shouldOpenUpdateModalOnAvailable = false;
return;
}
setUpdateBannerAvailableUi(activeInfo); setUpdateBannerAvailableUi(activeInfo);
if (shouldOpenUpdateModalOnAvailable) { if (shouldOpenUpdateModalOnAvailable) {
@ -552,7 +509,6 @@ window.api.onUpdateAvailable((info: UpdateInfo) => {
shouldOpenUpdateModalOnAvailable = false; shouldOpenUpdateModalOnAvailable = false;
}); });
window.api.onUpdateNotAvailable(() => { window.api.onUpdateNotAvailable(() => {
updateCheckInProgress = false; updateCheckInProgress = false;
setCheckButtonCheckingState(false); setCheckButtonCheckingState(false);
@ -584,10 +540,6 @@ window.api.onUpdateDownloadProgress((progress: UpdateDownloadProgress) => {
}); });
window.api.onUpdateDownloaded((info: UpdateInfo) => { window.api.onUpdateDownloaded((info: UpdateInfo) => {
// Once a version is actually downloaded the user clearly stopped
// skipping it — clear the skip flag so future updates aren't masked
// by a stale entry.
clearSkippedUpdateVersion();
const activeInfo = rememberUpdateInfo(info); const activeInfo = rememberUpdateInfo(info);
setDownloadReadyUi(activeInfo); setDownloadReadyUi(activeInfo);
openUpdateModal(activeInfo); openUpdateModal(activeInfo);