|
|
|
|
@@ -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') {
|
|
|
|
|
|