diff --git a/renderer/renderer.js b/renderer/renderer.js
index 230009e..296a887 100644
--- a/renderer/renderer.js
+++ b/renderer/renderer.js
@@ -862,6 +862,46 @@ function showChipContextMenu(ev, entry, type) {
setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
}
+// Teilfehler eines Uploads: fragt nach und sendet nur die fehlgeschlagenen Dateien erneut
+// (höchstens 3 Wiederholungen). `retryCall(failedItems)` führt den Upload erneut aus.
+async function retryFailedUploads(res, retryCall) {
+ for (let attempt = 0; attempt < 3; attempt++) {
+ const failedItems = !res?.ok && !res?.cancelled && Array.isArray(res?.retryItems) ? res.retryItems : [];
+ if (failedItems.length === 0) break;
+ showProgress(100, 'Upload teilweise fehlgeschlagen');
+ const uploadedInfo = res.uploadedCount ? `${res.uploadedCount} Datei(en) wurden hochgeladen, ` : '';
+ const again = await showActionConfirmModal({
+ title: 'Upload teilweise fehlgeschlagen',
+ message: `${uploadedInfo}${failedItems.length} Datei(en) nicht.\n\n${res.error || ''}\n\nNur die fehlgeschlagenen Dateien erneut senden?`,
+ confirmText: 'Nur Fehlgeschlagene wiederholen',
+ danger: true
+ });
+ if (!again) break;
+ showProgress(15, `Wiederhole ${failedItems.length} Datei(en) ...`, { cancellable: true });
+ res = await retryCall(failedItems);
+ }
+ return res;
+}
+
+// Hinweis, wenn die feste Sperre Dateien mit Zugangsdaten vom Upload ausgenommen hat.
+function warnBlockedFiles(res) {
+ const blocked = Array.isArray(res?.blockedFiles) ? res.blockedFiles : [];
+ if (blocked.length === 0) return;
+ const names = blocked.slice(0, 3).map(b => ppathBasename(b.path)).join(', ');
+ showWarning(`🛡️ ${blocked.length} Datei(en) mit Zugangsdaten wurden nicht hochgeladen: ${names}${blocked.length > 3 ? ' …' : ''}`);
+}
+
+// Ordner-Upload mit Wiederholung der fehlgeschlagenen Dateien.
+async function uploadFolderWithRetry(params) {
+ const res = await window.electronAPI.uploadLocalFolderToGitea(params);
+ warnBlockedFiles(res);
+ return retryFailedUploads(res, (failedItems) => window.electronAPI.uploadLocalFolderToGitea({
+ ...params,
+ items: failedItems,
+ messagePrefix: `${params.messagePrefix || 'Upload folder via GUI'} (Wiederholung)`
+ }));
+}
+
async function uploadDroppedPaths({ paths, owner, repo, destPath = '', cloneUrl = null, branch = 'HEAD' }) {
try { window.electronAPI.debugToMain('log', 'uploadDroppedPaths:start', { owner, repo, destPath, branch, pathCount: Array.isArray(paths) ? paths.length : 0 }); } catch (_) {}
console.log('[UPLOAD_DEBUG][renderer] uploadDroppedPaths:start', {
@@ -913,30 +953,16 @@ async function uploadDroppedPaths({ paths, owner, repo, destPath = '', cloneUrl
excludeFiles,
messagePrefix: 'Upload via GUI'
}), 900000, 'upload-paths-to-gitea'); // 15 Min Sicherheits-Timeout
+ warnBlockedFiles(res);
- // Teilfehler: nur die fehlgeschlagenen Dateien erneut senden (höchstens 3 Wiederholungen).
- for (let attempt = 0; attempt < 3; attempt++) {
- const failedItems = !res?.ok && !res?.cancelled && Array.isArray(res?.retryItems) ? res.retryItems : [];
- if (failedItems.length === 0) break;
- showProgress(100, 'Upload teilweise fehlgeschlagen');
- const uploadedInfo = res.uploadedCount ? `${res.uploadedCount} Datei(en) wurden hochgeladen, ` : '';
- const again = await showActionConfirmModal({
- title: 'Upload teilweise fehlgeschlagen',
- message: `${uploadedInfo}${failedItems.length} Datei(en) nicht.\n\n${res.error || ''}\n\nNur die fehlgeschlagenen Dateien erneut senden?`,
- confirmText: 'Nur Fehlgeschlagene wiederholen',
- danger: true
- });
- if (!again) break;
- showProgress(15, `Wiederhole ${failedItems.length} Datei(en) ...`, { cancellable: true });
- res = await withTimeout(window.electronAPI.uploadPathsToGitea({
- items: failedItems,
- owner,
- repo,
- destPath,
- branch,
- messagePrefix: 'Upload via GUI (Wiederholung)'
- }), 900000, 'upload-paths-to-gitea');
- }
+ res = await retryFailedUploads(res, (failedItems) => withTimeout(window.electronAPI.uploadPathsToGitea({
+ items: failedItems,
+ owner,
+ repo,
+ destPath,
+ branch,
+ messagePrefix: 'Upload via GUI (Wiederholung)'
+ }), 900000, 'upload-paths-to-gitea'));
if (res?.cancelled) {
hideProgress();
@@ -2712,7 +2738,24 @@ function ensureProgressUI() {
cancel.textContent = 'Wird abgebrochen …';
try { await window.electronAPI.cancelUpload(); } catch (_) {}
});
- container.appendChild(cancel);
+ // Pause-Knopf: hält die Übertragung an den nächsten Kontrollpunkten an und setzt sie fort.
+ const pause = document.createElement('button');
+ pause.id = 'folderProgressPause';
+ pause.textContent = PAUSE_LABEL;
+ pause.style.cssText = cancel.style.cssText;
+ pause.addEventListener('click', async () => {
+ pause.disabled = true;
+ try {
+ if (transferPaused) await window.electronAPI.resumeUpload();
+ else await window.electronAPI.pauseUpload();
+ } catch (_) {}
+ pause.disabled = false;
+ });
+
+ const buttonRow = document.createElement('div');
+ buttonRow.style.cssText = 'display:flex;gap:8px;';
+ buttonRow.append(pause, cancel);
+ container.appendChild(buttonRow);
// Esc bricht die laufende Übertragung ab, solange kein Dialog offen ist.
document.addEventListener('keydown', (e) => {
@@ -2726,6 +2769,22 @@ function ensureProgressUI() {
}
const CANCEL_LABEL = 'Abbrechen (Esc)';
+const PAUSE_LABEL = 'Pausieren';
+const RESUME_LABEL = 'Fortsetzen';
+const PAUSED_SUFFIX = ' · ⏸ pausiert';
+let transferPaused = false;
+
+// Zeigt den Pausen-Zustand im Fortschrittsbalken (Knopfbeschriftung und Hinweis im Text).
+function applyPauseState(paused) {
+ transferPaused = !!paused;
+ const pause = $('folderProgressPause');
+ const txt = $('folderProgressText');
+ if (pause) pause.textContent = transferPaused ? RESUME_LABEL : PAUSE_LABEL;
+ if (txt) {
+ const base = txt.innerText.replace(PAUSED_SUFFIX, '');
+ txt.innerText = transferPaused ? base + PAUSED_SUFFIX : base;
+ }
+}
function formatTransferBytes(bytes) {
if (!(bytes > 0)) return '0 KB';
@@ -2768,8 +2827,10 @@ function showProgress(percent, text, { cancellable = false } = {}) {
const bar = $('folderProgressBar');
const txt = $('folderProgressText');
const cancel = $('folderProgressCancel');
- if (txt) txt.innerText = text || '';
+ const pause = $('folderProgressPause');
+ if (txt) txt.innerText = (text || '') + (transferPaused && cancellable ? PAUSED_SUFFIX : '');
if (bar) bar.style.width = `${Math.min(100, Math.max(0, percent))}%`;
+ if (pause) pause.style.display = cancellable ? 'inline-block' : 'none';
if (cancel) {
cancel.style.display = cancellable ? 'inline-block' : 'none';
// Ein neuer Upload startet wieder mit aktivem Knopf; bei laufendem Abbruch bleibt er gesperrt.
@@ -2786,6 +2847,10 @@ function hideProgress() {
// Knopf für den nächsten Vorgang zurücksetzen (nach einem Abbruch ist er gesperrt).
const cancel = $('folderProgressCancel');
if (cancel) { cancel.disabled = false; cancel.textContent = CANCEL_LABEL; cancel.style.display = 'none'; }
+ const pause = $('folderProgressPause');
+ if (pause) { pause.disabled = false; pause.style.display = 'none'; }
+ transferPaused = false;
+ if (pause) pause.textContent = PAUSE_LABEL;
}, 500);
}
}
@@ -4922,7 +4987,7 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
const sel = await window.electronAPI.selectFolder();
if (sel) {
showProgress(0, 'Upload...', { cancellable: true });
- const res = await window.electronAPI.uploadLocalFolderToGitea({
+ const res = await uploadFolderWithRetry({
localFolder: sel,
owner,
repo: repoName,
@@ -5618,30 +5683,148 @@ async function createGitignoreForRepo(owner, repo) {
}
}
-// Zeigt die zuletzt aufgezeichneten Uploads; der Bestätigen-Knopf leert den Verlauf.
+// Zeigt die zuletzt aufgezeichneten Uploads, filterbar nach Ergebnis und Repo-Name.
+// Ein Klick auf einen Eintrag öffnet das Repo, „CSV exportieren“ speichert die gefilterte Liste.
async function showUploadHistory() {
- const res = await window.electronAPI.getUploadHistory();
- const history = (res && res.history) || [];
- if (history.length === 0) {
- await showInfoModal('Upload-Verlauf', 'Noch keine Uploads aufgezeichnet.');
- return;
- }
const icons = { ok: '✅', abgebrochen: '⏹️', fehler: '❌' };
- const lines = history.slice(0, 25).map((e) => {
- const when = new Date(e.at).toLocaleString('de-DE');
- const files = e.files != null ? `${e.files} Datei(en)` : '–';
- const failed = e.failed ? `, ${e.failed} fehlgeschlagen` : '';
- const took = e.durationMs ? ` · ${formatRemaining(e.durationMs / 1000)}` : '';
- const err = e.error ? `\n ${e.error}` : '';
- return `${icons[e.status] || '•'} ${when} · ${e.repo || '?'} · ${files}${failed} · ${e.kind}${took}${err}`;
- });
- if (history.length > 25) lines.push(`… und ${history.length - 25} ältere Einträge`);
- const clear = await showActionConfirmModal({
- title: 'Upload-Verlauf',
- message: lines.join('\n'),
- confirmText: 'Verlauf leeren'
- });
- if (clear) await window.electronAPI.clearUploadHistory();
+
+ const modal = document.createElement('div');
+ modal.className = 'modal confirm-modal';
+ modal.style.zIndex = '99999';
+ const card = document.createElement('div');
+ card.className = 'modalContent card confirm-modal-content';
+ card.style.cssText = 'width:min(760px,92vw);max-width:92vw;';
+
+ const titleEl = document.createElement('h2');
+ titleEl.className = 'confirm-modal-title';
+ titleEl.textContent = '🕘 Upload-Verlauf';
+
+ const filterRow = document.createElement('div');
+ filterRow.style.cssText = 'display:flex;gap:8px;margin:10px 0;flex-wrap:wrap;';
+ const statusSel = document.createElement('select');
+ statusSel.style.cssText = 'width:auto;flex:0 0 160px;';
+ for (const [value, label] of [['alle', 'Alle'], ['ok', 'Erfolgreich'], ['abgebrochen', 'Abgebrochen'], ['fehler', 'Fehler']]) {
+ const opt = document.createElement('option');
+ opt.value = value;
+ opt.textContent = label;
+ statusSel.appendChild(opt);
+ }
+ const search = document.createElement('input');
+ search.type = 'search';
+ search.placeholder = 'Repo suchen …';
+ search.style.cssText = 'width:auto;flex:1;min-width:160px;';
+ filterRow.appendChild(statusSel);
+ filterRow.appendChild(search);
+
+ const listEl = document.createElement('div');
+ listEl.className = 'confirm-modal-message';
+ listEl.style.cssText = 'max-height:50vh;overflow:auto;font-size:12px;line-height:1.6;';
+
+ const hint = document.createElement('div');
+ hint.style.cssText = 'font-size:11px;opacity:0.7;margin-top:6px;';
+ hint.textContent = 'Ein Klick auf einen Eintrag öffnet das Repo.';
+
+ const btnWrap = document.createElement('div');
+ btnWrap.className = 'modal-buttons confirm-modal-buttons';
+ const closeBtn = document.createElement('button');
+ closeBtn.className = 'confirm-modal-btn confirm-modal-btn--primary';
+ closeBtn.textContent = 'Schließen';
+ const exportBtn = document.createElement('button');
+ exportBtn.className = 'confirm-modal-btn confirm-modal-btn--secondary';
+ exportBtn.textContent = 'CSV exportieren';
+ const clearBtn = document.createElement('button');
+ clearBtn.className = 'confirm-modal-btn confirm-modal-btn--secondary';
+ clearBtn.textContent = 'Verlauf leeren';
+ btnWrap.append(closeBtn, exportBtn, clearBtn);
+
+ card.append(titleEl, filterRow, listEl, hint, btnWrap);
+ modal.appendChild(card);
+ document.body.appendChild(modal);
+
+ const currentFilter = () => ({ status: statusSel.value, query: search.value });
+
+ const render = async () => {
+ const res = await window.electronAPI.getUploadHistory(currentFilter());
+ const history = (res && res.history) || [];
+ listEl.textContent = '';
+ exportBtn.disabled = history.length === 0;
+ if (history.length === 0) {
+ listEl.textContent = statusSel.value === 'alle' && !search.value.trim()
+ ? 'Noch keine Uploads aufgezeichnet.'
+ : 'Keine Einträge für diesen Filter.';
+ return;
+ }
+ for (const e of history.slice(0, 40)) {
+ const row = document.createElement('div');
+ row.className = 'upload-history-row';
+ const [owner, repo] = String(e.repo || '').split('/');
+ const canOpen = !!(owner && repo);
+ if (canOpen) row.title = `${e.repo} öffnen`;
+ else row.style.cursor = 'default';
+
+ const when = new Date(e.at).toLocaleString('de-DE');
+ const files = e.files != null ? `${e.files} Datei(en)` : '–';
+ const failed = e.failed ? `, ${e.failed} fehlgeschlagen` : '';
+ const took = e.durationMs ? ` · ${formatRemaining(e.durationMs / 1000)}` : '';
+ const main = document.createElement('div');
+ main.textContent = `${icons[e.status] || '•'} ${when} · ${e.repo || '?'} · ${files}${failed} · ${e.kind}${took}`;
+ row.appendChild(main);
+ if (e.error) {
+ const err = document.createElement('div');
+ err.className = 'upload-history-err';
+ err.textContent = e.error;
+ row.appendChild(err);
+ }
+ if (canOpen) {
+ row.addEventListener('click', () => {
+ close();
+ loadRepoContents(owner, repo, '');
+ });
+ }
+ listEl.appendChild(row);
+ }
+ if (history.length > 40) {
+ const more = document.createElement('div');
+ more.style.cssText = 'opacity:0.7;padding:6px 8px;';
+ more.textContent = `… und ${history.length - 40} ältere Einträge (im CSV-Export enthalten)`;
+ listEl.appendChild(more);
+ }
+ };
+
+ const onKey = (e) => { if (e.key === 'Escape') close(); };
+ function close() {
+ document.removeEventListener('keydown', onKey);
+ modal.remove();
+ }
+ document.addEventListener('keydown', onKey);
+ closeBtn.onclick = close;
+
+ exportBtn.onclick = async () => {
+ exportBtn.disabled = true;
+ try {
+ const res = await window.electronAPI.exportUploadHistoryCsv(currentFilter());
+ if (res && res.ok) showSuccess(`Verlauf exportiert: ${res.count} Einträge → ${res.savedTo}`);
+ else if (res && !res.canceled) showError('Export fehlgeschlagen: ' + (res.error || 'Unbekannter Fehler'));
+ } finally {
+ exportBtn.disabled = false;
+ }
+ };
+
+ clearBtn.onclick = async () => {
+ const ok = await showActionConfirmModal({
+ title: 'Verlauf leeren',
+ message: 'Alle aufgezeichneten Uploads werden gelöscht.',
+ confirmText: 'Leeren',
+ danger: true
+ });
+ if (!ok) return;
+ await window.electronAPI.clearUploadHistory();
+ render();
+ };
+ statusSel.onchange = render;
+ let searchTimer = null;
+ search.oninput = () => { clearTimeout(searchTimer); searchTimer = setTimeout(render, 150); };
+ render();
}
async function cleanupGitignoredFiles(owner, repo, repoPath) {
@@ -5875,7 +6058,7 @@ function setupBackgroundContextMenu() {
showProgress(0, `Lade "${clipboard.item.name}" hoch...`, { cancellable: true });
try {
const res = clipboard.item.isDirectory
- ? await window.electronAPI.uploadLocalFolderToGitea({
+ ? await uploadFolderWithRetry({
localFolder: clipboard.item.path,
owner,
repo,
@@ -5890,6 +6073,7 @@ function setupBackgroundContextMenu() {
destPath: currentPath,
branch: getDefaultBranch(owner, repo)
});
+ warnBlockedFiles(res);
if (res?.cancelled) { setStatus('Upload abgebrochen'); return; }
if (!res?.ok) throw new Error(res?.error || 'Upload fehlgeschlagen');
showSuccess(`"${clipboard.item.name}" nach Gitea kopiert`);
@@ -6918,6 +7102,16 @@ window.addEventListener('DOMContentLoaded', async () => {
}
});
+ // Pause/Fortsetzen (auch vom Tray-Menü ausgelöst)
+ if (window.electronAPI.onTransferPauseState) {
+ window.electronAPI.onTransferPauseState(({ paused }) => applyPauseState(paused));
+ }
+
+ // Tray-Menü "Upload-Verlauf"
+ if (window.electronAPI.onOpenUploadHistory) {
+ window.electronAPI.onOpenUploadHistory(() => { showUploadHistory(); });
+ }
+
// Progress listeners
window.electronAPI.onFolderUploadProgress(p => {
showProgress(p.percent, describeTransferProgress('Upload', p), { cancellable: true });
@@ -7438,6 +7632,12 @@ function showCreateReleaseModal(owner, repo) {
assetGroup.appendChild(assetRow);
card.appendChild(assetGroup);
+ // Ergebnis der Prüfung des gewählten Installers (Prüfsumme, Abgleich mit der .sha256-Datei)
+ const assetInfoBox = document.createElement('div');
+ assetInfoBox.id = 'releaseAssetInfo';
+ assetInfoBox.style.cssText = 'display:none;margin:-4px 0 12px;padding:10px 12px;border-radius:8px;font-size:12px;line-height:1.5;border:1px solid rgba(255,255,255,0.12);';
+ card.appendChild(assetInfoBox);
+
const optionsGroup = document.createElement('div');
optionsGroup.className = 'input-group';
optionsGroup.style.cssText = 'display: flex; gap: 20px;';
@@ -7480,17 +7680,67 @@ function showCreateReleaseModal(owner, repo) {
// Variable zum Speichern des gewählten Dateipfads
let selectedAssetPath = null;
+ // Prüfergebnis zum gewählten Asset (Name, SHA-256, Version, .sha256-Datei) oder null
+ let assetInfo = null;
+
+ const showAssetInfo = (kind, lines) => {
+ const colors = {
+ ok: ['rgba(74,222,128,0.12)', 'rgba(74,222,128,0.4)'],
+ warn: ['rgba(251,191,36,0.12)', 'rgba(251,191,36,0.4)'],
+ error: ['rgba(248,113,113,0.14)', 'rgba(248,113,113,0.5)']
+ }[kind];
+ assetInfoBox.style.display = 'block';
+ assetInfoBox.style.background = colors[0];
+ assetInfoBox.style.borderColor = colors[1];
+ assetInfoBox.textContent = '';
+ for (const line of lines) {
+ const div = document.createElement('div');
+ div.textContent = line;
+ assetInfoBox.appendChild(div);
+ }
+ };
+
+ // Prüft die gewählte Datei (Prüfsumme, Version, .sha256-Datei) und füllt Tag und Name vor.
+ const applyAsset = async (filePath) => {
+ selectedAssetPath = filePath;
+ assetInfo = null;
+ const fileName = filePath.split(/[\\/]/).pop();
+ $('releaseAssetInput').value = fileName;
+ $('releaseAssetInput').style.color = 'var(--text-primary)';
+ $('releaseAssetInput').style.borderColor = 'var(--accent-primary)';
+ showAssetInfo('warn', ['Prüfe Datei und berechne SHA-256 …']);
+
+ const info = await window.electronAPI.inspectReleaseFile({ filePath });
+ if (!info || !info.ok) {
+ showAssetInfo('error', [`Datei konnte nicht geprüft werden: ${info?.error || 'Unbekannter Fehler'}`]);
+ return;
+ }
+ assetInfo = info;
+
+ if (info.version) {
+ if (!$('releaseTag').value.trim()) $('releaseTag').value = `v${info.version}`;
+ if (!$('releaseName').value.trim()) $('releaseName').value = `Version ${info.version}`;
+ }
+
+ const lines = [`${info.name} · ${formatTransferBytes(info.size)}`, `SHA-256: ${info.sha256}`];
+ if (info.sidecarMatches === true) {
+ lines.push('✓ Stimmt mit der Datei .sha256 überein. Beide werden hochgeladen.');
+ showAssetInfo('ok', lines);
+ } else if (info.sidecarMatches === false) {
+ lines.push(`✗ Die Datei .sha256 nennt eine andere Prüfsumme (${info.sidecarSha256}). Der Installer wurde nach dem Bauen verändert oder die Prüfsumme gehört zu einer anderen Version. Das Release wird so nicht erstellt.`);
+ showAssetInfo('error', lines);
+ } else {
+ lines.push('Keine .sha256-Datei neben dem Installer gefunden. Die Prüfsumme wird nur in den Release-Text eingetragen.');
+ showAssetInfo('warn', lines);
+ }
+ };
// Event Listener: Datei auswählen
$('btnSelectReleaseAsset').onclick = async () => {
try {
const res = await window.electronAPI.selectFile();
if (res.ok && res.files && res.files.length > 0) {
- selectedAssetPath = res.files[0];
- const fileName = selectedAssetPath.split(/[\\/]/).pop();
- $('releaseAssetInput').value = fileName;
- $('releaseAssetInput').style.color = 'var(--text-primary)';
- $('releaseAssetInput').style.borderColor = 'var(--accent-primary)';
+ await applyAsset(res.files[0]);
}
} catch (error) {
console.error('Fehler beim Auswählen der Datei:', error);
@@ -7511,7 +7761,30 @@ function showCreateReleaseModal(owner, repo) {
await showInfoModal('Eingabe fehlt', 'Tag Version ist erforderlich!');
return;
}
-
+
+ // Mit gewähltem Installer: Prüfsumme abgleichen, Tag gegen vorhandene Releases prüfen und die
+ // Prüfsumme in den Release-Text schreiben, damit der Updater das Release findet und verifiziert.
+ let releaseBody = body;
+ if (assetInfo) {
+ if (assetInfo.sidecarMatches === false) {
+ await showInfoModal('Prüfsumme stimmt nicht', 'Die Datei .sha256 passt nicht zum Installer. Bitte neu bauen (npm run build) und den Installer erneut wählen.', true);
+ return;
+ }
+ const prep = await window.electronAPI.prepareRelease({
+ owner,
+ repo,
+ tag,
+ notes: body,
+ assets: [{ name: assetInfo.name, sha256: assetInfo.sha256 }],
+ platform: currentState.platform
+ });
+ if (!prep || !prep.ok) {
+ await showInfoModal('Release nicht möglich', prep?.error || 'Unbekannter Fehler', true);
+ return;
+ }
+ releaseBody = prep.body;
+ }
+
setStatus('Creating release...');
try {
@@ -7521,7 +7794,7 @@ function showCreateReleaseModal(owner, repo) {
repo,
tag_name: tag,
name,
- body,
+ body: releaseBody,
target_commitish: target,
prerelease,
draft,
@@ -7545,6 +7818,20 @@ function showCreateReleaseModal(owner, repo) {
});
if (uploadRes.ok) {
+ // Prüfsummen-Datei neben den Installer legen, so sucht der Updater sie.
+ if (assetInfo && assetInfo.sidecarPath) {
+ showProgress(80, 'Lade Prüfsummen-Datei hoch…');
+ const sidecarRes = await window.electronAPI.uploadReleaseAsset({
+ owner,
+ repo,
+ releaseId: res.release.id,
+ filePath: assetInfo.sidecarPath,
+ fileName: assetInfo.sidecarPath.split(/[\\/]/).pop()
+ });
+ if (!sidecarRes || !sidecarRes.ok) {
+ showWarning('Release erstellt, aber die .sha256-Datei konnte nicht hochgeladen werden. Die Prüfsumme steht im Release-Text.');
+ }
+ }
setStatus(`Release "${tag}" und Asset erstellt!`);
} else {
console.error('Asset Upload fehlgeschlagen:', uploadRes.error);
diff --git a/renderer/settings-extra.js b/renderer/settings-extra.js
index ba296c6..ef48dd8 100644
--- a/renderer/settings-extra.js
+++ b/renderer/settings-extra.js
@@ -38,10 +38,13 @@
set('prefZoom', String(p.zoom));
if ($('prefZoom') && !$('prefZoom').value) $('prefZoom').value = '100';
set('prefAutoUpdate', p.autoUpdateCheck);
+ set('prefShowSplash', p.showSplash);
set('prefCloseAction', p.closeAction);
set('prefCloneDir', p.defaultCloneDir);
set('prefSecretScan', p.uploadSecretScan);
+ set('prefBlockSecrets', p.uploadBlockSecrets);
set('prefUploadPreview', p.uploadPreview);
+ set('prefNotifyTransfers', p.notifyTransfers);
set('prefUploadExcludes', p.uploadExtraExcludes);
}
@@ -54,10 +57,13 @@
theme: val('prefTheme'),
zoom: Number(val('prefZoom')),
autoUpdateCheck: chk('prefAutoUpdate'),
+ showSplash: chk('prefShowSplash'),
closeAction: val('prefCloseAction'),
defaultCloneDir: val('prefCloneDir'),
uploadSecretScan: chk('prefSecretScan'),
+ uploadBlockSecrets: chk('prefBlockSecrets'),
uploadPreview: val('prefUploadPreview'),
+ notifyTransfers: val('prefNotifyTransfers'),
uploadExtraExcludes: val('prefUploadExcludes')
};
}
diff --git a/tray-menu-preload.js b/tray-menu-preload.js
new file mode 100644
index 0000000..7aadcf3
--- /dev/null
+++ b/tray-menu-preload.js
@@ -0,0 +1,9 @@
+// Preload für das Tray-Menüfenster (renderer/tray-menu.html): nur die vier nötigen Kanäle.
+const { contextBridge, ipcRenderer } = require('electron');
+
+contextBridge.exposeInMainWorld('trayApi', {
+ onState: (cb) => ipcRenderer.on('tray-menu-state', (_event, state) => cb(state)),
+ action: (id) => ipcRenderer.send('tray-menu:action', id),
+ resize: (height) => ipcRenderer.send('tray-menu:resize', height),
+ close: () => ipcRenderer.send('tray-menu:close')
+});