release: Multi-Hoster-Upload 2.1.3

This commit is contained in:
Sucukdeluxe
2026-08-11 00:06:32 +02:00
parent ae80dde362
commit 13d21aa354
10 changed files with 107 additions and 24 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ Multi-Hoster-Upload is a Windows desktop application for sending file batches to
Download the current Setup or Portable build from the [latest GitHub release](https://github.com/Sucukdeluxe/Multi-Hoster-Upload/releases/latest).
The latest public release is version 2.1.2. Use the release page for the executables and the full English changelog.
The latest public release is version 2.1.3. Use the release page for the executables and the full English changelog.
## Features
+3 -1
View File
@@ -1,4 +1,6 @@
function configureStartupRenderer(app) {
function configureStartupRenderer(app, env = process.env, platform = process.platform) {
const sessionName = String(env && env.SESSIONNAME || '');
if (platform === 'win32' && /^RDP-/i.test(sessionName)) app.disableHardwareAcceleration();
return app;
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "multi-hoster-uploader",
"version": "2.1.2",
"version": "2.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "multi-hoster-uploader",
"version": "2.1.2",
"version": "2.1.3",
"dependencies": {
"chokidar": "^3.6.0",
"undici": "^7.29.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "multi-hoster-uploader",
"version": "2.1.2",
"version": "2.1.3",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js",
"scripts": {
+37 -9
View File
@@ -6545,6 +6545,28 @@ function setupListeners() {
}
// --- Update UI ---
function _formatUpdateReleaseNotes(value) {
const output = [];
let pendingBlank = false;
for (const rawLine of String(value || '').replace(/\r\n?/g, '\n').split('\n')) {
let line = rawLine.trim();
if (!line) {
if (output.length > 0) pendingBlank = true;
continue;
}
line = line
.replace(/^#{1,6}\s+/, '')
.replace(/^[-*+]\s+/, '• ')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
if (pendingBlank && output.at(-1) !== '') output.push('');
output.push(line);
pendingBlank = false;
}
return output.join('\n');
}
function showUpdateBanner(info) {
if (!info) return;
_knownUpdateInfo = { ...info, available: true };
@@ -6561,9 +6583,12 @@ function showUpdateBanner(info) {
const notesBody = document.getElementById('updateReleaseNotesBody');
const installButton = document.getElementById('installUpdateBtn');
if (title) title.textContent = 'Eine neue Version ist verfügbar';
if (message) message.textContent = `Update v${version} verfügbar`;
if (message) {
message.textContent = `Update v${version} verfügbar`;
message.hidden = false;
}
if (notes && notesBody) {
const releaseNotes = String(info.releaseNotes || '').trim();
const releaseNotes = _formatUpdateReleaseNotes(info.releaseNotes);
notesBody.textContent = releaseNotes.length > 2400 ? `${releaseNotes.slice(0, 2399)}` : releaseNotes;
notes.hidden = !releaseNotes;
}
@@ -6585,38 +6610,41 @@ function handleUpdateProgress(data) {
_updateInstallBusy = true;
_setUpdateDialogBusy(true);
_setUpdateProgress(0, 'Download 0%');
if (message) message.textContent = 'Download wird vorbereitet…';
if (message) message.hidden = true;
if (button) button.textContent = 'Download 0%';
} else if (progress.stage === 'downloading') {
const percent = Math.max(0, Math.min(100, Math.round(Number(progress.percent) || 0)));
_updateInstallBusy = true;
_setUpdateDialogBusy(true);
_setUpdateProgress(percent, `Download ${percent}%`);
if (message) message.textContent = `Update wird heruntergeladen… ${percent}%`;
if (message) message.hidden = true;
if (button) button.textContent = `Download ${percent}%`;
} else if (progress.stage === 'verifying') {
_updateInstallBusy = true;
_setUpdateDialogBusy(true);
_setUpdateProgress(100, 'Prüfen…');
if (message) message.textContent = 'Download wird geprüft…';
if (message) message.hidden = true;
if (button) button.textContent = 'Prüfen…';
} else if (progress.stage === 'prepared') {
_updateInstallBusy = true;
_setUpdateDialogBusy(true);
_setUpdateProgress(100, 'Neustart…');
if (message) message.textContent = 'Update ist bereit. Einstellungen werden gespeichert…';
if (message) message.hidden = true;
if (button) button.textContent = 'Neustart…';
} else if (progress.stage === 'launching' || progress.stage === 'done') {
_updateInstallBusy = true;
_setUpdateDialogBusy(true);
_setUpdateProgress(100, 'Neustart…');
if (message) message.textContent = progress.stage === 'done' ? 'Update installiert. Die Anwendung startet neu…' : 'Update wird installiert. Die Anwendung startet neu…';
if (message) message.hidden = true;
if (button) button.textContent = 'Neustart…';
} else if (progress.stage === 'error') {
_updateInstallBusy = false;
_setUpdateDialogBusy(false);
_setUpdateProgress(0, 'Update fehlgeschlagen');
if (message) message.textContent = `Update fehlgeschlagen: ${String(progress.error || 'Unbekannter Fehler').slice(0, 400)}`;
if (message) {
message.hidden = false;
message.textContent = `Update fehlgeschlagen: ${String(progress.error || 'Unbekannter Fehler').slice(0, 400)}`;
}
if (button) {
button.disabled = false;
button.textContent = 'Wiederholen';
@@ -6761,7 +6789,7 @@ async function installKnownUpdate() {
_setUpdateProgress(0, 'Download 0%');
const message = document.getElementById('updateMessage');
const button = document.getElementById('installUpdateBtn');
if (message) message.textContent = 'Download wird vorbereitet…';
if (message) message.hidden = true;
if (button) button.textContent = 'Download 0%';
try {
await persistQueueStateNow();
-1
View File
@@ -2286,7 +2286,6 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
color: var(--text-muted);
font-size: 11px;
line-height: 1.55;
white-space: pre-wrap;
}
.update-release-notes-title {
+15 -4
View File
@@ -10,8 +10,9 @@ module.exports = async function afterPack(context) {
}
const productFilename = context.packager?.appInfo?.productFilename;
if (!productFilename) {
console.warn(" rcedit: skipped - productFilename not available");
const version = context.packager?.appInfo?.version;
if (!productFilename || !version) {
console.warn(" rcedit: skipped - application metadata not available");
return;
}
@@ -24,8 +25,18 @@ module.exports = async function afterPack(context) {
console.warn(" rcedit: skipped - app_icon.ico not found");
return;
}
console.log(` rcedit: patching icon -> ${exePath}`);
await rcedit(exePath, { icon: iconPath });
console.log(` rcedit: branding executable -> ${exePath}`);
await rcedit(exePath, {
icon: iconPath,
"file-version": version,
"product-version": version,
"version-string": {
FileDescription: "Multi Hoster Uploader",
InternalName: productFilename,
OriginalFilename: `${productFilename}.exe`,
ProductName: "Multi Hoster Uploader"
}
});
} catch (error) {
console.warn(` rcedit: failed - ${String(error)}`);
}
+34
View File
@@ -1,6 +1,7 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Module = require('node:module');
const path = require('node:path');
const packageJson = require('../package.json');
test('packages every Electron preload referenced by the main process', () => {
@@ -8,6 +9,39 @@ test('packages every Electron preload referenced by the main process', () => {
assert.ok(packageJson.build.files.includes('preload-drop-target.js'));
});
test('afterPack brands the executable metadata shown by Windows', async () => {
let editCall = null;
const originalLoad = Module._load;
const afterPackPath = require.resolve('../scripts/afterPack.cjs');
delete require.cache[afterPackPath];
Module._load = function (request, parent, isMain) {
if (request === 'rcedit') {
return async (exePath, options) => { editCall = { exePath, options }; };
}
return originalLoad.call(this, request, parent, isMain);
};
try {
const afterPack = require(afterPackPath);
await afterPack({
appOutDir: 'C:\\release',
packager: { appInfo: { productFilename: 'Multi-Hoster-Upload', version: '9.8.7' } }
});
} finally {
Module._load = originalLoad;
delete require.cache[afterPackPath];
}
assert.equal(editCall.exePath, path.join('C:\\release', 'Multi-Hoster-Upload.exe'));
assert.equal(editCall.options['file-version'], '9.8.7');
assert.equal(editCall.options['product-version'], '9.8.7');
assert.deepEqual(editCall.options['version-string'], {
FileDescription: 'Multi Hoster Uploader',
InternalName: 'Multi-Hoster-Upload',
OriginalFilename: 'Multi-Hoster-Upload.exe',
ProductName: 'Multi Hoster Uploader'
});
});
test('close readiness is signaled only after the renderer explicitly finishes initialization', () => {
const listeners = new Map();
const sent = [];
+8 -2
View File
@@ -29,12 +29,18 @@ class TestBrowserWindow extends EventEmitter {
}
}
test('configureStartupRenderer leaves hardware acceleration enabled', () => {
test('configureStartupRenderer leaves hardware acceleration enabled for a local Windows session', () => {
let calls = 0;
configureStartupRenderer({ disableHardwareAcceleration() { calls++; } });
configureStartupRenderer({ disableHardwareAcceleration() { calls++; } }, { SESSIONNAME: 'Console' }, 'win32');
assert.equal(calls, 0);
});
test('configureStartupRenderer disables hardware acceleration for a Windows Remote Desktop session', () => {
let calls = 0;
configureStartupRenderer({ disableHardwareAcceleration() { calls++; } }, { SESSIONNAME: 'RDP-Tcp#12' }, 'win32');
assert.equal(calls, 1);
});
test('createStartupWindow forces the main window to start hidden', () => {
const startup = createStartupWindow(TestBrowserWindow, { width: 1100, show: true });
+6 -3
View File
@@ -1423,7 +1423,7 @@ setTimeout(async () => {
check('Sidebar indicator stays aligned at the minimum window size', queueProgressVisibility.minimum.sidebarIndicatorAligned);
check('Minimum window keeps the settings header compact', compactSettingsHeader <= 58);
const updateOverlayState = await wc.executeJavaScript('_knownUpdateInfo = { available: true, remoteVersion: "9.9.9" }; _syncHeaderUpdateState(); document.getElementById("headerUpdateBtn").focus(); showUpdateBanner({ remoteVersion: "9.9.9", releaseNotes: "Added live language switching.\\\\nImproved settings layout." }); (() => { const overlay = document.getElementById("updateBanner"); const dialog = overlay?.querySelector(".update-dialog"); const button = document.getElementById("headerUpdateBtn"); return [overlay?.classList.contains("update-overlay"), overlay?.style.display, dialog?.getAttribute("role"), dialog?.getAttribute("aria-modal"), button?.hidden, getComputedStyle(button).display].join("|"); })()');
const updateOverlayState = await wc.executeJavaScript('_knownUpdateInfo = { available: true, remoteVersion: "9.9.9" }; _syncHeaderUpdateState(); document.getElementById("headerUpdateBtn").focus(); showUpdateBanner({ remoteVersion: "9.9.9", releaseNotes: "\\\\n\\\\n\\\\n## New in this version\\\\n\\\\n\\\\n### Menus and navigation\\\\n\\\\n- Added live language switching.\\\\n- Improved settings layout.\\\\n\\\\n\\\\n" }); (() => { const overlay = document.getElementById("updateBanner"); const dialog = overlay?.querySelector(".update-dialog"); const button = document.getElementById("headerUpdateBtn"); return [overlay?.classList.contains("update-overlay"), overlay?.style.display, dialog?.getAttribute("role"), dialog?.getAttribute("aria-modal"), button?.hidden, getComputedStyle(button).display].join("|"); })()');
check('Available update opens an accessible update dialog', updateOverlayState === 'true|flex|dialog|true|false|flex');
await new Promise(resolve => setTimeout(resolve, 100));
@@ -1450,8 +1450,8 @@ setTimeout(async () => {
const updateDialogActions = await wc.executeJavaScript('[document.getElementById("dismissUpdateBtn")?.textContent?.trim(), document.getElementById("installUpdateBtn")?.textContent?.trim()].join("|")');
check('Update dialog offers cancel and install actions', updateDialogActions === 'Abbrechen|Jetzt installieren');
const updateDialogChangelog = await wc.executeJavaScript('document.getElementById("updateReleaseNotes")?.hidden + "|" + document.querySelector(".update-release-notes-title")?.textContent?.trim() + "|" + document.getElementById("updateReleaseNotesBody")?.textContent');
check('Update dialog shows the GitHub changelog with real line breaks', updateDialogChangelog === 'false|Changelog|Added live language switching.\\nImproved settings layout.');
const updateDialogChangelog = await wc.executeJavaScript('(() => { const title = document.querySelector(".update-release-notes-title"); const body = document.getElementById("updateReleaseNotesBody"); const titleRect = title?.getBoundingClientRect(); const bodyRect = body?.getBoundingClientRect(); return { hidden: document.getElementById("updateReleaseNotes")?.hidden, title: title?.textContent?.trim(), body: body?.textContent, gap: bodyRect && titleRect ? bodyRect.top - titleRect.bottom : null }; })()');
check('Update dialog renders a compact normalized changelog', updateDialogChangelog.hidden === false && updateDialogChangelog.title === 'Changelog' && updateDialogChangelog.body === 'New in this version\\n\\nMenus and navigation\\n\\n• Added live language switching.\\nImproved settings layout.' && updateDialogChangelog.gap <= 10);
const updateHeaderHint = await wc.executeJavaScript('(() => { const button = document.getElementById("headerUpdateBtn"); return [button?.textContent?.trim(), button?.getAttribute("aria-label"), button?.dataset.tooltip].join("|"); })()');
check('Available update gives the header action a matching hint', updateHeaderHint === 'Update verfügbar|Update v9.9.9 verfügbar. Klicken zum Installieren.|Update v9.9.9 verfügbar. Klicken zum Installieren.');
@@ -1475,12 +1475,15 @@ setTimeout(async () => {
closeDisabled: document.getElementById('updateCloseBtn').disabled,
dismissDisabled: document.getElementById('dismissUpdateBtn').disabled,
headerHidden: header.hidden,
messageHidden: document.getElementById('updateMessage').hidden,
messageText: document.getElementById('updateMessage').textContent,
progressLabel: progress.getAttribute('aria-label'),
progressText: progress.getAttribute('aria-valuetext')
};
})()\`);
check('Busy update keeps its progress dialog open', busyUpdateState.display === 'flex' && busyUpdateState.hidden === 'false' && busyUpdateState.closeDisabled === true && busyUpdateState.dismissDisabled === true && busyUpdateState.headerHidden === false);
check('Update progress exposes an accessible live value', busyUpdateState.progressLabel === 'Update-Fortschritt' && busyUpdateState.progressText === 'Download 50%');
check('Busy update shows progress only below the bar', busyUpdateState.messageHidden === true && busyUpdateState.messageText === 'Update v9.9.9 verfügbar');
const updateErrorRecovery = await wc.executeJavaScript('handleUpdateProgress({ stage: "error", error: "Netzwerkfehler" }); document.getElementById("dismissUpdateBtn").click(); document.getElementById("updateBanner").style.display + "|" + document.getElementById("updateCloseBtn").disabled + "|" + document.getElementById("dismissUpdateBtn").disabled + "|" + document.getElementById("headerUpdateBtn").hidden');
check('Update errors restore all close actions', updateErrorRecovery === 'none|false|false|false');