diff --git a/renderer/index.html b/renderer/index.html
index 176d090..5e1c8dc 100644
--- a/renderer/index.html
+++ b/renderer/index.html
@@ -47,6 +47,7 @@
+
@@ -81,6 +82,8 @@
+
+
diff --git a/renderer/renderer.js b/renderer/renderer.js
index 55ca230..90e583e 100644
--- a/renderer/renderer.js
+++ b/renderer/renderer.js
@@ -595,32 +595,34 @@ async function uploadDroppedPaths({ paths, owner, repo, destPath = '', cloneUrl
try {
let res = null;
- // Fast path: treat dropped item as file upload first.
- // If it is actually a directory, the call returns ok=false and we fall back to uploadAndPush.
- const fileTry = await withTimeout(window.electronAPI.uploadGiteaFile({
- owner,
- repo,
- localPath: [p],
- destPath,
- branch,
- platform: currentState.platform
- }), 15000, 'upload-gitea-file');
+ const pathType = await withTimeout(window.electronAPI.getPathType(p), 5000, 'get-path-type');
+ console.log('[UPLOAD_DEBUG][renderer] uploadDroppedPaths:pathType', { path: p, pathType });
+ try { window.electronAPI.debugToMain('log', 'uploadDroppedPaths:pathType', { path: p, pathType }); } catch (_) {}
- console.log('[UPLOAD_DEBUG][renderer] uploadDroppedPaths:fileTry', { path: p, fileTry });
- try { window.electronAPI.debugToMain('log', 'uploadDroppedPaths:fileTry', { path: p, fileTry }); } catch (_) {}
+ if (!pathType?.ok) {
+ throw new Error(pathType?.error || 'path-type-failed');
+ }
- const fileFailed = !fileTry?.ok || (Array.isArray(fileTry.results) && fileTry.results.some(r => !r.ok));
- if (!fileFailed) {
- res = { ok: true, via: 'upload-gitea-file', results: fileTry.results || [] };
- } else {
- res = await withTimeout(window.electronAPI.uploadAndPush({
+ if (pathType.type === 'file') {
+ res = await withTimeout(window.electronAPI.uploadGiteaFile({
+ owner,
+ repo,
+ localPath: [p],
+ destPath,
+ branch,
+ platform: currentState.platform
+ }), 15000, 'upload-gitea-file');
+ } else if (pathType.type === 'dir') {
+ res = await withTimeout(window.electronAPI.uploadLocalFolderToGitea({
localFolder: p,
owner,
repo,
destPath,
- cloneUrl,
- branch
- }), 30000, 'upload-and-push');
+ branch,
+ messagePrefix: 'Upload folder via GUI'
+ }), 600000, 'upload-local-folder-to-gitea');
+ } else {
+ throw new Error(`Nicht unterstuetzter Pfadtyp: ${pathType.type || 'unknown'}`);
}
console.log('[UPLOAD_DEBUG][renderer] uploadDroppedPaths:itemResult', { path: p, res });
@@ -628,7 +630,10 @@ async function uploadDroppedPaths({ paths, owner, repo, destPath = '', cloneUrl
if (!res?.ok) {
failedCount++;
- errors.push(`${baseName}: ${res?.error || 'Unbekannter Fehler'}`);
+ const detailedFailure = Array.isArray(res?.failedFiles) && res.failedFiles.length > 0
+ ? `${baseName}: ${res.failedFiles[0].targetPath} - ${res.failedFiles[0].error || 'Unbekannter Fehler'}`
+ : `${baseName}: ${res?.error || 'Unbekannter Fehler'}`;
+ errors.push(detailedFailure);
continue;
}
@@ -951,12 +956,13 @@ function getDefaultBranch(owner, repo) {
// Navigations-Status für die Explorer-Ansicht
let currentState = {
- view: 'none', // 'local', 'gitea-list', 'gitea-repo'
+ view: 'none', // 'local', 'gitea-list', 'gitea-repo', 'gitea-trash'
owner: null,
repo: null,
path: '',
platform: 'gitea' // 'gitea' | 'github'
};
+let lastRepoPathBeforeTrash = '';
let repoLoadRequestId = 0;
const USER_CACHE_MS = 5 * 60 * 1000;
@@ -1852,6 +1858,31 @@ function showSuccess(msg) { setStatus(msg); showToast(msg, 'success', 3000); log
function showWarning(msg) { setStatus(msg); showToast(msg, 'warning'); logActivity('warning', msg); }
function showInfo(msg) { setStatus(msg); showToast(msg, 'info', 2500); }
+function formatUploadFailureDetails(result, fallbackMessage = 'Upload fehlgeschlagen') {
+ const lines = [];
+ const summary = result?.error || fallbackMessage;
+ lines.push(summary);
+
+ if (Array.isArray(result?.failedFiles) && result.failedFiles.length > 0) {
+ lines.push('');
+ lines.push('Betroffene Dateien:');
+ result.failedFiles.slice(0, 8).forEach((entry) => {
+ const target = entry?.targetPath || entry?.localFile || 'unbekannter Pfad';
+ const error = entry?.error || 'Unbekannter Fehler';
+ lines.push(`- ${target}: ${error}`);
+ });
+ if (result.failedFiles.length > 8) {
+ lines.push(`- ... und ${result.failedFiles.length - 8} weitere`);
+ }
+ }
+
+ return lines.join('\n');
+}
+
+async function showUploadFailureModal(title, result, fallbackMessage = 'Upload fehlgeschlagen') {
+ await showInfoModal(title, formatUploadFailureDetails(result, fallbackMessage), true);
+}
+
function normalizeSearchText(value) {
return String(value || '')
.toLowerCase()
@@ -2927,7 +2958,9 @@ function updateNavigationUI() {
if (!btnBack) return;
// Back Button zeigen, wenn wir in einem Repo oder tief in Ordnern sind
- if (currentState.view === 'gitea-repo' ||
+ if (currentState.view === 'gitea-repo' ||
+ currentState.view === 'gitea-trash' ||
+ currentState.view === 'gitea-trash-global' ||
(currentState.view === 'gitea-list' && currentState.path !== '')) {
btnBack.classList.remove('hidden');
} else {
@@ -2935,6 +2968,333 @@ function updateNavigationUI() {
}
}
+async function loadGlobalTrashView() {
+ if (currentState.platform !== 'gitea') {
+ showError('Globaler Papierkorb ist aktuell nur für Gitea verfügbar.');
+ return;
+ }
+
+ currentState.view = 'gitea-trash-global';
+ currentState.path = '_trash';
+ updateNavigationUI();
+
+ const btnCommits = $('btnCommits');
+ const btnReleases = $('btnReleases');
+ const btnPurgeTrash = $('btnPurgeTrash');
+ if (btnCommits) btnCommits.classList.add('hidden');
+ if (btnReleases) btnReleases.classList.add('hidden');
+
+ const grid = $('explorerGrid');
+ if (!grid) return;
+ grid.innerHTML = '';
+ setStatus('Lade globalen Papierkorb...');
+
+ let repos = Array.isArray(currentGiteaRepos) ? currentGiteaRepos.slice() : [];
+ if (repos.length === 0) {
+ const reposRes = await window.electronAPI.listGiteaRepos();
+ if (!reposRes?.ok) {
+ showError('Globaler Papierkorb konnte nicht geladen werden: Repo-Liste fehlgeschlagen');
+ return;
+ }
+ repos = Array.isArray(reposRes.repos) ? reposRes.repos : [];
+ currentGiteaRepos = repos;
+ }
+
+ const allItems = [];
+ let purgedFilesTotal = 0;
+
+ for (const r of repos) {
+ const owner = r?.owner?.login || r?.owner?.username;
+ const repoName = r?.name;
+ if (!owner || !repoName) continue;
+
+ const ref = getDefaultBranch(owner, repoName);
+ const res = await window.electronAPI.listGiteaTrash({
+ owner,
+ repo: repoName,
+ ref,
+ autoPurge: true,
+ autoPurgeDays: 7
+ });
+ if (!res?.ok) continue;
+
+ purgedFilesTotal += Number(res?.purgeSummary?.purgedFiles || 0);
+ const items = Array.isArray(res.items) ? res.items : [];
+ items.forEach(item => {
+ allItems.push({
+ ...item,
+ owner,
+ repo: repoName,
+ ref
+ });
+ });
+ }
+
+ if (btnPurgeTrash) {
+ btnPurgeTrash.classList.remove('hidden');
+ btnPurgeTrash.textContent = '🧹 Global Purge 7d';
+ btnPurgeTrash.title = 'Papierkorb älter als 7 Tage in allen Repositories leeren';
+ btnPurgeTrash.onclick = async () => {
+ const ok = await showActionConfirmModal({
+ title: 'Globaler Purge',
+ message: 'Alle Papierkorb-Einträge älter als 7 Tage in ALLEN Repositories löschen?',
+ confirmText: 'Global Purge',
+ danger: true
+ });
+ if (!ok) return;
+
+ showProgress(35, 'Globaler Purge läuft...');
+ let totalPurged = 0;
+ for (const r of repos) {
+ const owner = r?.owner?.login || r?.owner?.username;
+ const repoName = r?.name;
+ if (!owner || !repoName) continue;
+ const ref = getDefaultBranch(owner, repoName);
+ const purgeRes = await window.electronAPI.purgeGiteaTrash({
+ owner,
+ repo: repoName,
+ ref,
+ olderThanDays: 7
+ });
+ if (purgeRes?.ok) totalPurged += Number(purgeRes.purgedFiles || 0);
+ }
+ hideProgress();
+ showSuccess(`Global Purge abgeschlossen: ${totalPurged} Dateien entfernt`);
+ await loadGlobalTrashView();
+ };
+ }
+
+ if (purgedFilesTotal > 0) {
+ setStatus(`Auto-Purge (global): ${purgedFilesTotal} Dateien entfernt`);
+ }
+
+ if (allItems.length === 0) {
+ const emptyEl = document.createElement('div');
+ emptyEl.style.cssText = 'grid-column: 1/-1; text-align: center; padding: 40px; color: var(--text-muted);';
+ emptyEl.textContent = '🧺 Globaler Papierkorb ist leer';
+ grid.appendChild(emptyEl);
+ setStatus('Globaler Papierkorb ist leer');
+ return;
+ }
+
+ allItems.sort((a, b) => String(b.restoreTimestamp).localeCompare(String(a.restoreTimestamp)));
+ for (const item of allItems) {
+ const card = document.createElement('div');
+ card.className = 'item-card';
+
+ const iconEl = makeFileIconEl(item.name || item.originalPath || 'item', false);
+ const nameEl = document.createElement('div');
+ nameEl.className = 'item-name';
+ nameEl.textContent = item.originalPath || item.name || '(unbekannt)';
+
+ const metaEl = document.createElement('div');
+ metaEl.className = 'trash-card-meta';
+ metaEl.textContent = `${item.owner}/${item.repo} | Trash: ${item.trashPath} | Zeit: ${item.restoreTimestamp}`;
+
+ const actions = document.createElement('div');
+ actions.className = 'trash-card-actions';
+ actions.style.position = 'relative';
+ actions.style.zIndex = '3';
+
+ const restoreBtn = document.createElement('button');
+ restoreBtn.type = 'button';
+ restoreBtn.className = 'trash-restore-btn';
+ restoreBtn.textContent = '♻️ Restore';
+ restoreBtn.style.position = 'relative';
+ restoreBtn.style.zIndex = '4';
+ restoreBtn.addEventListener('click', async (ev) => {
+ ev.stopPropagation();
+ try {
+ restoreBtn.disabled = true;
+ restoreBtn.textContent = '⏳ Restore...';
+ setStatus(`Restore: ${item.owner}/${item.repo} -> ${item.originalPath}`);
+ showProgress(25, 'Wiederherstellung läuft...');
+ const restoreRes = await window.electronAPI.restoreGiteaTrashItem({
+ owner: item.owner,
+ repo: item.repo,
+ trashPath: item.trashPath,
+ restorePath: item.originalPath,
+ ref: item.ref
+ });
+ hideProgress();
+ if (restoreRes?.ok) {
+ const warning = restoreRes.warning ? ` (${restoreRes.warning})` : '';
+ showSuccess(`Wiederhergestellt: ${item.owner}/${item.repo}/${item.originalPath}${warning}`);
+ await loadGlobalTrashView();
+ } else {
+ showError('Restore fehlgeschlagen: ' + (restoreRes?.error || 'Unbekannter Fehler'));
+ }
+ } catch (e) {
+ hideProgress();
+ showError('Restore fehlgeschlagen: ' + (e?.message || String(e)));
+ } finally {
+ restoreBtn.disabled = false;
+ restoreBtn.textContent = '♻️ Restore';
+ }
+ });
+
+ actions.appendChild(restoreBtn);
+ card.appendChild(iconEl);
+ card.appendChild(nameEl);
+ card.appendChild(metaEl);
+ card.appendChild(actions);
+ grid.appendChild(card);
+ }
+
+ setStatus(`Globaler Papierkorb: ${allItems.length} Einträge`);
+}
+
+async function loadTrashView(owner, repo) {
+ if (!owner || !repo) {
+ showError('Papierkorb nur innerhalb eines Repositories verfügbar.');
+ return;
+ }
+
+ lastRepoPathBeforeTrash = currentState.path || '';
+ currentState.view = 'gitea-trash';
+ currentState.owner = owner;
+ currentState.repo = repo;
+ currentState.path = '_trash';
+ updateNavigationUI();
+
+ const btnCommits = $('btnCommits');
+ const btnReleases = $('btnReleases');
+ const btnPurgeTrash = $('btnPurgeTrash');
+ if (btnCommits) btnCommits.classList.add('hidden');
+ if (btnReleases) btnReleases.classList.add('hidden');
+ if (btnPurgeTrash) {
+ btnPurgeTrash.classList.remove('hidden');
+ btnPurgeTrash.textContent = '🧹 Purge 7d';
+ btnPurgeTrash.title = 'Papierkorb älter als 7 Tage leeren';
+ btnPurgeTrash.onclick = async () => {
+ const ok = await showActionConfirmModal({
+ title: 'Papierkorb leeren',
+ message: 'Alle Papierkorb-Einträge älter als 7 Tage löschen?',
+ confirmText: 'Purge 7d',
+ danger: true
+ });
+ if (!ok) return;
+ showProgress(30, 'Papierkorb wird bereinigt...');
+ const purgeRes = await window.electronAPI.purgeGiteaTrash({
+ owner,
+ repo,
+ ref,
+ olderThanDays: 7
+ });
+ hideProgress();
+ if (purgeRes?.ok) {
+ showSuccess(`Papierkorb bereinigt: ${purgeRes.purgedFiles || 0} Dateien entfernt`);
+ await loadTrashView(owner, repo);
+ } else {
+ showError('Purge fehlgeschlagen: ' + (purgeRes?.error || 'Unbekannter Fehler'));
+ }
+ };
+ }
+
+ const grid = $('explorerGrid');
+ if (!grid) return;
+ grid.innerHTML = '';
+ setStatus('Lade Papierkorb...');
+
+ const ref = getDefaultBranch(owner, repo);
+ const res = await window.electronAPI.listGiteaTrash({ owner, repo, ref });
+ if (!res?.ok) {
+ showError('Papierkorb konnte nicht geladen werden: ' + (res?.error || 'Unbekannter Fehler'));
+ return;
+ }
+
+ if ((res?.purgeSummary?.purgedFiles || 0) > 0) {
+ setStatus(`Auto-Purge: ${res.purgeSummary.purgedFiles} alte Papierkorb-Dateien entfernt`);
+ }
+
+ const items = Array.isArray(res.items) ? res.items : [];
+ if (items.length === 0) {
+ const emptyEl = document.createElement('div');
+ emptyEl.style.cssText = 'grid-column: 1/-1; text-align: center; padding: 40px; color: var(--text-muted);';
+ emptyEl.textContent = '🧺 Papierkorb ist leer';
+ grid.appendChild(emptyEl);
+ setStatus('Papierkorb ist leer');
+ return;
+ }
+
+ items.forEach((item) => {
+ const card = document.createElement('div');
+ card.className = 'item-card';
+
+ const iconEl = makeFileIconEl(item.name || item.originalPath || 'item', false);
+ const nameEl = document.createElement('div');
+ nameEl.className = 'item-name';
+ nameEl.textContent = item.originalPath || item.name || '(unbekannt)';
+
+ const metaEl = document.createElement('div');
+ metaEl.className = 'trash-card-meta';
+ metaEl.textContent = `Trash: ${item.trashPath} | Zeit: ${item.restoreTimestamp}`;
+
+ const actions = document.createElement('div');
+ actions.className = 'trash-card-actions';
+ actions.style.position = 'relative';
+ actions.style.zIndex = '3';
+
+ const restoreBtn = document.createElement('button');
+ restoreBtn.type = 'button';
+ restoreBtn.className = 'trash-restore-btn';
+ restoreBtn.textContent = '♻️ Restore';
+ restoreBtn.style.position = 'relative';
+ restoreBtn.style.zIndex = '4';
+ restoreBtn.addEventListener('click', async (ev) => {
+ ev.stopPropagation();
+ try {
+ restoreBtn.disabled = true;
+ restoreBtn.textContent = '⏳ Restore...';
+ setStatus(`Restore: ${item.originalPath}`);
+ showProgress(25, 'Wiederherstellung läuft...');
+ if (window.electronAPI?.debugToMain) {
+ window.electronAPI.debugToMain('info', 'trash-restore-clicked', {
+ owner,
+ repo,
+ trashPath: item.trashPath,
+ restorePath: item.originalPath,
+ ref
+ });
+ }
+
+ const restoreRes = await window.electronAPI.restoreGiteaTrashItem({
+ owner,
+ repo,
+ trashPath: item.trashPath,
+ restorePath: item.originalPath,
+ ref
+ });
+
+ hideProgress();
+ if (restoreRes?.ok) {
+ const warning = restoreRes.warning ? ` (${restoreRes.warning})` : '';
+ showSuccess(`Wiederhergestellt: ${item.originalPath}${warning}`);
+ await loadTrashView(owner, repo);
+ } else {
+ showError('Restore fehlgeschlagen: ' + (restoreRes?.error || 'Unbekannter Fehler'));
+ }
+ } catch (e) {
+ hideProgress();
+ showError('Restore fehlgeschlagen: ' + (e?.message || String(e)));
+ console.error('trash restore click handler error', e);
+ } finally {
+ restoreBtn.disabled = false;
+ restoreBtn.textContent = '♻️ Restore';
+ }
+ });
+
+ actions.appendChild(restoreBtn);
+ card.appendChild(iconEl);
+ card.appendChild(nameEl);
+ card.appendChild(metaEl);
+ card.appendChild(actions);
+ grid.appendChild(card);
+ });
+
+ setStatus(`Papierkorb: ${items.length} Einträge`);
+}
+
/* -------------------------
GITEA CORE LOGIK (GRID)
------------------------- */
@@ -2947,8 +3307,14 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
// Verstecke Commits & Releases-Buttons in Repo-Liste
const btnCommits = $('btnCommits');
const btnReleases = $('btnReleases');
+ const btnTrash = $('btnTrash');
+ const btnGlobalTrash = $('btnGlobalTrash');
+ const btnPurgeTrash = $('btnPurgeTrash');
if (btnCommits) btnCommits.classList.add('hidden');
if (btnReleases) btnReleases.classList.add('hidden');
+ if (btnTrash) btnTrash.classList.add('hidden');
+ if (btnGlobalTrash) btnGlobalTrash.classList.add('hidden');
+ if (btnPurgeTrash) btnPurgeTrash.classList.add('hidden');
// WICHTIG: Grid-Layout zurücksetzen
const grid = $('explorerGrid');
@@ -2975,6 +3341,11 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
}
currentGiteaRepos = Array.isArray(res.repos) ? res.repos : [];
+ const btnGlobalTrash = $('btnGlobalTrash');
+ if (btnGlobalTrash && currentState.platform === 'gitea') {
+ btnGlobalTrash.classList.remove('hidden');
+ btnGlobalTrash.onclick = () => loadGlobalTrashView();
+ }
if (!preloadedData) {
try {
const cachedName = getCachedUsername('gitea');
@@ -3328,6 +3699,7 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
console.log('[UPLOAD_DEBUG][renderer] repoCard:dropResult', res);
if (!res.ok) {
showError('Fehler: ' + (res.error || 'Upload fehlgeschlagen'));
+ await showUploadFailureModal('Upload fehlgeschlagen', res, 'Upload fehlgeschlagen');
setStatus('Upload fehlgeschlagen');
} else {
setStatus('Upload abgeschlossen');
@@ -3376,6 +3748,9 @@ async function loadRepoContents(owner, repo, path) {
// Zeige Commits & Releases-Buttons wenn wir in einem Repo sind
const btnCommits = $('btnCommits');
const btnReleases = $('btnReleases');
+ const btnTrash = $('btnTrash');
+ const btnGlobalTrash = $('btnGlobalTrash');
+ const btnPurgeTrash = $('btnPurgeTrash');
if (btnCommits) {
btnCommits.classList.remove('hidden');
@@ -3387,6 +3762,16 @@ async function loadRepoContents(owner, repo, path) {
btnReleases.onclick = () => loadRepoReleases(owner, repo);
}
+ if (btnTrash) {
+ btnTrash.classList.remove('hidden');
+ btnTrash.onclick = () => loadTrashView(owner, repo);
+ }
+ if (btnGlobalTrash) {
+ btnGlobalTrash.classList.remove('hidden');
+ btnGlobalTrash.onclick = () => loadGlobalTrashView();
+ }
+ if (btnPurgeTrash) btnPurgeTrash.classList.add('hidden');
+
// WICHTIG: Grid-Layout zurücksetzen
const grid = $('explorerGrid');
if (grid) {
@@ -3404,7 +3789,8 @@ async function loadRepoContents(owner, repo, path) {
repo,
path,
ref,
- platform
+ platform,
+ noCache: true
});
if (!res.ok) {
@@ -3523,6 +3909,7 @@ loadRepos = function(...args) {
});
if (!res.ok) {
showError('Upload error: ' + (res.error || 'Unbekannter Fehler'));
+ await showUploadFailureModal('Upload fehlgeschlagen', res, 'Upload fehlgeschlagen');
}
} catch (error) {
console.error('Upload error:', error);
@@ -3583,6 +3970,13 @@ async function selectLocalFolder() {
await refreshLocalTree(folder);
await loadBranches(folder);
+
+ const btnTrash = $('btnTrash');
+ if (btnTrash) btnTrash.classList.add('hidden');
+ const btnGlobalTrash = $('btnGlobalTrash');
+ if (btnGlobalTrash) btnGlobalTrash.classList.add('hidden');
+ const btnPurgeTrash = $('btnPurgeTrash');
+ if (btnPurgeTrash) btnPurgeTrash.classList.add('hidden');
} catch (error) {
console.error('Error selecting folder:', error);
showError('Error selecting folder');
@@ -4182,21 +4576,26 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
const sel = await window.electronAPI.selectFolder();
if (sel) {
showProgress(0, 'Upload...');
- await window.electronAPI.uploadAndPush({
+ const res = await window.electronAPI.uploadLocalFolderToGitea({
localFolder: sel,
owner,
repo: repoName,
destPath: '',
- cloneUrl,
- branch: getDefaultBranch(owner, repoName)
+ branch: getDefaultBranch(owner, repoName),
+ messagePrefix: 'Upload folder via GUI'
});
+ if (!res?.ok) {
+ showError('Upload fehlgeschlagen: ' + (res?.error || 'Unbekannter Fehler'));
+ await showUploadFailureModal('Upload fehlgeschlagen', res, 'Upload fehlgeschlagen');
+ return;
+ }
hideProgress();
setStatus('Upload complete');
}
} catch (error) {
console.error('Upload error:', error);
hideProgress();
- showError('Upload failed');
+ showError('Upload failed: ' + (error?.message || 'Unbekannter Fehler'));
}
});
@@ -4288,7 +4687,7 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
let done = 0;
for (const p of selectedItems) {
const isGithub = currentState.platform === 'github';
- await window.electronAPI.deleteFile({ path: p, owner, repo, isGitea: !isGithub, isGithub, ref: getDefaultBranch(owner, repo) });
+ await window.electronAPI.deleteFile({ path: p, owner, repo, isGitea: !isGithub, isGithub, ref: getDefaultBranch(owner, repo), softDelete: true });
done++;
showProgress(Math.round((done / selectedItems.size) * 100), `Lösche ${done}/${selectedItems.size}`);
}
@@ -4366,10 +4765,10 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
if (!ok) return;
showProgress(0, `Lösche ${item.name}...`);
const isGithub = currentState.platform === 'github';
- const res = await window.electronAPI.deleteFile({ path: item.path, owner, repo, isGitea: !isGithub, isGithub, ref: getDefaultBranch(owner, repo) });
+ const res = await window.electronAPI.deleteFile({ path: item.path, owner, repo, isGitea: !isGithub, isGithub, ref: getDefaultBranch(owner, repo), softDelete: true });
hideProgress();
if (res && res.ok) {
- setStatus(`${item.name} gelöscht`);
+ setStatus(`${item.name} gelöscht` + (res.softDeleteWarning ? ` (${res.softDeleteWarning})` : ''));
loadRepoContents(owner, repo, currentState.path);
} else {
showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
@@ -4556,7 +4955,7 @@ function showNewGiteaItemModal(owner, repo, parentPath, type) {
type
});
if (res?.ok) {
- setStatus(`"${name}" erstellt`);
+ setStatus(res?.exists ? `"${name}" existiert bereits` : `"${name}" erstellt`);
loadRepoContents(owner, repo, currentState.path);
} else {
await showInfoModal('Erstellen fehlgeschlagen', 'Erstellen fehlgeschlagen:\n' + (res?.error || ''), true);
@@ -4936,12 +5335,23 @@ function setupBackgroundContextMenu() {
addItem('📋', `⬆️ Von Lokal einfügen: "${clipboard.item.name}"`, async () => {
showProgress(0, `Lade "${clipboard.item.name}" hoch...`);
try {
- await window.electronAPI.uploadAndPush({
- localFolder: clipboard.item.path,
- owner, repo,
- destPath: currentPath,
- branch: getDefaultBranch(owner, repo)
- });
+ const res = clipboard.item.isDirectory
+ ? await window.electronAPI.uploadLocalFolderToGitea({
+ localFolder: clipboard.item.path,
+ owner,
+ repo,
+ destPath: currentPath,
+ branch: getDefaultBranch(owner, repo),
+ messagePrefix: 'Upload folder via GUI'
+ })
+ : await window.electronAPI.uploadAndPush({
+ localFolder: clipboard.item.path,
+ owner,
+ repo,
+ destPath: currentPath,
+ branch: getDefaultBranch(owner, repo)
+ });
+ if (!res?.ok) throw new Error(res?.error || 'Upload fehlgeschlagen');
showSuccess(`"${clipboard.item.name}" nach Gitea kopiert`);
loadRepoContents(owner, repo, currentState.path);
} catch(e) { showError('Cross-Paste fehlgeschlagen'); }
@@ -5239,10 +5649,34 @@ window.addEventListener('DOMContentLoaded', async () => {
parts.pop();
loadRepoContents(currentState.owner, currentState.repo, parts.join('/'));
}
+ } else if (currentState.view === 'gitea-trash') {
+ loadRepoContents(currentState.owner, currentState.repo, lastRepoPathBeforeTrash || '');
+ } else if (currentState.view === 'gitea-trash-global') {
+ loadGiteaRepos();
}
};
}
+ if ($('btnTrash')) {
+ $('btnTrash').onclick = () => {
+ if (!currentState.owner || !currentState.repo) {
+ showError('Bitte zuerst ein Repository öffnen.');
+ return;
+ }
+ loadTrashView(currentState.owner, currentState.repo);
+ };
+ }
+
+ if ($('btnGlobalTrash')) {
+ $('btnGlobalTrash').onclick = () => {
+ if (currentState.platform !== 'gitea') {
+ showError('Globaler Papierkorb ist nur für Gitea verfügbar.');
+ return;
+ }
+ loadGlobalTrashView();
+ };
+ }
+
// Modal controls
if ($('btnWinMinimize')) $('btnWinMinimize').onclick = () => window.electronAPI.windowMinimize();
if ($('btnWinMaximize')) $('btnWinMaximize').onclick = () => window.electronAPI.windowMaximize();
@@ -5912,8 +6346,21 @@ window.addEventListener('DOMContentLoaded', async () => {
if (lastSelectedItem.type === 'gitea') {
const { item, owner, repo } = lastSelectedItem;
showDeleteConfirm(`"${item.name}" wirklich löschen?`, async () => {
- const res = await window.electronAPI.deleteFile({ path: item.path, owner, repo, isGitea: true });
- if (res?.ok) { showSuccess(`"${item.name}" gelöscht`); loadRepoContents(owner, repo, currentState.path); lastSelectedItem = null; }
+ const isGithub = currentState.platform === 'github';
+ const res = await window.electronAPI.deleteFile({
+ path: item.path,
+ owner,
+ repo,
+ isGitea: !isGithub,
+ isGithub,
+ ref: getDefaultBranch(owner, repo),
+ softDelete: true
+ });
+ if (res?.ok) {
+ showSuccess(`"${item.name}" gelöscht` + (res.softDeleteWarning ? ` (${res.softDeleteWarning})` : ''));
+ loadRepoContents(owner, repo, currentState.path);
+ lastSelectedItem = null;
+ }
else showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
});
} else if (lastSelectedItem.type === 'local') {
diff --git a/renderer/style.css b/renderer/style.css
index 12a0771..2f8156d 100644
--- a/renderer/style.css
+++ b/renderer/style.css
@@ -427,6 +427,11 @@ body {
.tool-group--quick-actions {
gap: 8px;
+ flex-wrap: nowrap;
+}
+
+.tool-group--utility {
+ flex-wrap: nowrap;
}
.tool-group--repo {
@@ -646,7 +651,8 @@ body {
}
#btnOpenRepoActions,
-#btnPush {
+#btnPush,
+#btnGlobalTrash {
min-width: 92px;
}
@@ -2014,6 +2020,61 @@ input[type="checkbox"] {
color: #d9f7ff;
}
+/* ===========================
+ TRASH VIEW
+ =========================== */
+.item-card .trash-card-meta {
+ font-size: 11px;
+ opacity: 0.84;
+ margin-top: 8px;
+ line-height: 1.45;
+ word-break: break-all;
+ color: rgba(226, 232, 240, 0.9);
+}
+
+.item-card .trash-card-actions {
+ display: flex;
+ gap: 10px;
+ margin-top: 12px;
+ align-items: center;
+}
+
+.item-card button.trash-restore-btn {
+ appearance: none;
+ -webkit-appearance: none;
+ border: 1px solid rgba(56, 189, 248, 0.45);
+ background: linear-gradient(135deg, rgba(10, 88, 122, 0.58), rgba(30, 64, 175, 0.55));
+ color: #e0f2fe;
+ border-radius: 9px;
+ padding: 7px 12px;
+ min-height: 32px;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1;
+ letter-spacing: 0.2px;
+ cursor: pointer;
+ box-shadow: 0 8px 18px rgba(3, 19, 42, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.12);
+ transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease, background 0.12s ease;
+}
+
+.item-card button.trash-restore-btn:hover {
+ border-color: rgba(56, 189, 248, 0.78);
+ background: linear-gradient(135deg, rgba(14, 116, 144, 0.64), rgba(37, 99, 235, 0.62));
+ box-shadow: 0 10px 22px rgba(7, 42, 90, 0.3), 0 0 0 2px rgba(56, 189, 248, 0.16);
+ transform: translateY(-1px);
+}
+
+.item-card button.trash-restore-btn:active {
+ transform: translateY(0);
+}
+
+.item-card button.trash-restore-btn:disabled {
+ opacity: 0.62;
+ cursor: wait;
+ transform: none;
+ box-shadow: none;
+}
+
.repo-owner-tab.active {
border-color: rgba(88, 213, 255, 0.65);
background: linear-gradient(135deg, rgba(88, 213, 255, 0.2), rgba(92, 135, 255, 0.18));