diff --git a/renderer/index.html b/renderer/index.html
index 5e1c8dc..6ef3ca5 100644
--- a/renderer/index.html
+++ b/renderer/index.html
@@ -47,7 +47,7 @@
-
+
@@ -82,8 +82,8 @@
-
-
+
+
@@ -226,6 +226,18 @@
+
+
+
+
+
+
+
@@ -284,6 +296,25 @@
+
+
+
+
+
+
+
+
+
+ Export enthält Tokens nur verschlüsselt, nie als Klartext. Der Import ist für andere PCs geeignet.
+
+
+
+
diff --git a/renderer/renderer.js b/renderer/renderer.js
index 90e583e..a06e04d 100644
--- a/renderer/renderer.js
+++ b/renderer/renderer.js
@@ -9,7 +9,7 @@ let currentLocalProjects = [];
FAVORITEN & ZULETZT GEÖFFNET — State & Helpers
================================================ */
-let favorites = []; // [{ owner, repo, cloneUrl, addedAt }]
+let favorites = []; // [{ owner, repo, cloneUrl, platform, displayName, addedAt }]
let recentRepos = []; // [{ owner, repo, cloneUrl, openedAt }]
// Feature-Flags (aus Settings, persistent via Credentials)
@@ -17,6 +17,8 @@ let featureFavorites = true;
let featureRecent = true;
let compactMode = false;
let featureAutostart = false;
+const MAX_PINBOARD_REPOS = 5;
+let pinnedRepos = []; // [{ owner, repo, cloneUrl, platform, pinnedAt }]
let repoNameValidationTimer = null;
let batchCloneValidationTimer = null;
let activityHeatmapCollapsed = true;
@@ -30,7 +32,31 @@ let repoTopicsByFullName = {}; // owner/repo -> string[]
let repoKnownTopics = []; // all known topics across repos
let repoKnownTopicsLoadedAt = 0;
let currentGiteaUsername = '';
-let activeRepoOwnerFilter = 'mine'; // mine | all | owner:
+let activeRepoOwnerFilter = 'mine'; // mine | all | archived | owner:
+
+function reportUnhandledRendererIssue(kind, errorLike, extra = {}) {
+ try {
+ const errorObj = errorLike instanceof Error
+ ? { name: errorLike.name, message: errorLike.message, stack: errorLike.stack }
+ : { message: String(errorLike || 'Unknown renderer error') };
+ window.electronAPI?.debugToMain?.('error', `renderer:${kind}`, { ...extra, error: errorObj });
+ } catch (_) {
+ // never break UI because of diagnostics reporting
+ }
+}
+
+window.addEventListener('error', (event) => {
+ reportUnhandledRendererIssue('window-error', event?.error || event?.message || 'window error', {
+ fileName: event?.filename || '',
+ line: event?.lineno || 0,
+ column: event?.colno || 0
+ });
+});
+
+window.addEventListener('unhandledrejection', (event) => {
+ const reason = event?.reason;
+ reportUnhandledRendererIssue('unhandledrejection', reason || 'Unhandled rejection');
+});
function normalizePlatform(value) {
return value === 'github' ? 'github' : 'gitea';
@@ -50,13 +76,178 @@ function platformEntries(items, platform = currentPlatformKey()) {
return (Array.isArray(items) ? items : []).filter(e => normalizePlatform(e?.platform || 'gitea') === p);
}
+function getPinnedRepoKey(owner, repo, platform = currentPlatformKey()) {
+ return `${normalizePlatform(platform)}::${String(owner || '').toLowerCase()}::${String(repo || '').toLowerCase()}`;
+}
+
+function normalizePinnedRepoEntry(entry, fallbackPlatform = currentPlatformKey()) {
+ if (!entry || typeof entry !== 'object') return null;
+ const owner = String(entry.owner || '').trim();
+ const repo = String(entry.repo || '').trim();
+ if (!owner || !repo) return null;
+ return {
+ owner,
+ repo,
+ cloneUrl: String(entry.cloneUrl || '').trim(),
+ platform: normalizePlatform(entry.platform || fallbackPlatform),
+ pinnedAt: entry.pinnedAt || new Date().toISOString()
+ };
+}
+
+function getPinnedReposForPlatform(platform = currentPlatformKey()) {
+ const p = normalizePlatform(platform);
+ return pinnedRepos.filter(entry => normalizePlatform(entry.platform || 'gitea') === p);
+}
+
+function isRepoPinned(owner, repo, platform = currentPlatformKey()) {
+ const key = getPinnedRepoKey(owner, repo, platform);
+ return pinnedRepos.some(entry => getPinnedRepoKey(entry.owner, entry.repo, entry.platform) === key);
+}
+
+async function persistPinnedRepos() {
+ const creds = await window.electronAPI.loadCredentials() || {};
+ await window.electronAPI.saveCredentials({
+ ...creds,
+ pinnedRepos
+ });
+}
+
+async function toggleRepoPinned(owner, repo, cloneUrl = '', platform = currentPlatformKey()) {
+ const key = getPinnedRepoKey(owner, repo, platform);
+ const exists = pinnedRepos.some(entry => getPinnedRepoKey(entry.owner, entry.repo, entry.platform) === key);
+
+ if (exists) {
+ pinnedRepos = pinnedRepos.filter(entry => getPinnedRepoKey(entry.owner, entry.repo, entry.platform) !== key);
+ await persistPinnedRepos();
+ showInfo(`Pin entfernt: ${owner}/${repo}`);
+ loadGiteaRepos();
+ return;
+ }
+
+ const platformPins = getPinnedReposForPlatform(platform);
+ if (platformPins.length >= MAX_PINBOARD_REPOS) {
+ showWarning(`Maximal ${MAX_PINBOARD_REPOS} Pins pro Plattform erlaubt.`);
+ return;
+ }
+
+ pinnedRepos.unshift({
+ owner,
+ repo,
+ cloneUrl: String(cloneUrl || '').trim(),
+ platform: normalizePlatform(platform),
+ pinnedAt: new Date().toISOString()
+ });
+ await persistPinnedRepos();
+ showSuccess(`Angepinnt: ${owner}/${repo}`);
+ loadGiteaRepos();
+}
+
+function renderRepoPinboard(grid, allRepos) {
+ if (!grid) return;
+
+ const platform = currentPlatformKey();
+ const pins = getPinnedReposForPlatform(platform)
+ .slice()
+ .sort((a, b) => new Date(b.pinnedAt || 0).getTime() - new Date(a.pinnedAt || 0).getTime())
+ .slice(0, MAX_PINBOARD_REPOS);
+
+ if (pins.length === 0) return;
+
+ const wrap = document.createElement('div');
+ wrap.className = 'repo-pinboard-wrap';
+ wrap.style.cssText = 'grid-column: 1/-1; margin-bottom: 12px;';
+
+ const title = document.createElement('div');
+ title.className = 'repo-pinboard-title';
+ title.textContent = `📌 Pinboard (${pins.length}/${MAX_PINBOARD_REPOS})`;
+ wrap.appendChild(title);
+
+ const row = document.createElement('div');
+ row.className = 'repo-pinboard-row';
+
+ pins.forEach(pin => {
+ const fullName = `${pin.owner}/${pin.repo}`;
+ const match = Array.isArray(allRepos)
+ ? allRepos.find(r => {
+ const owner = r?.owner?.login || r?.owner?.username || '';
+ const name = r?.name || '';
+ return owner === pin.owner && name === pin.repo;
+ })
+ : null;
+
+ const chip = document.createElement('button');
+ chip.type = 'button';
+ chip.className = 'repo-pinboard-chip' + (match ? '' : ' is-missing');
+ chip.title = match ? `${fullName} öffnen` : `${fullName} (nicht in Liste gefunden)`;
+
+ const icon = document.createElement('span');
+ icon.className = 'repo-pinboard-chip-icon';
+ icon.textContent = '📌';
+ chip.appendChild(icon);
+
+ const label = document.createElement('span');
+ label.className = 'repo-pinboard-chip-label';
+ label.textContent = pin.repo;
+ chip.appendChild(label);
+
+ const ownerEl = document.createElement('span');
+ ownerEl.className = 'repo-pinboard-chip-owner';
+ ownerEl.textContent = pin.owner;
+ chip.appendChild(ownerEl);
+
+ if (!match) {
+ const missingEl = document.createElement('span');
+ missingEl.className = 'repo-pinboard-chip-missing';
+ missingEl.textContent = 'nicht gefunden';
+ chip.appendChild(missingEl);
+ }
+
+ chip.addEventListener('click', async () => {
+ if (!match) {
+ showWarning(`${fullName} ist aktuell nicht in der Liste verfügbar.`);
+ return;
+ }
+ addToRecent(pin.owner, pin.repo, pin.cloneUrl || match?.clone_url || match?.clone_url_ssh || '', platform);
+ loadRepoContents(pin.owner, pin.repo, '');
+ });
+
+ chip.addEventListener('contextmenu', (ev) => {
+ ev.preventDefault();
+ showRepoContextMenu(
+ ev,
+ pin.owner,
+ pin.repo,
+ pin.cloneUrl || match?.clone_url || match?.clone_url_ssh || '',
+ chip,
+ !!match?.private,
+ match || null
+ );
+ });
+
+ row.appendChild(chip);
+ });
+
+ wrap.appendChild(row);
+ grid.appendChild(wrap);
+}
+
async function loadFavoritesAndRecent() {
try {
const [favRes, recRes] = await Promise.all([
window.electronAPI.loadFavorites(),
window.electronAPI.loadRecent()
]);
- if (favRes && favRes.ok) favorites = (favRes.favorites || []).map(e => withPlatform(e, 'gitea'));
+ if (favRes && favRes.ok) {
+ favorites = (favRes.favorites || []).map((e) => {
+ const base = withPlatform(e, 'gitea');
+ if (!base || typeof base !== 'object') return base;
+ const normalizedAlias = String(base.displayName ?? base.alias ?? '').trim();
+ return {
+ ...base,
+ displayName: normalizedAlias || null
+ };
+ });
+ }
if (recRes && recRes.ok) recentRepos = (recRes.recent || []).map(e => withPlatform(e, 'gitea'));
} catch(e) {
console.error('loadFavoritesAndRecent:', e);
@@ -73,12 +264,37 @@ async function toggleFavorite(owner, repo, cloneUrl, platform = currentPlatformK
if (isFavorite(owner, repo, p)) {
favorites = favorites.filter(f => !(f.owner === owner && f.repo === repo && normalizePlatform(f.platform || 'gitea') === p));
} else {
- favorites.unshift({ owner, repo, cloneUrl, platform: p, addedAt: new Date().toISOString() });
+ favorites.unshift({ owner, repo, cloneUrl, platform: p, displayName: null, addedAt: new Date().toISOString() });
}
await window.electronAPI.saveFavorites(favorites);
refreshFavHistoryUi();
}
+async function setFavoriteAlias(owner, repo, platform = currentPlatformKey()) {
+ const targetPlatform = normalizePlatform(platform);
+ const fav = favorites.find(f => f.owner === owner && f.repo === repo && normalizePlatform(f.platform || 'gitea') === targetPlatform)
+ || favorites.find(f => f.owner === owner && f.repo === repo);
+ if (!fav) {
+ showError('Favorit nicht gefunden');
+ return;
+ }
+
+ const currentAlias = fav.displayName || '';
+ showInputModal({
+ title: '📝 Alias setzen',
+ label: `Alias für "${owner}/${repo}" (leer = entfernen):`,
+ defaultValue: currentAlias,
+ confirmText: '✓ Speichern',
+ onConfirm: async (newAlias) => {
+ fav.displayName = newAlias ? newAlias : null;
+ if (Object.prototype.hasOwnProperty.call(fav, 'alias')) delete fav.alias;
+ await window.electronAPI.saveFavorites(favorites);
+ refreshFavHistoryUi();
+ showSuccess(newAlias ? `Alias gespeichert: ${newAlias}` : 'Alias entfernt');
+ }
+ });
+}
+
async function addToRecent(owner, repo, cloneUrl, platform = currentPlatformKey()) {
if (!featureRecent) return;
const p = normalizePlatform(platform);
@@ -352,7 +568,11 @@ function renderFavHistorySidebar(allRepos) {
const name = document.createElement('span');
name.className = 'fav-history-item-name';
- name.textContent = entry.repo || '-';
+ const sidebarDisplayName = (activeType === 'favorites' && (entry.displayName || entry.alias))
+ ? (entry.displayName || entry.alias)
+ : (entry.repo || '-');
+ name.textContent = sidebarDisplayName;
+ name.title = `${entry.owner || '-'} / ${entry.repo || '-'}`;
const meta = document.createElement('span');
meta.className = 'fav-history-item-meta';
@@ -413,7 +633,9 @@ function makeChip(entry, type, allRepos) {
const label = document.createElement('span');
label.className = 'fav-chip-label';
- label.textContent = `${entry.owner}/${entry.repo}`;
+ const displayName = entry.displayName || entry.alias || entry.repo;
+ label.textContent = (entry.displayName || entry.alias) ? `${displayName}` : `${entry.owner}/${entry.repo}`;
+ label.title = `${entry.owner}/${entry.repo}`;
chip.appendChild(icon);
chip.appendChild(label);
@@ -517,6 +739,15 @@ function showChipContextMenu(ev, entry, type) {
const fullName = `${entry.owner || ''}/${entry.repo || ''}`;
const isPrivate = !!repoPrivacyByFullName[fullName];
+ const matchedRepo = Array.isArray(currentGiteaRepos)
+ ? currentGiteaRepos.find(r => {
+ const o = r?.owner?.login || r?.owner?.username || '';
+ const n = r?.name || '';
+ return o === entry.owner && n === entry.repo;
+ })
+ : null;
+ const isArchived = !!matchedRepo?.archived;
+ const canArchive = !!matchedRepo && isRepoWritable(matchedRepo, currentGiteaUsername);
addItem(
isPrivate ? '🌍' : '🔒',
isPrivate ? 'Öffentlich machen' : 'Privat machen',
@@ -525,6 +756,25 @@ function showChipContextMenu(ev, entry, type) {
}
);
+ if (canArchive) {
+ addItem(
+ isArchived ? '📦' : '🗄️',
+ isArchived ? 'Archivierung rückgängig machen' : 'Archivieren',
+ async () => {
+ await toggleRepoArchived(entry.owner, entry.repo, isArchived);
+ }
+ );
+ }
+
+ const pinned = isRepoPinned(entry.owner, entry.repo, entry.platform || currentPlatformKey());
+ addItem(
+ pinned ? '📍' : '📌',
+ pinned ? 'Vom Pinboard lösen' : 'An Pinboard anheften',
+ async () => {
+ await toggleRepoPinned(entry.owner, entry.repo, entry.cloneUrl || '', entry.platform || currentPlatformKey());
+ }
+ );
+
const chipTopics = repoTopicsByFullName[fullName] || [];
addItem('🏷️', 'Tags bearbeiten', async () => {
await editRepoTopics(entry.owner, entry.repo, chipTopics);
@@ -536,6 +786,10 @@ function showChipContextMenu(ev, entry, type) {
menu.appendChild(sep);
if (type === 'favorite') {
+ addItem('📝', 'Alias setzen', async () => {
+ menu.remove();
+ await setFavoriteAlias(entry.owner, entry.repo, entry.platform || currentPlatformKey());
+ });
addItem('⭐', 'Aus Favoriten entfernen', async () => {
await toggleFavorite(entry.owner, entry.repo, entry.cloneUrl, entry.platform || currentPlatformKey());
loadGiteaRepos();
@@ -714,6 +968,33 @@ async function toggleRepoVisibility(owner, repoName, currentPrivate) {
}
}
+async function toggleRepoArchived(owner, repoName, currentlyArchived) {
+ try {
+ const targetArchived = !currentlyArchived;
+ const actionText = targetArchived ? 'archiviert' : 'entarchiviert';
+ showProgress(35, `Repository wird ${targetArchived ? 'archiviert' : 'entarchiviert'}...`);
+
+ const result = await window.electronAPI.updateRepoArchived({
+ owner,
+ repo: repoName,
+ archived: targetArchived,
+ platform: currentState.platform
+ });
+ hideProgress();
+
+ if (result?.ok) {
+ showSuccess(`Repository wurde ${actionText}.`);
+ loadGiteaRepos();
+ } else {
+ showError('Archivierung fehlgeschlagen: ' + (result?.error || 'Unbekannter Fehler'));
+ }
+ } catch (error) {
+ hideProgress();
+ console.error('Archive toggle error:', error);
+ showError('Archivierung fehlgeschlagen');
+ }
+}
+
function showTagsEditorModal(owner, repoName, seed, knownTopics) {
return new Promise((resolve) => {
const modal = document.createElement('div');
@@ -956,14 +1237,12 @@ function getDefaultBranch(owner, repo) {
// Navigations-Status für die Explorer-Ansicht
let currentState = {
- view: 'none', // 'local', 'gitea-list', 'gitea-repo', 'gitea-trash'
+ view: 'none', // 'local', 'gitea-list', 'gitea-repo'
owner: null,
repo: null,
path: '',
platform: 'gitea' // 'gitea' | 'github'
};
-let lastRepoPathBeforeTrash = '';
-
let repoLoadRequestId = 0;
const USER_CACHE_MS = 5 * 60 * 1000;
let currentUserCache = {
@@ -1667,16 +1946,20 @@ function renderSettingsHealth() {
function syncSettingsPanelHeights() {
const credentialsPanel = document.querySelector('#settingsModal .settings-panel--credentials');
const healthPanel = document.querySelector('#settingsModal .settings-panel--health');
- if (!credentialsPanel || !healthPanel) return;
- healthPanel.style.minHeight = '';
+ if (healthPanel) {
+ healthPanel.style.minHeight = '';
+ healthPanel.style.height = '';
+ }
// In der einspaltigen Ansicht sollen die Karten natuerlich fliessen.
if (window.matchMedia('(max-width: 1120px)').matches) return;
- const targetHeight = Math.ceil(credentialsPanel.getBoundingClientRect().height);
- if (targetHeight > 0) {
- healthPanel.style.minHeight = `${targetHeight}px`;
+ if (credentialsPanel && healthPanel) {
+ const topRightTargetHeight = Math.ceil(credentialsPanel.getBoundingClientRect().height);
+ if (topRightTargetHeight > 0) {
+ healthPanel.style.minHeight = `${topRightTargetHeight}px`;
+ }
}
}
@@ -1901,6 +2184,7 @@ function fuzzyScoreToken(token, text) {
if (idx >= 0) {
let score = 120 - Math.min(idx, 60);
if (t.startsWith(q)) score += 20;
+ else if (idx > 0) score += 10; // Teilwort-Treffer, aber nicht am Anfang
return score;
}
@@ -1915,6 +2199,9 @@ function fuzzyScoreToken(token, text) {
ti = found + 1;
}
+ // Für Teilwort-Suche: Wenn q ein echter Substring von t ist, aber nicht am Anfang, gibt es einen kleinen Score
+ if (t.includes(q)) return 25;
+
return Math.max(8, 80 - gaps * 2);
}
@@ -1947,16 +2234,18 @@ function getRepoCardSearchScore(card, query) {
function applyRepoFuzzyFilter(grid, searchInput, searchMetaEl) {
if (!grid) return;
- const cards = Array.from(grid.querySelectorAll('.item-card'));
+ const cards = Array.from(grid.querySelectorAll('.item-card[data-repo-card="main"]'));
const query = (searchInput?.value || '').trim();
const isOwnerMatch = (card) => {
const owner = String(card.dataset.searchOwner || '').toLowerCase();
const isShared = String(card.dataset.shared || '') === 'true';
+ const isArchived = String(card.dataset.archived || '') === 'true';
if (activeRepoOwnerFilter === 'all') return true;
+ if (activeRepoOwnerFilter === 'archived') return isArchived;
if (activeRepoOwnerFilter === 'mine') {
if (!currentGiteaUsername) return true;
- return owner === String(currentGiteaUsername).toLowerCase();
+ return owner === String(currentGiteaUsername).toLowerCase() && !isArchived;
}
if (activeRepoOwnerFilter === 'shared') {
return isShared;
@@ -2959,8 +3248,6 @@ function updateNavigationUI() {
// Back Button zeigen, wenn wir in einem Repo oder tief in Ordnern sind
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 {
@@ -2968,331 +3255,14 @@ function updateNavigationUI() {
}
}
+// loadGlobalTrashView: Papierkorb entfernt
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`);
+ showError('Papierkorb wurde deaktiviert.');
}
-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`);
+// loadTrashView: Papierkorb entfernt
+async function loadTrashView(_owner, _repo) {
+ showError('Papierkorb wurde deaktiviert.');
}
/* -------------------------
@@ -3307,14 +3277,8 @@ 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');
@@ -3341,11 +3305,6 @@ 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');
@@ -3385,9 +3344,16 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
ownerCounts.set(owner, (ownerCounts.get(owner) || 0) + 1);
}
const myOwner = String(currentGiteaUsername || '').trim();
+ const archivedCount = currentGiteaRepos.filter(r => !!r?.archived).length;
+ const mineActiveCount = currentGiteaRepos.filter(r => {
+ const owner = String(r?.owner?.login || r?.owner?.username || '').trim().toLowerCase();
+ if (!myOwner) return !r?.archived;
+ return owner === myOwner.toLowerCase() && !r?.archived;
+ }).length;
const tabDefs = [
- { key: 'mine', label: 'Meine', count: myOwner ? (ownerCounts.get(myOwner) || 0) : currentGiteaRepos.length },
+ { key: 'mine', label: 'Meine', count: mineActiveCount },
+ { key: 'archived', label: 'Archiviert', count: archivedCount },
{ key: 'all', label: 'Alle', count: currentGiteaRepos.length }
];
Array.from(ownerCounts.keys())
@@ -3401,6 +3367,9 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
if (activeRepoOwnerFilter === 'shared') {
activeRepoOwnerFilter = 'all';
}
+ if (activeRepoOwnerFilter === 'archived' && archivedCount === 0) {
+ activeRepoOwnerFilter = 'all';
+ }
const makeTab = (tab) => {
const btn = document.createElement('button');
@@ -3420,6 +3389,7 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
return btn;
};
tabDefs.forEach(tab => ownerTabsWrap.appendChild(makeTab(tab)));
+ renderRepoPinboard(grid, currentGiteaRepos);
grid.appendChild(ownerTabsWrap);
const searchContainer = document.createElement('div');
@@ -3531,6 +3501,7 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
const card = document.createElement('div');
card.className = 'item-card';
card.style.position = 'relative';
+ card.dataset.repoCard = 'main';
card.dataset.cloneUrl = cloneUrl;
card.dataset.owner = owner;
card.dataset.repo = repoName;
@@ -3545,9 +3516,18 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
const sharedByOthers = !!ownerLower && !!myOwnerLower && ownerLower !== myOwnerLower;
card.dataset.shared = sharedByOthers ? 'true' : 'false';
card.dataset.searchSharedOwner = sharedByOthers ? `geteilt von ${owner}` : '';
+ card.dataset.archived = repo?.archived ? 'true' : 'false';
const writable = isRepoWritable(repo, currentGiteaUsername);
const readOnly = !writable;
card.dataset.readOnly = readOnly ? 'true' : 'false';
+ const metaFooter = document.createElement('div');
+ metaFooter.className = 'repo-card-footer';
+ const archivedBadge = repo?.archived
+ ? Object.assign(document.createElement('div'), {
+ className: 'repo-archived-badge',
+ textContent: 'Archiviert'
+ })
+ : null;
// Stern-Button (nur wenn Favoriten-Feature aktiv)
if (featureFavorites) {
@@ -3606,7 +3586,15 @@ async function loadGiteaRepos(preloadedData = null, requestId = null) {
sizeEl.textContent = kb >= 1024
? `${(kb / 1024).toFixed(1)} MB`
: `${kb} KB`;
- card.appendChild(sizeEl);
+ metaFooter.appendChild(sizeEl);
+ }
+
+ if (archivedBadge) {
+ metaFooter.appendChild(archivedBadge);
+ }
+
+ if (metaFooter.childElementCount > 0) {
+ card.appendChild(metaFooter);
}
// --- Nativer Drag Start (Download) ---
@@ -3748,10 +3736,6 @@ 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');
btnCommits.onclick = () => loadCommitHistory(owner, repo, getDefaultBranch(owner, repo));
@@ -3762,16 +3746,6 @@ 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) {
@@ -3971,12 +3945,6 @@ 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');
@@ -4437,7 +4405,18 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
else showError('Clone-URL konnte nicht kopiert werden');
}));
+ const pinned = isRepoPinned(owner, repoName, currentPlatformKey());
+ menu.appendChild(createMenuItem(
+ pinned ? '📍' : '📌',
+ pinned ? 'Vom Pinboard lösen' : 'An Pinboard anheften',
+ async () => {
+ menu.remove();
+ await toggleRepoPinned(owner, repoName, cloneUrl || '', currentPlatformKey());
+ }
+ ));
+
const isOwnRepo = String(owner || '').toLowerCase() === String(currentGiteaUsername || '').toLowerCase();
+ const isArchived = !!repoMeta?.archived;
if (isOwnRepo) {
const isGiteaView = currentState.platform === 'gitea';
const syncLabel = isGiteaView ? 'Gitea -> GitHub synchronisieren' : 'GitHub -> Gitea synchronisieren';
@@ -4548,6 +4527,15 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element, isPrivate =
await toggleRepoVisibility(owner, repoName, isPrivate);
}
));
+
+ menu.appendChild(createMenuItem(
+ isArchived ? '📦' : '🗄️',
+ isArchived ? 'Archivierung rückgängig machen' : 'Archivieren',
+ async () => {
+ menu.remove();
+ await toggleRepoArchived(owner, repoName, isArchived);
+ }
+ ));
}
addSep();
@@ -4663,7 +4651,7 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
el.className = 'context-item';
el.textContent = `${icon} ${text}`;
if (color) el.style.color = color;
- el.onclick = () => { menu.remove(); onClick(); };
+ el.onclick = () => { menu.remove(); Promise.resolve().then(() => onClick()).catch(e => console.error('context-menu error', e)); };
menu.appendChild(el);
};
@@ -4687,7 +4675,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), softDelete: true });
+ await window.electronAPI.deleteFile({ path: p, owner, repo, isGitea: !isGithub, isGithub, ref: getDefaultBranch(owner, repo) });
done++;
showProgress(Math.round((done / selectedItems.size) * 100), `Lösche ${done}/${selectedItems.size}`);
}
@@ -4765,10 +4753,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), softDelete: true });
+ const res = await window.electronAPI.deleteFile({ path: item.path, owner, repo, isGitea: !isGithub, isGithub, ref: getDefaultBranch(owner, repo) });
hideProgress();
if (res && res.ok) {
- setStatus(`${item.name} gelöscht` + (res.softDeleteWarning ? ` (${res.softDeleteWarning})` : ''));
+ setStatus(`${item.name} gelöscht`);
loadRepoContents(owner, repo, currentState.path);
} else {
showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
@@ -4811,7 +4799,7 @@ function showLocalItemContextMenu(ev, node) {
el.className = 'context-item';
el.textContent = `${icon} ${text}`;
if (color) el.style.color = color;
- el.onclick = () => { menu.remove(); onClick(); };
+ el.onclick = () => { menu.remove(); Promise.resolve().then(() => onClick()).catch(e => console.error('context-menu error', e)); };
menu.appendChild(el);
};
@@ -5308,7 +5296,7 @@ function setupBackgroundContextMenu() {
const el = document.createElement('div');
el.className = 'context-item';
el.textContent = `${icon} ${text}`;
- el.onclick = () => { menu.remove(); onClick(); };
+ el.onclick = () => { menu.remove(); Promise.resolve().then(() => onClick()).catch(e => console.error('context-menu error', e)); };
menu.appendChild(el);
};
@@ -5545,6 +5533,11 @@ window.addEventListener('DOMContentLoaded', async () => {
if (typeof creds.featureFavorites === 'boolean') featureFavorites = creds.featureFavorites;
if (typeof creds.featureRecent === 'boolean') featureRecent = creds.featureRecent;
if (typeof creds.compactMode === 'boolean') compactMode = creds.compactMode;
+ if (Array.isArray(creds.pinnedRepos)) {
+ pinnedRepos = creds.pinnedRepos
+ .map(entry => normalizePinnedRepoEntry(entry, currentPlatformKey()))
+ .filter(Boolean);
+ }
if (typeof creds.featureColoredIcons === 'boolean') featureColoredIcons = creds.featureColoredIcons;
document.body.classList.toggle('compact-mode', compactMode);
@@ -5649,34 +5642,10 @@ 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();
@@ -6204,6 +6173,7 @@ window.addEventListener('DOMContentLoaded', async () => {
featureRecent,
compactMode,
featureColoredIcons,
+ pinnedRepos,
favCollapsedFavorites: favSectionCollapsed.favorites,
favCollapsedRecent: favSectionCollapsed.recent
};
@@ -6353,11 +6323,10 @@ window.addEventListener('DOMContentLoaded', async () => {
repo,
isGitea: !isGithub,
isGithub,
- ref: getDefaultBranch(owner, repo),
- softDelete: true
+ ref: getDefaultBranch(owner, repo)
});
if (res?.ok) {
- showSuccess(`"${item.name}" gelöscht` + (res.softDeleteWarning ? ` (${res.softDeleteWarning})` : ''));
+ showSuccess(`"${item.name}" gelöscht`);
loadRepoContents(owner, repo, currentState.path);
lastSelectedItem = null;
}
@@ -7930,6 +7899,69 @@ async function initUpdater() {
}
};
}
+
+ if ($('btnExportSettingsBundle')) {
+ $('btnExportSettingsBundle').onclick = async () => {
+ try {
+ const res = await window.electronAPI.exportSettingsBundle();
+ if (res?.ok) {
+ showSuccess('Backup exportiert');
+ setStatus('Backup exportiert');
+ } else if (!res?.canceled) {
+ showError('Backup-Export fehlgeschlagen: ' + (res?.error || 'Unbekannter Fehler'));
+ setStatus('Backup-Export fehlgeschlagen');
+ }
+ } catch (error) {
+ showError('Backup-Export fehlgeschlagen: ' + (error?.message || error));
+ setStatus('Backup-Export fehlgeschlagen');
+ }
+ };
+ }
+
+ if ($('btnImportSettingsBundle')) {
+ $('btnImportSettingsBundle').onclick = async () => {
+ const ok = await showActionConfirmModal({
+ title: 'Backup importieren',
+ message: 'Vorhandene Einstellungen koennen ueberschrieben werden. Fortfahren?',
+ confirmText: 'Import starten',
+ danger: false
+ });
+ if (!ok) return;
+
+ try {
+ const res = await window.electronAPI.importSettingsBundle();
+ if (res?.ok) {
+ showSuccess('Backup importiert. Ansicht wird aktualisiert...');
+ setStatus('Backup importiert');
+ setTimeout(() => window.location.reload(), 650);
+ } else if (!res?.canceled) {
+ showError('Backup-Import fehlgeschlagen: ' + (res?.error || 'Unbekannter Fehler'));
+ setStatus('Backup-Import fehlgeschlagen');
+ }
+ } catch (error) {
+ showError('Backup-Import fehlgeschlagen: ' + (error?.message || error));
+ setStatus('Backup-Import fehlgeschlagen');
+ }
+ };
+ }
+
+ if ($('btnCreateDiagnostics')) {
+ $('btnCreateDiagnostics').onclick = async () => {
+ try {
+ const res = await window.electronAPI.createDiagnosticsPackage();
+ if (res?.ok) {
+ showSuccess('Diagnosepaket erstellt');
+ setStatus('Diagnosepaket erstellt');
+ } else if (!res?.canceled) {
+ showError('Diagnosepaket fehlgeschlagen: ' + (res?.error || 'Unbekannter Fehler'));
+ setStatus('Diagnosepaket fehlgeschlagen');
+ }
+ } catch (error) {
+ showError('Diagnosepaket fehlgeschlagen: ' + (error?.message || error));
+ setStatus('Diagnosepaket fehlgeschlagen');
+ }
+ };
+ }
}
// Event-Listener für das Update-Modal
@@ -7968,6 +8000,18 @@ if (window.electronAPI.onUpdateNotAvailable) {
});
}
+if (window.electronAPI.onUpdateError) {
+ window.electronAPI.onUpdateError((payload) => {
+ const message = String(payload?.message || 'Update-Fehler');
+ const silent = !!payload?.details?.silent;
+
+ if (!silent) {
+ showError(message);
+ }
+ setStatus(message);
+ });
+}
+
// AM ENDE DER DATEI: Initialisierung beim Start
document.addEventListener('DOMContentLoaded', () => {
// 1. Basis-Setup (Settings-Feld füllen etc.)
diff --git a/renderer/style.css b/renderer/style.css
index 2f8156d..e087bb2 100644
--- a/renderer/style.css
+++ b/renderer/style.css
@@ -651,8 +651,7 @@ body {
}
#btnOpenRepoActions,
-#btnPush,
-#btnGlobalTrash {
+#btnPush {
min-width: 92px;
}
@@ -1759,6 +1758,18 @@ input[type="checkbox"] {
margin: 0;
}
+.settings-version-field .settings-readonly-input {
+ height: 40px;
+ padding-top: 0;
+ padding-bottom: 0;
+ line-height: 40px;
+ text-align: center;
+ font-size: 16px;
+ font-weight: 400;
+ letter-spacing: 0.01em;
+ color: var(--text-primary);
+}
+
.settings-readonly-input {
background: rgba(255,255,255,0.05);
color: var(--text-secondary);
@@ -1767,15 +1778,17 @@ input[type="checkbox"] {
.settings-update-btn {
min-width: 210px;
- height: 42px;
- padding: 0 16px;
+ height: 40px;
+ padding: 0 12px;
border-radius: var(--radius-md);
border: 1px solid rgba(88, 213, 255, 0.3);
background: linear-gradient(135deg, rgba(88, 213, 255, 0.94), rgba(92, 135, 255, 0.9));
color: #08111f;
- font-size: 13px;
- font-weight: 800;
- letter-spacing: 0.01em;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0;
+ white-space: nowrap;
+ line-height: 1;
cursor: pointer;
transition: transform var(--transition-normal), box-shadow var(--transition-normal), filter var(--transition-normal);
box-shadow: 0 16px 28px rgba(24, 136, 255, 0.26);
@@ -1791,6 +1804,121 @@ input[type="checkbox"] {
transform: translateY(0);
}
+.settings-tools-grid {
+ margin-top: 0;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 8px;
+}
+
+.settings-backup-card {
+ margin-top: 6px;
+}
+
+.settings-diagnostics-card {
+ margin-top: 4px;
+}
+
+.settings-backup-title {
+ margin: 0 0 8px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-secondary);
+}
+
+.settings-action-btn {
+ height: 40px;
+ border-radius: var(--radius-md);
+ padding: 0 14px;
+ font-size: 13px;
+ font-weight: 700;
+ letter-spacing: 0.15px;
+ cursor: pointer;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(0, 212, 255, 0.08) 100%);
+ color: var(--text-primary);
+ transition: transform var(--transition-normal), box-shadow var(--transition-normal), border-color var(--transition-normal), background var(--transition-normal);
+}
+
+.settings-action-btn:hover {
+ transform: translateY(-1px);
+ border-color: rgba(88, 213, 255, 0.5);
+ background: linear-gradient(135deg, rgba(88, 213, 255, 0.17) 0%, rgba(92, 135, 255, 0.15) 100%);
+ box-shadow: 0 10px 20px rgba(8, 20, 40, 0.24);
+}
+
+.settings-action-btn:active {
+ transform: translateY(0);
+}
+
+.settings-tools-grid .settings-action-btn {
+ width: 100%;
+ justify-content: center;
+}
+
+.settings-panel--app {
+ padding-bottom: 10px;
+}
+
+.settings-panel--app .settings-panel-header {
+ margin-bottom: 8px;
+}
+
+.settings-panel--app .settings-version-card {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ align-items: end;
+}
+
+.settings-panel--app .settings-update-btn {
+ width: 100%;
+ min-width: 0;
+ height: 40px;
+}
+
+.settings-panel--app .settings-inline-hint {
+ margin-top: 6px;
+}
+
+.settings-panel--backup .settings-inline-hint {
+ margin-top: 6px;
+}
+
+.settings-panel--backup .settings-tools-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.settings-panel--diagnostics {
+ padding: 11px;
+ height: 140px;
+}
+
+.settings-panel--backup {
+ padding: 11px;
+ height: 165px;
+}
+
+.settings-panel--diagnostics .settings-panel-header {
+ margin-bottom: 7px;
+}
+
+.settings-panel--backup .settings-panel-header {
+ margin-bottom: 7px;
+}
+
+.settings-panel--diagnostics .settings-panel-header p {
+ font-size: 10px;
+ line-height: 1.25;
+}
+
+.settings-panel--backup .settings-panel-header p {
+ font-size: 10px;
+ line-height: 1.25;
+}
+
.settings-modal-actions {
margin-top: 12px;
}
@@ -1987,6 +2115,84 @@ input[type="checkbox"] {
gap: 8px;
}
+.repo-pinboard-wrap {
+ border: 1px solid rgba(88, 213, 255, 0.22);
+ background: linear-gradient(135deg, rgba(88, 213, 255, 0.07), rgba(92, 135, 255, 0.08));
+ border-radius: 14px;
+ padding: 10px 12px;
+}
+
+.repo-pinboard-title {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #d8f7ff;
+ margin-bottom: 8px;
+}
+
+.repo-pinboard-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.repo-pinboard-chip {
+ appearance: none;
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ background: rgba(7, 17, 31, 0.56);
+ color: var(--text-primary);
+ border-radius: 999px;
+ min-height: 30px;
+ padding: 0 10px;
+ font-size: 12px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ transition: border-color 140ms ease, background 140ms ease, transform 140ms ease;
+}
+
+.repo-pinboard-chip:hover {
+ border-color: rgba(88, 213, 255, 0.6);
+ background: rgba(88, 213, 255, 0.12);
+ transform: translateY(-1px);
+}
+
+.repo-pinboard-chip-icon {
+ font-size: 11px;
+ opacity: 0.9;
+}
+
+.repo-pinboard-chip-label {
+ font-weight: 700;
+ max-width: 150px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.repo-pinboard-chip-owner {
+ color: var(--text-muted);
+ font-size: 11px;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.repo-pinboard-chip-missing {
+ color: #fca5a5;
+ font-size: 10px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.repo-pinboard-chip.is-missing {
+ border-color: rgba(248, 113, 113, 0.4);
+ background: rgba(127, 29, 29, 0.2);
+}
+
.repo-owner-tab {
appearance: none;
border: 1px solid rgba(255, 255, 255, 0.18);
@@ -2021,59 +2227,8 @@ input[type="checkbox"] {
}
/* ===========================
- TRASH VIEW
+ (trash view removed)
=========================== */
-.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);
@@ -3567,6 +3722,14 @@ progress::-moz-progress-bar {
gap: 12px;
}
+ .settings-panel--diagnostics {
+ height: auto;
+ }
+
+ .settings-panel--backup {
+ height: auto;
+ }
+
.settings-credentials-grid {
grid-template-columns: minmax(0, 1fr);
}
@@ -3591,11 +3754,13 @@ progress::-moz-progress-bar {
.settings-credentials-grid,
.settings-connection-tools,
.settings-version-card,
+ .settings-tools-grid,
.modal-buttons {
grid-template-columns: minmax(0, 1fr);
}
.settings-update-btn,
+ .settings-action-btn,
#btnTestGiteaConnection,
#btnTestGithubConnection {
width: 100%;
@@ -3681,10 +3846,38 @@ body.compact-mode .file-type-badge {
.repo-size-badge {
font-size: 10px;
color: var(--text-muted);
- margin-top: 4px;
+ margin-top: 0;
opacity: 0.7;
}
+.repo-card-footer {
+ margin-top: auto;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
+}
+
+.repo-archived-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ margin-top: 0;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ color: #fcd34d;
+ background: rgba(120, 53, 15, 0.32);
+ border: 1px solid rgba(245, 158, 11, 0.55);
+ border-radius: 999px;
+ padding: 2px 8px;
+ pointer-events: none;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
/* ===========================
FAVORITEN DRAG-REORDER
=========================== */