diff --git a/renderer/renderer.js b/renderer/renderer.js
index 070bd32..7dfb6c6 100644
--- a/renderer/renderer.js
+++ b/renderer/renderer.js
@@ -4,6 +4,293 @@ const $ = id => document.getElementById(id);
let selectedFolder = null;
let giteaCache = {};
+/* ================================================
+ FAVORITEN & ZULETZT GEÖFFNET — State & Helpers
+ ================================================ */
+
+let favorites = []; // [{ owner, repo, cloneUrl, addedAt }]
+let recentRepos = []; // [{ owner, repo, cloneUrl, openedAt }]
+
+// Feature-Flags (aus Settings, persistent via Credentials)
+let featureFavorites = true;
+let featureRecent = true;
+let compactMode = false;
+
+async function loadFavoritesAndRecent() {
+ try {
+ const [favRes, recRes] = await Promise.all([
+ window.electronAPI.loadFavorites(),
+ window.electronAPI.loadRecent()
+ ]);
+ if (favRes && favRes.ok) favorites = favRes.favorites || [];
+ if (recRes && recRes.ok) recentRepos = recRes.recent || [];
+ } catch(e) {
+ console.error('loadFavoritesAndRecent:', e);
+ }
+}
+
+function isFavorite(owner, repo) {
+ return favorites.some(f => f.owner === owner && f.repo === repo);
+}
+
+async function toggleFavorite(owner, repo, cloneUrl) {
+ if (isFavorite(owner, repo)) {
+ favorites = favorites.filter(f => !(f.owner === owner && f.repo === repo));
+ } else {
+ favorites.unshift({ owner, repo, cloneUrl, addedAt: new Date().toISOString() });
+ }
+ await window.electronAPI.saveFavorites(favorites);
+}
+
+async function addToRecent(owner, repo, cloneUrl) {
+ if (!featureRecent) return;
+ recentRepos = recentRepos.filter(r => !(r.owner === owner && r.repo === repo));
+ recentRepos.unshift({ owner, repo, cloneUrl, openedAt: new Date().toISOString() });
+ recentRepos = recentRepos.slice(0, 20);
+ await window.electronAPI.saveRecent(recentRepos);
+}
+
+function formatRelDate(iso) {
+ if (!iso) return '';
+ const diff = Date.now() - new Date(iso).getTime();
+ const m = Math.floor(diff / 60000);
+ const h = Math.floor(diff / 3600000);
+ const d = Math.floor(diff / 86400000);
+ if (m < 1) return 'Gerade eben';
+ if (m < 60) return `vor ${m} Min.`;
+ if (h < 24) return `vor ${h} Std.`;
+ if (d < 7) return `vor ${d} Tag${d > 1 ? 'en' : ''}`;
+ return new Date(iso).toLocaleDateString('de-DE');
+}
+
+/* Rendert Favoriten + Zuletzt-geöffnet-Bereich in ein beliebiges Container-Element */
+// Collapse-Zustand (wird in Credentials persistiert)
+const favSectionCollapsed = { favorites: false, recent: false };
+
+function makeFavSectionBlock(type, allRepos) {
+ const isFav = type === 'favorites';
+ const icon = isFav ? '⭐' : '🕐';
+ const label = isFav ? 'Favoriten' : 'Zuletzt geöffnet';
+
+ const sec = document.createElement('div');
+ sec.style.cssText = `margin-bottom: ${isFav ? '20' : '24'}px;`;
+
+ // ── Header (klickbar) ──────────────────────────────
+ const hdr = document.createElement('div');
+ hdr.className = 'fav-section-header fav-section-header--toggle';
+
+ const iconEl = document.createElement('span');
+ iconEl.className = 'fav-section-icon';
+ if (isFav) iconEl.style.color = '#f59e0b';
+ iconEl.textContent = icon;
+
+ const labelEl = document.createElement('span');
+ labelEl.textContent = label;
+
+ const arrow = document.createElement('span');
+ arrow.className = 'fav-collapse-arrow';
+ arrow.textContent = favSectionCollapsed[type] ? '▶' : '▼';
+
+ hdr.appendChild(iconEl);
+ hdr.appendChild(labelEl);
+ hdr.appendChild(arrow);
+ sec.appendChild(hdr);
+
+ // ── Inhalt ────────────────────────────────────────
+ const row = document.createElement('div');
+ row.className = 'fav-chips-row';
+ row.style.cssText = favSectionCollapsed[type]
+ ? 'display:none;'
+ : 'display:flex;flex-wrap:wrap;gap:8px;';
+
+ const items = isFav ? favorites : recentRepos.slice(0, 8);
+ items.forEach(entry => row.appendChild(makeChip(entry, isFav ? 'favorite' : 'recent', allRepos)));
+ sec.appendChild(row);
+
+ // ── Toggle-Logik ──────────────────────────────────
+ hdr.addEventListener('click', () => {
+ favSectionCollapsed[type] = !favSectionCollapsed[type];
+ const collapsed = favSectionCollapsed[type];
+ row.style.display = collapsed ? 'none' : 'flex';
+ arrow.textContent = collapsed ? '▶' : '▼';
+ // Zustand persistent speichern
+ window.electronAPI.loadCredentials().then(c => {
+ if (c && c.ok) {
+ window.electronAPI.saveCredentials({
+ ...c,
+ favCollapsedFavorites: favSectionCollapsed.favorites,
+ favCollapsedRecent: favSectionCollapsed.recent
+ });
+ }
+ }).catch(() => {});
+ });
+
+ return sec;
+}
+
+function renderFavRecentSection(container, allRepos) {
+ container.innerHTML = '';
+ const showFav = featureFavorites && favorites.length > 0;
+ const showRec = featureRecent && recentRepos.length > 0;
+ if (!showFav && !showRec) return;
+
+ if (showFav) container.appendChild(makeFavSectionBlock('favorites', allRepos));
+ if (showRec) container.appendChild(makeFavSectionBlock('recent', allRepos));
+
+ // Trennlinie
+ const div = document.createElement('div');
+ div.className = 'fav-divider';
+ container.appendChild(div);
+}
+
+function makeChip(entry, type, allRepos) {
+ const isFav = type === 'favorite';
+ const chip = document.createElement('div');
+ chip.className = `fav-chip${isFav ? ' fav-chip--star' : ''}`;
+ chip.title = `${entry.owner}/${entry.repo}`;
+
+ const icon = document.createElement('span');
+ icon.className = 'fav-chip-icon';
+ icon.textContent = isFav ? '⭐' : '🕐';
+
+ const label = document.createElement('span');
+ label.className = 'fav-chip-label';
+ label.textContent = `${entry.owner}/${entry.repo}`;
+
+ chip.appendChild(icon);
+ chip.appendChild(label);
+
+ if (!isFav && entry.openedAt) {
+ const time = document.createElement('span');
+ time.className = 'fav-chip-time';
+ time.textContent = formatRelDate(entry.openedAt);
+ chip.appendChild(time);
+ }
+
+ chip.onclick = () => {
+ addToRecent(entry.owner, entry.repo, entry.cloneUrl);
+ loadRepoContents(entry.owner, entry.repo, '');
+ };
+
+ chip.oncontextmenu = (ev) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ showChipContextMenu(ev, entry, type);
+ };
+
+ // Drag-Reorder (nur für Favoriten)
+ if (isFav) {
+ chip.draggable = true;
+ chip.dataset.owner = entry.owner;
+ chip.dataset.repo = entry.repo;
+
+ chip.addEventListener('dragstart', (ev) => {
+ ev.dataTransfer.effectAllowed = 'move';
+ ev.dataTransfer.setData('text/fav-owner', entry.owner);
+ ev.dataTransfer.setData('text/fav-repo', entry.repo);
+ chip.classList.add('fav-chip--dragging');
+ });
+ chip.addEventListener('dragend', () => chip.classList.remove('fav-chip--dragging'));
+
+ chip.addEventListener('dragover', (ev) => {
+ ev.preventDefault();
+ ev.dataTransfer.dropEffect = 'move';
+ chip.classList.add('fav-chip--drop-target');
+ });
+ chip.addEventListener('dragleave', () => chip.classList.remove('fav-chip--drop-target'));
+
+ chip.addEventListener('drop', async (ev) => {
+ ev.preventDefault();
+ chip.classList.remove('fav-chip--drop-target');
+ const srcOwner = ev.dataTransfer.getData('text/fav-owner');
+ const srcRepo = ev.dataTransfer.getData('text/fav-repo');
+ if (srcOwner === entry.owner && srcRepo === entry.repo) return;
+
+ const fromIdx = favorites.findIndex(f => f.owner === srcOwner && f.repo === srcRepo);
+ const toIdx = favorites.findIndex(f => f.owner === entry.owner && f.repo === entry.repo);
+ if (fromIdx < 0 || toIdx < 0) return;
+
+ // Reorder
+ const [moved] = favorites.splice(fromIdx, 1);
+ favorites.splice(toIdx, 0, moved);
+ await window.electronAPI.saveFavorites(favorites);
+
+ // Sektion neu rendern
+ const sec = $('favRecentSection');
+ if (sec) {
+ // allRepos fehlt hier, daher einfach neu laden
+ const favBlock = sec.querySelector('.fav-chips-row');
+ if (favBlock) {
+ const allChips = Array.from(favBlock.querySelectorAll('.fav-chip'));
+ const movedChip = allChips.find(c => c.dataset.owner === srcOwner && c.dataset.repo === srcRepo);
+ const targetChip = allChips.find(c => c.dataset.owner === entry.owner && c.dataset.repo === entry.repo);
+ if (movedChip && targetChip) {
+ favBlock.insertBefore(movedChip, fromIdx > toIdx ? targetChip : targetChip.nextSibling);
+ }
+ }
+ }
+ });
+ }
+
+ return chip;
+}
+
+function showChipContextMenu(ev, entry, type) {
+ const old = $('ctxMenu');
+ if (old) old.remove();
+ const menu = document.createElement('div');
+ menu.id = 'ctxMenu';
+ menu.className = 'context-menu';
+ menu.style.left = Math.min(ev.clientX, window.innerWidth - 240) + 'px';
+ menu.style.top = Math.min(ev.clientY, window.innerHeight - 160) + 'px';
+
+ const addItem = (icon, text, cb, color) => {
+ const el = document.createElement('div');
+ el.className = 'context-item';
+ el.innerHTML = `${icon} ${text}`;
+ if (color) el.style.color = color;
+ el.onclick = () => { menu.remove(); cb(); };
+ menu.appendChild(el);
+ };
+
+ addItem('📂', 'Öffnen', () => {
+ addToRecent(entry.owner, entry.repo, entry.cloneUrl);
+ loadRepoContents(entry.owner, entry.repo, '');
+ });
+
+ // Separator
+ const sep = document.createElement('div');
+ sep.style.cssText = 'height:1px;background:rgba(255,255,255,0.1);margin:4px 0;';
+ menu.appendChild(sep);
+
+ if (type === 'favorite') {
+ addItem('⭐', 'Aus Favoriten entfernen', async () => {
+ await toggleFavorite(entry.owner, entry.repo, entry.cloneUrl);
+ loadGiteaRepos();
+ }, '#f59e0b');
+ } else {
+ addItem('⭐', 'Zu Favoriten hinzufügen', async () => {
+ await toggleFavorite(entry.owner, entry.repo, entry.cloneUrl);
+ loadGiteaRepos();
+ });
+ addItem('✕', 'Aus Verlauf entfernen', async () => {
+ recentRepos = recentRepos.filter(r => !(r.owner === entry.owner && r.repo === entry.repo));
+ await window.electronAPI.saveRecent(recentRepos);
+ loadGiteaRepos();
+ }, '#ef4444');
+ }
+
+ document.body.appendChild(menu);
+ setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
+}
+
+// Speichert den default_branch pro Repo (owner/repo -> 'main' oder 'master')
+let repoDefaultBranches = {};
+
+function getDefaultBranch(owner, repo) {
+ return repoDefaultBranches[`${owner}/${repo}`] || 'HEAD';
+}
+
// Navigations-Status für die Explorer-Ansicht
let currentState = {
view: 'none', // 'local', 'gitea-list', 'gitea-repo'
@@ -12,11 +299,171 @@ let currentState = {
path: ''
};
+// Clipboard für Cut & Paste
+let clipboard = {
+ item: null, // { path, name, type, owner, repo, isGitea, isLocal, nodePath }
+ action: null // 'cut'
+};
+
+// Mehrfachauswahl
+let selectedItems = new Set(); // Set von item-Pfaden
+let isMultiSelectMode = false;
+
+// Zuletzt angeklicktes Item (für F2/Entf)
+let lastSelectedItem = null; // { type:'gitea', item, owner, repo } | { type:'local', node }
+
+// Feature-Flag für farbige Icons
+let featureColoredIcons = true;
+
function setStatus(txt) {
const s = $('status');
if (s) s.innerText = txt || '';
}
+/* -------------------------
+ TOAST NOTIFICATIONS
+ ------------------------- */
+function showToast(message, type = 'info', duration = 4000) {
+ const container = (() => {
+ let c = $('toastContainer');
+ if (!c) {
+ c = document.createElement('div');
+ c.id = 'toastContainer';
+ c.style.cssText = `
+ position: fixed;
+ bottom: 24px;
+ right: 24px;
+ z-index: 99999;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ pointer-events: none;
+ `;
+ document.body.appendChild(c);
+ }
+ return c;
+ })();
+
+ const colors = {
+ error: { bg: 'rgba(239,68,68,0.15)', border: '#ef4444', icon: '✗' },
+ success: { bg: 'rgba(34,197,94,0.15)', border: '#22c55e', icon: '✓' },
+ info: { bg: 'rgba(0,212,255,0.12)', border: '#00d4ff', icon: 'ℹ' },
+ warning: { bg: 'rgba(245,158,11,0.15)', border: '#f59e0b', icon: '⚠' },
+ };
+ const c = colors[type] || colors.info;
+
+ const toast = document.createElement('div');
+ toast.style.cssText = `
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 12px 16px;
+ background: ${c.bg};
+ border: 1px solid ${c.border};
+ border-left: 3px solid ${c.border};
+ border-radius: 10px;
+ backdrop-filter: blur(12px);
+ box-shadow: 0 4px 24px rgba(0,0,0,0.4);
+ color: #fff;
+ font-size: 13px;
+ font-weight: 500;
+ max-width: 360px;
+ pointer-events: auto;
+ cursor: pointer;
+ opacity: 0;
+ transform: translateX(20px);
+ transition: opacity 220ms ease, transform 220ms ease;
+ line-height: 1.4;
+ `;
+
+ const iconEl = document.createElement('span');
+ iconEl.style.cssText = `color: ${c.border}; font-weight: 700; font-size: 15px; flex-shrink: 0; margin-top: 1px;`;
+ iconEl.textContent = c.icon;
+
+ const msgEl = document.createElement('span');
+ msgEl.textContent = message;
+
+ toast.appendChild(iconEl);
+ toast.appendChild(msgEl);
+ container.appendChild(toast);
+
+ // Einblenden
+ requestAnimationFrame(() => {
+ toast.style.opacity = '1';
+ toast.style.transform = 'translateX(0)';
+ });
+
+ const dismiss = () => {
+ toast.style.opacity = '0';
+ toast.style.transform = 'translateX(20px)';
+ setTimeout(() => toast.remove(), 220);
+ };
+
+ toast.addEventListener('click', dismiss);
+ setTimeout(dismiss, duration);
+}
+
+// Kurzformen
+function showError(msg) { setStatus(msg); showToast(msg, 'error'); }
+function showSuccess(msg) { setStatus(msg); showToast(msg, 'success', 3000); }
+function showWarning(msg) { setStatus(msg); showToast(msg, 'warning'); }
+
+// Löschen-Bestätigung als Toast (ersetzt confirm())
+function showDeleteConfirm(message, onConfirm) {
+ const container = (() => {
+ let c = $('toastContainer');
+ if (!c) { c = document.createElement('div'); c.id = 'toastContainer'; c.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:99999;display:flex;flex-direction:column;gap:10px;pointer-events:none;'; document.body.appendChild(c); }
+ return c;
+ })();
+
+ const toast = document.createElement('div');
+ toast.style.cssText = `
+ padding: 14px 16px;
+ background: rgba(239,68,68,0.15);
+ border: 1px solid #ef4444;
+ border-left: 3px solid #ef4444;
+ border-radius: 10px;
+ backdrop-filter: blur(12px);
+ box-shadow: 0 4px 24px rgba(0,0,0,0.4);
+ color: #fff;
+ font-size: 13px;
+ max-width: 360px;
+ pointer-events: auto;
+ opacity: 0;
+ transform: translateX(20px);
+ transition: opacity 220ms ease, transform 220ms ease;
+ `;
+
+ const msgEl = document.createElement('div');
+ msgEl.style.cssText = 'font-weight:600;margin-bottom:10px;';
+ msgEl.textContent = '🗑️ ' + message;
+
+ const btns = document.createElement('div');
+ btns.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';
+
+ const cancelBtn = document.createElement('button');
+ cancelBtn.textContent = 'Abbrechen';
+ cancelBtn.style.cssText = 'padding:5px 12px;border-radius:6px;border:1px solid rgba(255,255,255,0.2);background:transparent;color:#fff;cursor:pointer;font-size:12px;';
+
+ const confirmBtn = document.createElement('button');
+ confirmBtn.textContent = 'Löschen';
+ confirmBtn.style.cssText = 'padding:5px 12px;border-radius:6px;border:none;background:#ef4444;color:#fff;cursor:pointer;font-size:12px;font-weight:600;';
+
+ btns.appendChild(cancelBtn);
+ btns.appendChild(confirmBtn);
+ toast.appendChild(msgEl);
+ toast.appendChild(btns);
+ container.appendChild(toast);
+
+ requestAnimationFrame(() => { toast.style.opacity = '1'; toast.style.transform = 'translateX(0)'; });
+
+ const dismiss = () => { toast.style.opacity = '0'; toast.style.transform = 'translateX(20px)'; setTimeout(() => toast.remove(), 220); };
+
+ cancelBtn.addEventListener('click', dismiss);
+ confirmBtn.addEventListener('click', () => { dismiss(); onConfirm(); });
+ setTimeout(dismiss, 8000);
+}
+
/* -------------------------
PROGRESS UI
------------------------- */
@@ -286,7 +733,7 @@ async function updateEditor() {
owner: tab.owner,
repo: tab.repo,
path: filePath,
- ref: 'main'
+ ref: getDefaultBranch(tab.owner, tab.repo)
});
if (response.ok) {
@@ -566,7 +1013,7 @@ async function openGiteaFileInEditor(owner, repo, filePath, fileName) {
owner,
repo,
path: filePath,
- ref: 'main'
+ ref: getDefaultBranch(owner, repo)
});
if (response.ok) {
@@ -583,25 +1030,102 @@ async function openGiteaFileInEditor(owner, repo, filePath, fileName) {
console.log('✅ Gitea file opened');
} else {
alert(`Fehler: ${response.error}`);
- setStatus('Fehler beim Laden der Datei');
+ showError('Fehler beim Laden der Datei');
}
} catch (error) {
console.error('Error opening Gitea file:', error);
alert('Fehler beim Öffnen der Datei');
- setStatus('Fehler');
+ showError('Fehler');
}
}
+// Farbige Icons pro Dateityp (emoji + Farb-Overlay via CSS-Klassen)
+const FILE_ICONS = {
+ // Web
+ js: { icon: '📄', color: '#f7df1e', label: 'JS' },
+ jsx: { icon: '📄', color: '#61dafb', label: 'JSX' },
+ ts: { icon: '📄', color: '#3178c6', label: 'TS' },
+ tsx: { icon: '📄', color: '#3178c6', label: 'TSX' },
+ html: { icon: '📄', color: '#e34c26', label: 'HTML' },
+ css: { icon: '📄', color: '#264de4', label: 'CSS' },
+ scss: { icon: '📄', color: '#cd6799', label: 'SCSS' },
+ vue: { icon: '📄', color: '#42b883', label: 'VUE' },
+ svelte:{ icon: '📄', color: '#ff3e00', label: 'SVE' },
+ // Backend
+ py: { icon: '📄', color: '#3572a5', label: 'PY' },
+ java: { icon: '📄', color: '#b07219', label: 'JAVA' },
+ rb: { icon: '📄', color: '#701516', label: 'RB' },
+ php: { icon: '📄', color: '#4f5d95', label: 'PHP' },
+ go: { icon: '📄', color: '#00add8', label: 'GO' },
+ rs: { icon: '📄', color: '#dea584', label: 'RS' },
+ cs: { icon: '📄', color: '#178600', label: 'C#' },
+ cpp: { icon: '📄', color: '#f34b7d', label: 'C++' },
+ c: { icon: '📄', color: '#555555', label: 'C' },
+ // Config
+ json: { icon: '📄', color: '#fbc02d', label: 'JSON' },
+ yaml: { icon: '📄', color: '#cb171e', label: 'YAML' },
+ yml: { icon: '📄', color: '#cb171e', label: 'YAML' },
+ toml: { icon: '📄', color: '#9c4221', label: 'TOML' },
+ env: { icon: '📄', color: '#ecd53f', label: 'ENV' },
+ xml: { icon: '📄', color: '#f60', label: 'XML' },
+ // Docs
+ md: { icon: '📄', color: '#083fa1', label: 'MD' },
+ txt: { icon: '📄', color: '#888', label: 'TXT' },
+ pdf: { icon: '📄', color: '#e53935', label: 'PDF' },
+ // Shell
+ sh: { icon: '📄', color: '#89e051', label: 'SH' },
+ bat: { icon: '📄', color: '#c1f12e', label: 'BAT' },
+ // Images
+ png: { icon: '🖼️', color: '#4caf50', label: 'PNG' },
+ jpg: { icon: '🖼️', color: '#4caf50', label: 'JPG' },
+ jpeg: { icon: '🖼️', color: '#4caf50', label: 'JPG' },
+ gif: { icon: '🖼️', color: '#4caf50', label: 'GIF' },
+ svg: { icon: '🖼️', color: '#ff9800', label: 'SVG' },
+ webp: { icon: '🖼️', color: '#4caf50', label: 'WEBP' },
+ // Archives
+ zip: { icon: '📦', color: '#ff9800', label: 'ZIP' },
+ tar: { icon: '📦', color: '#ff9800', label: 'TAR' },
+ gz: { icon: '📦', color: '#ff9800', label: 'GZ' },
+};
+
function getFileIcon(fileName) {
const ext = fileName.split('.').pop()?.toLowerCase();
- const icons = {
- 'js': '🟨', 'jsx': '⚛️', 'ts': '🔵', 'tsx': '⚛️',
- 'py': '🐍', 'java': '☕', 'cpp': '⚙️', 'c': '⚙️',
- 'html': '🌐', 'css': '🎨', 'scss': '🎨', 'json': '📋',
- 'md': '📝', 'txt': '📄', 'xml': '📦', 'yaml': '⚙️',
- 'yml': '⚙️', 'env': '🔑', 'sh': '💻', 'bat': '💻'
- };
- return icons[ext] || '📄';
+ const info = FILE_ICONS[ext];
+ if (!info) return '📄';
+ return info.icon;
+}
+
+// Gibt ein DOM-Element für das Explorer-Icon zurück (mit farbigem Badge wenn aktiviert)
+function makeFileIconEl(fileName, isDir = false) {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'item-icon';
+
+ if (isDir) {
+ wrapper.textContent = '📁';
+ return wrapper;
+ }
+
+ const ext = fileName.split('.').pop()?.toLowerCase();
+ const info = featureColoredIcons ? FILE_ICONS[ext] : null;
+
+ wrapper.textContent = info ? info.icon : '📄';
+
+ if (info) {
+ const badge = document.createElement('span');
+ badge.className = 'file-type-badge';
+ badge.textContent = info.label;
+ badge.style.background = info.color;
+ // Helligkeit prüfen für Textfarbe
+ const hex = info.color.replace('#','');
+ const r = parseInt(hex.slice(0,2)||'88',16);
+ const g = parseInt(hex.slice(2,4)||'88',16);
+ const b = parseInt(hex.slice(4,6)||'88',16);
+ const lum = (0.299*r + 0.587*g + 0.114*b) / 255;
+ badge.style.color = lum > 0.55 ? '#111' : '#fff';
+ wrapper.appendChild(badge);
+ }
+
+ return wrapper;
}
/* -------------------------
@@ -696,7 +1220,7 @@ async function saveCurrentFile(isAutoSave = false) {
repo: tab.repo,
path: currentActiveTab.replace(`gitea://${tab.owner}/${tab.repo}/`, ''),
content: content,
- ref: 'main'
+ ref: getDefaultBranch(tab.owner, tab.repo)
});
} else {
// Lokale Datei
@@ -843,7 +1367,7 @@ async function loadGiteaRepos() {
try {
const res = await window.electronAPI.listGiteaRepos();
if (!res.ok) {
- setStatus('Failed to load repos: ' + (res.error || 'Unknown error'));
+ showError('Failed to load repos: ' + (res.error || 'Unknown error'));
return;
}
@@ -902,16 +1426,71 @@ async function loadGiteaRepos() {
});
// -----------------------------------
+ // ── Favoriten & Zuletzt geöffnet ──
+ // Sektion IMMER ins DOM einfügen (auch wenn leer),
+ // damit der Stern-Klick später $('favRecentSection') findet
+ if (featureFavorites || featureRecent) {
+ const favSection = document.createElement('div');
+ favSection.id = 'favRecentSection';
+ favSection.style.cssText = 'grid-column: 1/-1;';
+ grid.appendChild(favSection);
+ renderFavRecentSection(favSection, res.repos);
+ }
+
res.repos.forEach(repo => {
let owner = (repo.owner && (repo.owner.login || repo.owner.username)) || null;
let repoName = repo.name;
let cloneUrl = repo.clone_url || repo.clone_url_ssh;
+ // default_branch speichern (main ODER master je nach Repo)
+ const defaultBranch = repo.default_branch || 'HEAD';
+ repoDefaultBranches[`${owner}/${repoName}`] = defaultBranch;
+
const card = document.createElement('div');
card.className = 'item-card';
- card.innerHTML = `
📦
${repoName}
`;
+ card.style.position = 'relative';
card.dataset.cloneUrl = cloneUrl;
+ // Stern-Button (nur wenn Favoriten-Feature aktiv)
+ if (featureFavorites) {
+ const starBtn = document.createElement('button');
+ starBtn.className = 'fav-star-btn' + (isFavorite(owner, repoName) ? ' active' : '');
+ starBtn.title = isFavorite(owner, repoName) ? 'Aus Favoriten entfernen' : 'Zu Favoriten hinzufügen';
+ starBtn.textContent = isFavorite(owner, repoName) ? '⭐' : '☆';
+ starBtn.addEventListener('click', async (e) => {
+ e.stopPropagation();
+ await toggleFavorite(owner, repoName, cloneUrl);
+ const nowFav = isFavorite(owner, repoName);
+ starBtn.textContent = nowFav ? '⭐' : '☆';
+ starBtn.title = nowFav ? 'Aus Favoriten entfernen' : 'Zu Favoriten hinzufügen';
+ starBtn.classList.toggle('active', nowFav);
+ // Favoriten-Sektion live aktualisieren
+ const sec = $('favRecentSection');
+ if (sec) renderFavRecentSection(sec, res.repos);
+ });
+ card.appendChild(starBtn);
+ }
+
+ const iconEl = document.createElement('div');
+ iconEl.className = 'item-icon';
+ iconEl.textContent = '📦';
+ card.appendChild(iconEl);
+ const nameEl = document.createElement('div');
+ nameEl.className = 'item-name';
+ nameEl.textContent = repoName;
+ card.appendChild(nameEl);
+
+ // Repo-Größe anzeigen
+ if (repo.size != null) {
+ const sizeEl = document.createElement('div');
+ sizeEl.className = 'repo-size-badge';
+ const kb = repo.size;
+ sizeEl.textContent = kb >= 1024
+ ? `${(kb / 1024).toFixed(1)} MB`
+ : `${kb} KB`;
+ card.appendChild(sizeEl);
+ }
+
// --- Nativer Drag Start (Download) ---
card.draggable = true;
card.addEventListener('dragstart', async (ev) => {
@@ -930,11 +1509,11 @@ async function loadGiteaRepos() {
window.electronAPI.startNativeDrag(resDrag.tempPath);
setStatus('Ready to drag');
} else {
- setStatus('Download preparation failed');
+ showError('Download preparation failed');
}
} catch (error) {
console.error('Drag preparation error:', error);
- setStatus('Error preparing download');
+ showError('Error preparing download');
} finally {
hideProgress();
}
@@ -960,7 +1539,7 @@ async function loadGiteaRepos() {
const files = ev.dataTransfer.files;
if (!files || files.length === 0) {
- setStatus("Keine Dateien zum Upload gefunden.");
+ showWarning("Keine Dateien zum Upload gefunden.");
return;
}
@@ -978,12 +1557,12 @@ async function loadGiteaRepos() {
repo: repoName,
destPath: '',
cloneUrl,
- branch: 'main'
+ branch: getDefaultBranch(owner, repoName)
});
if (!res.ok) {
console.error("Upload Fehler:", res.error);
- setStatus("Fehler: " + res.error);
+ showError("Fehler: " + res.error);
}
} catch (err) {
console.error("Kritischer Upload Fehler:", err);
@@ -995,7 +1574,10 @@ async function loadGiteaRepos() {
setStatus('Upload abgeschlossen');
});
- card.onclick = () => loadRepoContents(owner, repoName, '');
+ card.onclick = () => {
+ addToRecent(owner, repoName, cloneUrl);
+ loadRepoContents(owner, repoName, '');
+ };
card.oncontextmenu = (ev) => showRepoContextMenu(ev, owner, repoName, cloneUrl, card);
grid.appendChild(card);
});
@@ -1003,7 +1585,7 @@ async function loadGiteaRepos() {
setStatus(`Loaded ${res.repos.length} repos`);
} catch (error) {
console.error('Error loading repos:', error);
- setStatus('Error loading repositories');
+ showError('Error loading repositories');
}
}
@@ -1020,7 +1602,7 @@ async function loadRepoContents(owner, repo, path) {
if (btnCommits) {
btnCommits.classList.remove('hidden');
- btnCommits.onclick = () => loadCommitHistory(owner, repo, 'main');
+ btnCommits.onclick = () => loadCommitHistory(owner, repo, getDefaultBranch(owner, repo));
}
if (btnReleases) {
@@ -1036,16 +1618,18 @@ async function loadRepoContents(owner, repo, path) {
setStatus(`Loading: /${path || 'root'}`);
+ const ref = getDefaultBranch(owner, repo);
+
try {
const res = await window.electronAPI.getGiteaRepoContents({
owner,
repo,
path,
- ref: 'main'
+ ref
});
if (!res.ok) {
- setStatus('Error: ' + (res.error || 'Unknown error'));
+ showError('Error: ' + (res.error || 'Unknown error'));
return;
}
@@ -1054,16 +1638,26 @@ async function loadRepoContents(owner, repo, path) {
grid.innerHTML = '';
if (!res.items || res.items.length === 0) {
- grid.innerHTML = '
Leerer Ordner
';
- setStatus('Empty folder');
+ const emptyMsg = res.empty
+ ? '📭 Leeres Repository — noch keine Commits'
+ : '📂 Leerer Ordner';
+ grid.innerHTML = `
${emptyMsg}
`;
+ setStatus(res.empty ? 'Leeres Repository' : 'Leerer Ordner');
return;
}
res.items.forEach(item => {
- const icon = item.type === 'dir' ? '📁' : '📄';
const card = document.createElement('div');
card.className = 'item-card';
- card.innerHTML = `
${icon}
${item.name}
`;
+ // Farbiges Icon-Element
+ const iconEl = makeFileIconEl(item.name, item.type === 'dir');
+ const nameEl = document.createElement('div');
+ nameEl.className = 'item-name';
+ nameEl.textContent = item.name;
+ card.appendChild(iconEl);
+ card.appendChild(nameEl);
+ // lastSelectedItem tracken
+ card.addEventListener('click', () => { lastSelectedItem = { type: 'gitea', item, owner, repo }; });
// Drag für Files und Folders
if (item.type === 'dir') {
@@ -1126,7 +1720,7 @@ async function loadRepoContents(owner, repo, path) {
owner,
repo,
destPath: targetPath,
- branch: 'main'
+ branch: getDefaultBranch(owner, repo)
});
} catch (error) {
console.error('Upload error:', error);
@@ -1138,9 +1732,27 @@ async function loadRepoContents(owner, repo, path) {
});
if (item.type === 'dir') {
- card.onclick = () => loadRepoContents(owner, repo, item.path);
+ card.onclick = (e) => {
+ if (e.ctrlKey || e.metaKey) {
+ if (selectedItems.has(item.path)) { selectedItems.delete(item.path); card.classList.remove('selected'); }
+ else { selectedItems.add(item.path); card.classList.add('selected'); }
+ return;
+ }
+ selectedItems.clear();
+ document.querySelectorAll('.item-card.selected').forEach(c => c.classList.remove('selected'));
+ loadRepoContents(owner, repo, item.path);
+ };
} else {
- card.onclick = () => openGiteaFileInEditor(owner, repo, item.path, item.name);
+ card.onclick = (e) => {
+ if (e.ctrlKey || e.metaKey) {
+ if (selectedItems.has(item.path)) { selectedItems.delete(item.path); card.classList.remove('selected'); }
+ else { selectedItems.add(item.path); card.classList.add('selected'); }
+ return;
+ }
+ selectedItems.clear();
+ document.querySelectorAll('.item-card.selected').forEach(c => c.classList.remove('selected'));
+ openGiteaFileInEditor(owner, repo, item.path, item.name);
+ };
}
card.oncontextmenu = (ev) => showGiteaItemContextMenu(ev, item, owner, repo);
@@ -1150,7 +1762,7 @@ async function loadRepoContents(owner, repo, path) {
setStatus(`Loaded ${res.items.length} items`);
} catch (error) {
console.error('Error loading repo contents:', error);
- setStatus('Error loading contents');
+ showError('Error loading contents');
}
}
@@ -1171,7 +1783,7 @@ async function selectLocalFolder() {
await loadBranches(folder);
} catch (error) {
console.error('Error selecting folder:', error);
- setStatus('Error selecting folder');
+ showError('Error selecting folder');
}
}
@@ -1188,7 +1800,7 @@ async function refreshLocalTree(folder) {
grid.innerHTML = '';
if (!res.ok) {
- setStatus('Error loading local files');
+ showError('Error loading local files');
return;
}
@@ -1200,22 +1812,44 @@ async function refreshLocalTree(folder) {
res.tree.forEach(node => {
const card = document.createElement('div');
card.className = 'item-card';
- card.innerHTML = `
-
${node.isDirectory ? '📁' : '📄'}
-
${node.name}
- `;
+ card.dataset.path = node.path;
+ // Farbiges Icon + Name
+ const nodeIconEl = makeFileIconEl(node.name, node.isDirectory);
+ const nodeNameEl = document.createElement('div');
+ nodeNameEl.className = 'item-name';
+ nodeNameEl.textContent = node.name;
+ card.appendChild(nodeIconEl);
+ card.appendChild(nodeNameEl);
+ // lastSelectedItem tracken
+ card.addEventListener('click', () => { lastSelectedItem = { type: 'local', node }; });
- card.onclick = async () => {
+ card.onclick = async (e) => {
+ if (e.ctrlKey || e.metaKey) {
+ // Mehrfachauswahl
+ if (selectedItems.has(node.path)) {
+ selectedItems.delete(node.path);
+ card.classList.remove('selected');
+ } else {
+ selectedItems.add(node.path);
+ card.classList.add('selected');
+ }
+ return;
+ }
+ // Normale Auswahl
+ selectedItems.clear();
+ grid.querySelectorAll('.item-card.selected').forEach(c => c.classList.remove('selected'));
if (!node.isDirectory) {
openFileEditor(node.path, node.name);
}
};
+
+ card.oncontextmenu = (ev) => showLocalItemContextMenu(ev, node);
grid.appendChild(card);
});
} catch (error) {
console.error('Error refreshing tree:', error);
- setStatus('Error loading file tree');
+ showError('Error loading file tree');
}
}
@@ -1227,7 +1861,11 @@ async function pushLocalFolder() {
alert('Select local folder first');
return;
}
-
+
+ // Commit-Nachricht abfragen
+ const message = await showCommitMessageModal();
+ if (message === null) return; // Abgebrochen
+
const branch = $('branchSelect')?.value || 'main';
const repoName = $('repoName')?.value;
const platform = $('platform')?.value;
@@ -1240,22 +1878,87 @@ async function pushLocalFolder() {
folder: selectedFolder,
branch,
repoName,
- platform
+ platform,
+ commitMessage: message
});
if (res.ok) {
setStatus('Push succeeded');
} else {
- setStatus('Push failed: ' + (res.error || 'Unknown error'));
+ showError('Push failed: ' + (res.error || 'Unknown error'));
}
} catch (error) {
console.error('Push error:', error);
- setStatus('Push failed');
+ showError('Push failed');
} finally {
hideProgress();
}
}
+// Modal für Commit-Nachricht
+function showCommitMessageModal() {
+ return new Promise((resolve) => {
+ const modal = document.createElement('div');
+ modal.className = 'modal';
+ modal.style.zIndex = '99999';
+ modal.innerHTML = `
+
+
💬 Commit-Nachricht
+
+
+
+
+
+ ${['🐛 Fix Bug', '✨ Neues Feature', '📝 Dokumentation', '♻️ Refactoring', '🚀 Release'].map(t =>
+ ``
+ ).join('')}
+
+
+
+
+
+
+ `;
+ document.body.appendChild(modal);
+
+ const input = modal.querySelector('#commitMsgInput');
+ input.focus();
+
+ // Quick-Buttons
+ modal.querySelectorAll('.commit-quick-btn').forEach(btn => {
+ btn.onclick = () => {
+ input.value = btn.textContent.trim();
+ input.focus();
+ };
+ });
+
+ modal.querySelector('#btnCommitOk').onclick = () => {
+ const val = input.value.trim() || 'Update via Git Manager GUI';
+ modal.remove();
+ resolve(val);
+ };
+
+ modal.querySelector('#btnCommitCancel').onclick = () => {
+ modal.remove();
+ resolve(null);
+ };
+
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') modal.querySelector('#btnCommitOk').click();
+ if (e.key === 'Escape') modal.querySelector('#btnCommitCancel').click();
+ });
+ });
+}
+
async function loadBranches(folder) {
try {
const res = await window.electronAPI.getBranches({ folder });
@@ -1323,6 +2026,26 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element) {
return item;
};
+ // ── Favorit ──
+ if (featureFavorites) {
+ const isFav = isFavorite(owner, repoName);
+ const favItem = createMenuItem(
+ isFav ? '⭐' : '☆',
+ isFav ? 'Aus Favoriten entfernen' : 'Zu Favoriten hinzufügen',
+ async () => {
+ menu.remove();
+ await toggleFavorite(owner, repoName, cloneUrl);
+ loadGiteaRepos();
+ },
+ isFav ? '#f59e0b' : null
+ );
+ menu.appendChild(favItem);
+
+ const sep = document.createElement('div');
+ sep.style.cssText = 'height:1px;background:rgba(255,255,255,0.08);margin:4px 0;';
+ menu.appendChild(sep);
+ }
+
const uploadItem = createMenuItem('🚀', 'Folder hier hochladen', async () => {
menu.remove();
try {
@@ -1335,7 +2058,7 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element) {
repo: repoName,
destPath: '',
cloneUrl,
- branch: 'main'
+ branch: getDefaultBranch(owner, repoName)
});
hideProgress();
setStatus('Upload complete');
@@ -1343,7 +2066,7 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element) {
} catch (error) {
console.error('Upload error:', error);
hideProgress();
- setStatus('Upload failed');
+ showError('Upload failed');
}
});
@@ -1354,13 +2077,13 @@ function showRepoContextMenu(ev, owner, repoName, cloneUrl, element) {
const res = await window.electronAPI.deleteGiteaRepo({ owner, repo: repoName });
if (res.ok) {
element.remove();
- setStatus('Repository deleted');
+ showSuccess('Repository deleted');
} else {
- setStatus('Delete failed: ' + res.error);
+ showError('Delete failed: ' + res.error);
}
} catch (error) {
console.error('Delete error:', error);
- setStatus('Delete failed');
+ showError('Delete failed');
}
}
}, '#ef4444');
@@ -1384,38 +2107,413 @@ function showGiteaItemContextMenu(ev, item, owner, repo) {
const menu = document.createElement('div');
menu.id = 'ctxMenu';
menu.className = 'context-menu';
- menu.style.left = ev.clientX + 'px';
- menu.style.top = ev.clientY + 'px';
- if (item.type === 'file') {
- const downloadItem = document.createElement('div');
- downloadItem.className = 'context-item';
- downloadItem.innerHTML = '📥 Download File';
- downloadItem.onclick = async () => {
- menu.remove();
- try {
- const res = await window.electronAPI.downloadGiteaFile({
- owner,
- repo,
- path: item.path
- });
- if (res.ok) {
- setStatus(`Saved to ${res.savedTo}`);
- } else {
- setStatus('Download failed');
- }
- } catch (error) {
- console.error('Download error:', error);
- setStatus('Download failed');
+ // Menü positionieren (nicht außerhalb des Fensters)
+ const menuW = 220, menuH = 360;
+ const x = Math.min(ev.clientX, window.innerWidth - menuW);
+ const y = Math.min(ev.clientY, window.innerHeight - menuH);
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+
+ const addSep = () => {
+ const s = document.createElement('div');
+ s.style.cssText = 'height:1px;background:rgba(255,255,255,0.1);margin:4px 0;';
+ menu.appendChild(s);
+ };
+
+ const addItem = (icon, text, onClick, color = null) => {
+ const el = document.createElement('div');
+ el.className = 'context-item';
+ el.innerHTML = `${icon} ${text}`;
+ if (color) el.style.color = color;
+ el.onclick = () => { menu.remove(); onClick(); };
+ menu.appendChild(el);
+ };
+
+ // Mehrfachauswahl-Info
+ if (selectedItems.size > 1 && selectedItems.has(item.path)) {
+ const infoEl = document.createElement('div');
+ infoEl.style.cssText = 'padding:8px 14px;font-size:11px;color:var(--accent-primary);font-weight:600;';
+ infoEl.textContent = `${selectedItems.size} Elemente ausgewählt`;
+ menu.appendChild(infoEl);
+ addSep();
+
+ addItem('🗑️', `Alle ${selectedItems.size} löschen`, async () => {
+ if (!confirm(`${selectedItems.size} Elemente wirklich löschen?`)) return;
+ showProgress(0, 'Lösche...');
+ let done = 0;
+ for (const p of selectedItems) {
+ await window.electronAPI.deleteFile({ path: p, owner, repo, isGitea: true });
+ done++;
+ showProgress(Math.round((done / selectedItems.size) * 100), `Lösche ${done}/${selectedItems.size}`);
}
- };
- menu.appendChild(downloadItem);
+ selectedItems.clear();
+ hideProgress();
+ setStatus('Bulk-Delete abgeschlossen');
+ loadRepoContents(owner, repo, currentState.path);
+ }, '#ef4444');
+
+ document.body.appendChild(menu);
+ setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
+ return;
}
+ // --- ÖFFNEN ---
+ addItem(
+ item.type === 'dir' ? '📂' : '✏️',
+ item.type === 'dir' ? 'Öffnen' : 'Im Editor öffnen',
+ () => {
+ if (item.type === 'dir') loadRepoContents(owner, repo, item.path);
+ else openGiteaFileInEditor(owner, repo, item.path, item.name);
+ }
+ );
+
+ addSep();
+
+ // --- NEU ERSTELLEN (immer sichtbar) ---
+ addItem('📄', 'Neue Datei erstellen', () => showNewGiteaItemModal(owner, repo, item.type === 'dir' ? item.path : currentState.path, 'file'));
+ addItem('📁', 'Neuen Ordner erstellen', () => showNewGiteaItemModal(owner, repo, item.type === 'dir' ? item.path : currentState.path, 'folder'));
+
+ addSep();
+
+ // --- UMBENENNEN ---
+ addItem('✏️', 'Umbenennen', () => showGiteaRenameModal(item, owner, repo));
+
+ // --- CUT & PASTE ---
+ addItem('✂️', 'Ausschneiden (Cut)', () => {
+ clipboard = { item: { ...item, owner, repo, isGitea: true }, action: 'cut' };
+ setStatus(`✂️ "${item.name}" ausgeschnitten — Zielordner öffnen und Einfügen wählen`);
+ });
+
+ if (clipboard.item && clipboard.item.isGitea && item.type === 'dir') {
+ addItem('📋', `Einfügen: "${clipboard.item.name}"`, async () => {
+ await pasteGiteaItem(owner, repo, item.path);
+ });
+ }
+
+ addSep();
+
+ // --- DOWNLOAD ---
+ if (item.type === 'file') {
+ addItem('📥', 'Herunterladen', async () => {
+ const res = await window.electronAPI.downloadGiteaFile({ owner, repo, path: item.path });
+ setStatus(res.ok ? `Gespeichert: ${res.savedTo}` : 'Download fehlgeschlagen');
+ });
+ } else {
+ addItem('📥', 'Ordner herunterladen', async () => {
+ showProgress(0, `Lade ${item.name}...`);
+ const res = await window.electronAPI.downloadGiteaFolder({ owner, repo, path: item.path });
+ hideProgress();
+ setStatus(res.ok ? `Gespeichert: ${res.savedTo}` : 'Download fehlgeschlagen');
+ });
+ }
+
+ addSep();
+
+ // --- LÖSCHEN ---
+ addItem('🗑️', 'Löschen', async () => {
+ if (!confirm(`"${item.name}" wirklich löschen?`)) return;
+ showProgress(0, `Lösche ${item.name}...`);
+ const res = await window.electronAPI.deleteFile({ path: item.path, owner, repo, isGitea: true });
+ hideProgress();
+ if (res && res.ok) {
+ setStatus(`${item.name} gelöscht`);
+ loadRepoContents(owner, repo, currentState.path);
+ } else {
+ showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
+ alert('Löschen fehlgeschlagen:\n' + (res?.error || 'Unbekannter Fehler'));
+ }
+ }, '#ef4444');
+
document.body.appendChild(menu);
- setTimeout(() => {
- document.addEventListener('click', () => menu.remove(), { once: true });
- }, 10);
+ setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
+}
+
+/* -------------------------
+ LOKALES KONTEXT-MENÜ
+ ------------------------- */
+function showLocalItemContextMenu(ev, node) {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ const old = $('ctxMenu');
+ if (old) old.remove();
+
+ const menu = document.createElement('div');
+ menu.id = 'ctxMenu';
+ menu.className = 'context-menu';
+
+ const menuW = 220, menuH = 360;
+ const x = Math.min(ev.clientX, window.innerWidth - menuW);
+ const y = Math.min(ev.clientY, window.innerHeight - menuH);
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+
+ const addSep = () => {
+ const s = document.createElement('div');
+ s.style.cssText = 'height:1px;background:rgba(255,255,255,0.1);margin:4px 0;';
+ menu.appendChild(s);
+ };
+
+ const addItem = (icon, text, onClick, color = null) => {
+ const el = document.createElement('div');
+ el.className = 'context-item';
+ el.innerHTML = `${icon} ${text}`;
+ if (color) el.style.color = color;
+ el.onclick = () => { menu.remove(); onClick(); };
+ menu.appendChild(el);
+ };
+
+ // Mehrfachauswahl-Bulk-Delete
+ if (selectedItems.size > 1 && selectedItems.has(node.path)) {
+ const infoEl = document.createElement('div');
+ infoEl.style.cssText = 'padding:8px 14px;font-size:11px;color:var(--accent-primary);font-weight:600;';
+ infoEl.textContent = `${selectedItems.size} Elemente ausgewählt`;
+ menu.appendChild(infoEl);
+ addSep();
+
+ addItem('🗑️', `Alle ${selectedItems.size} löschen`, async () => {
+ if (!confirm(`${selectedItems.size} Elemente wirklich löschen?`)) return;
+ for (const p of selectedItems) {
+ await window.electronAPI.deleteFile({ path: p });
+ }
+ selectedItems.clear();
+ setStatus('Bulk-Delete abgeschlossen');
+ if (selectedFolder) refreshLocalTree(selectedFolder);
+ }, '#ef4444');
+
+ document.body.appendChild(menu);
+ setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
+ return;
+ }
+
+ // --- ÖFFNEN ---
+ if (!node.isDirectory) {
+ addItem('✏️', 'Im Editor öffnen', () => openFileEditor(node.path, node.name));
+ addSep();
+ }
+
+ // --- NEU ERSTELLEN ---
+ const targetDir = node.isDirectory ? node.path : require('path').dirname(node.path);
+ addItem('📄', 'Neue Datei erstellen', () => showNewLocalItemModal(targetDir, 'file'));
+ addItem('📁', 'Neuen Ordner erstellen', () => showNewLocalItemModal(targetDir, 'folder'));
+
+ addSep();
+
+ // --- UMBENENNEN ---
+ addItem('✏️', 'Umbenennen', () => showLocalRenameModal(node));
+
+ // --- CUT & PASTE ---
+ addItem('✂️', 'Ausschneiden (Cut)', () => {
+ clipboard = { item: { ...node, isLocal: true }, action: 'cut' };
+ setStatus(`✂️ "${node.name}" ausgeschnitten`);
+ });
+
+ if (clipboard.item && clipboard.item.isLocal && node.isDirectory) {
+ addItem('📋', `Einfügen: "${clipboard.item.name}"`, async () => {
+ await pasteLocalItem(node.path);
+ });
+ }
+
+ addSep();
+
+ // --- DOWNLOAD / KOPIEREN ---
+ addItem('📥', 'Kopieren nach...', async () => {
+ const destFolder = await window.electronAPI.selectFolder();
+ if (!destFolder) return;
+ const res = await window.electronAPI.copyLocalItem({ src: node.path, destDir: destFolder });
+ setStatus(res?.ok ? `Kopiert nach: ${destFolder}` : 'Kopieren fehlgeschlagen: ' + (res?.error || ''));
+ });
+
+ addSep();
+
+ // --- LÖSCHEN ---
+ addItem('🗑️', 'Löschen', async () => {
+ if (!confirm(`"${node.name}" wirklich löschen?`)) return;
+ const res = await window.electronAPI.deleteFile({ path: node.path });
+ if (res && res.ok) {
+ setStatus(`${node.name} gelöscht`);
+ if (selectedFolder) refreshLocalTree(selectedFolder);
+ } else {
+ showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
+ }
+ }, '#ef4444');
+
+ document.body.appendChild(menu);
+ setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
+}
+
+/* =========================================
+ MODAL HELPER FUNKTIONEN
+ ========================================= */
+
+// Gitea: Umbenennen
+function showGiteaRenameModal(item, owner, repo) {
+ showInputModal({
+ title: `✏️ Umbenennen`,
+ label: 'Neuer Name',
+ defaultValue: item.name,
+ confirmText: 'Umbenennen',
+ onConfirm: async (newName) => {
+ if (!newName || newName === item.name) return;
+ setStatus('Umbenennen...');
+ const parentPath = item.path.split('/').slice(0, -1).join('/');
+ const newPath = parentPath ? `${parentPath}/${newName}` : newName;
+ const res = await window.electronAPI.renameGiteaItem({
+ owner, repo,
+ oldPath: item.path,
+ newPath,
+ isDir: item.type === 'dir'
+ });
+ if (res?.ok) {
+ setStatus(`Umbenannt in "${newName}"`);
+ loadRepoContents(owner, repo, currentState.path);
+ } else {
+ alert('Umbenennen fehlgeschlagen:\n' + (res?.error || 'Unbekannter Fehler'));
+ showError('Fehler beim Umbenennen');
+ }
+ }
+ });
+}
+
+// Gitea: Neue Datei / Ordner
+function showNewGiteaItemModal(owner, repo, parentPath, type) {
+ showInputModal({
+ title: type === 'file' ? '📄 Neue Datei' : '📁 Neuer Ordner',
+ label: type === 'file' ? 'Dateiname (z.B. README.md)' : 'Ordnername',
+ defaultValue: '',
+ confirmText: 'Erstellen',
+ onConfirm: async (name) => {
+ if (!name) return;
+ const targetPath = parentPath ? `${parentPath}/${name}` : name;
+ const res = await window.electronAPI.createGiteaItem({
+ owner, repo,
+ path: targetPath,
+ type
+ });
+ if (res?.ok) {
+ setStatus(`"${name}" erstellt`);
+ loadRepoContents(owner, repo, currentState.path);
+ } else {
+ alert('Erstellen fehlgeschlagen:\n' + (res?.error || ''));
+ }
+ }
+ });
+}
+
+// Lokal: Umbenennen
+function showLocalRenameModal(node) {
+ showInputModal({
+ title: `✏️ Umbenennen`,
+ label: 'Neuer Name',
+ defaultValue: node.name,
+ confirmText: 'Umbenennen',
+ onConfirm: async (newName) => {
+ if (!newName || newName === node.name) return;
+ const res = await window.electronAPI.renameLocalItem({ oldPath: node.path, newName });
+ if (res?.ok) {
+ setStatus(`Umbenannt in "${newName}"`);
+ if (selectedFolder) refreshLocalTree(selectedFolder);
+ } else {
+ alert('Umbenennen fehlgeschlagen:\n' + (res?.error || ''));
+ }
+ }
+ });
+}
+
+// Lokal: Neue Datei / Ordner
+function showNewLocalItemModal(parentDir, type) {
+ showInputModal({
+ title: type === 'file' ? '📄 Neue Datei' : '📁 Neuer Ordner',
+ label: type === 'file' ? 'Dateiname (z.B. README.md)' : 'Ordnername',
+ defaultValue: '',
+ confirmText: 'Erstellen',
+ onConfirm: async (name) => {
+ if (!name) return;
+ const res = await window.electronAPI.createLocalItem({ parentDir, name, type });
+ if (res?.ok) {
+ setStatus(`"${name}" erstellt`);
+ if (selectedFolder) refreshLocalTree(selectedFolder);
+ } else {
+ alert('Erstellen fehlgeschlagen:\n' + (res?.error || ''));
+ }
+ }
+ });
+}
+
+// Gitea: Einfügen nach Cut
+async function pasteGiteaItem(owner, repo, destFolderPath) {
+ if (!clipboard.item || !clipboard.item.isGitea) return;
+ const src = clipboard.item;
+ const newPath = destFolderPath ? `${destFolderPath}/${src.name}` : src.name;
+ setStatus(`Verschiebe "${src.name}"...`);
+ showProgress(0, `Verschiebe...`);
+ const res = await window.electronAPI.renameGiteaItem({
+ owner, repo,
+ oldPath: src.path,
+ newPath,
+ isDir: src.type === 'dir'
+ });
+ hideProgress();
+ if (res?.ok) {
+ clipboard = { item: null, action: null };
+ setStatus(`"${src.name}" verschoben`);
+ loadRepoContents(owner, repo, currentState.path);
+ } else {
+ alert('Verschieben fehlgeschlagen:\n' + (res?.error || ''));
+ showError('Fehler beim Verschieben');
+ }
+}
+
+// Lokal: Einfügen nach Cut
+async function pasteLocalItem(destDir) {
+ if (!clipboard.item || !clipboard.item.isLocal) return;
+ const src = clipboard.item;
+ setStatus(`Verschiebe "${src.name}"...`);
+ const res = await window.electronAPI.moveLocalItem({ srcPath: src.path, destDir });
+ if (res?.ok) {
+ clipboard = { item: null, action: null };
+ setStatus(`"${src.name}" verschoben`);
+ if (selectedFolder) refreshLocalTree(selectedFolder);
+ } else {
+ alert('Verschieben fehlgeschlagen:\n' + (res?.error || ''));
+ }
+}
+
+// Generic Input Modal
+function showInputModal({ title, label, defaultValue, confirmText, onConfirm }) {
+ const modal = document.createElement('div');
+ modal.className = 'modal';
+ modal.style.zIndex = '99999';
+ modal.innerHTML = `
+
+
${title}
+
+
+
+
+
+
+
+
+
+ `;
+ document.body.appendChild(modal);
+ const input = modal.querySelector('#inputModalField');
+ input.focus();
+ input.select();
+
+ modal.querySelector('#inputModalOk').onclick = () => {
+ const val = input.value.trim();
+ modal.remove();
+ onConfirm(val);
+ };
+ modal.querySelector('#inputModalCancel').onclick = () => modal.remove();
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') modal.querySelector('#inputModalOk').click();
+ if (e.key === 'Escape') modal.querySelector('#inputModalCancel').click();
+ });
+ modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
}
/* -------------------------
@@ -1435,18 +2533,18 @@ async function previewGiteaFile(owner, repo, filePath) {
owner,
repo,
path: filePath,
- ref: 'main'
+ ref: getDefaultBranch(owner, repo)
});
if (res.ok) {
console.log("Content of", filePath, ":", res.content);
setStatus(`Previewed: ${filePath}`);
} else {
- setStatus('Preview failed');
+ showError('Preview failed');
}
} catch (error) {
console.error('Preview error:', error);
- setStatus('Preview failed');
+ showError('Preview failed');
}
}
@@ -1469,20 +2567,149 @@ async function createRepoHandler() {
if (res.ok) {
$('repoActionModal')?.classList.add('hidden');
- setStatus('Repository created');
+ showSuccess('Repository created');
loadGiteaRepos();
} else {
- setStatus('Create failed: ' + (res.error || 'Unknown error'));
+ showError('Create failed: ' + (res.error || 'Unknown error'));
}
} catch (error) {
console.error('Create repo error:', error);
- setStatus('Create failed');
+ showError('Create failed');
}
}
/* -------------------------
GLOBALER DROP-HANDLER FÜR REPO-ANSICHT
------------------------- */
+/* -------------------------
+ HINTERGRUND KONTEXT-MENÜ (Rechtsklick auf leere Fläche)
+ ------------------------- */
+function setupBackgroundContextMenu() {
+ // Listener auf #main statt explorerGrid, da Grid kleiner als sichtbarer Bereich sein kann
+ const mainEl = $('main');
+ if (!mainEl) return;
+
+ mainEl.addEventListener('contextmenu', (ev) => {
+ // Nicht auslösen wenn auf eine Karte oder interaktives Element geklickt wird
+ if (ev.target.closest('.item-card, .release-card, .commit-card, .fav-chip, .fav-star-btn, button, input, textarea, select, a')) return;
+
+ // Nur in Repo- oder Lokal-Ansicht
+ if (currentState.view !== 'gitea-repo' && currentState.view !== 'local') return;
+
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ const old = $('ctxMenu');
+ if (old) old.remove();
+
+ const menu = document.createElement('div');
+ menu.id = 'ctxMenu';
+ menu.className = 'context-menu';
+
+ const menuW = 220, menuH = 160;
+ const x = Math.min(ev.clientX, window.innerWidth - menuW);
+ const y = Math.min(ev.clientY, window.innerHeight - menuH);
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+
+ // Aktuelle Pfad-Info als Header
+ const header = document.createElement('div');
+ header.style.cssText = 'padding:8px 14px 4px;font-size:11px;color:var(--text-muted);letter-spacing:0.5px;';
+ const currentPath = currentState.view === 'gitea-repo'
+ ? (currentState.path || 'Root')
+ : (selectedFolder ? selectedFolder.split(/[\\/]/).pop() : 'Lokal');
+ header.textContent = `📂 ${currentPath}`;
+ menu.appendChild(header);
+
+ const sep = document.createElement('div');
+ sep.style.cssText = 'height:1px;background:rgba(255,255,255,0.1);margin:4px 0;';
+ menu.appendChild(sep);
+
+ const addItem = (icon, text, onClick) => {
+ const el = document.createElement('div');
+ el.className = 'context-item';
+ el.innerHTML = `${icon} ${text}`;
+ el.onclick = () => { menu.remove(); onClick(); };
+ menu.appendChild(el);
+ };
+
+ if (currentState.view === 'gitea-repo') {
+ const { owner, repo, path: currentPath } = currentState;
+
+ addItem('📄', 'Neue Datei erstellen', () =>
+ showNewGiteaItemModal(owner, repo, currentPath, 'file')
+ );
+ addItem('📁', 'Neuen Ordner erstellen', () =>
+ showNewGiteaItemModal(owner, repo, currentPath, 'folder')
+ );
+
+ // Einfügen: gleiche Quelle ODER Cross-Paste von Lokal
+ if (clipboard.item) {
+ const sep2 = document.createElement('div');
+ sep2.style.cssText = 'height:1px;background:rgba(255,255,255,0.1);margin:4px 0;';
+ menu.appendChild(sep2);
+ if (clipboard.item.isGitea) {
+ addItem('📋', `Einfügen: "${clipboard.item.name}"`, () =>
+ pasteGiteaItem(owner, repo, currentPath)
+ );
+ } else if (clipboard.item.isLocal) {
+ 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)
+ });
+ showSuccess(`"${clipboard.item.name}" nach Gitea kopiert`);
+ loadRepoContents(owner, repo, currentState.path);
+ } catch(e) { showError('Cross-Paste fehlgeschlagen'); }
+ finally { hideProgress(); }
+ });
+ }
+ }
+
+ } else if (currentState.view === 'local' && selectedFolder) {
+ addItem('📄', 'Neue Datei erstellen', () =>
+ showNewLocalItemModal(selectedFolder, 'file')
+ );
+ addItem('📁', 'Neuen Ordner erstellen', () =>
+ showNewLocalItemModal(selectedFolder, 'folder')
+ );
+
+ if (clipboard.item) {
+ const sep2 = document.createElement('div');
+ sep2.style.cssText = 'height:1px;background:rgba(255,255,255,0.1);margin:4px 0;';
+ menu.appendChild(sep2);
+ if (clipboard.item.isLocal) {
+ addItem('📋', `Einfügen: "${clipboard.item.name}"`, () =>
+ pasteLocalItem(selectedFolder)
+ );
+ } else if (clipboard.item.isGitea) {
+ addItem('📋', `⬇️ Von Gitea einfügen: "${clipboard.item.name}"`, async () => {
+ showProgress(0, `Lade "${clipboard.item.name}" herunter...`);
+ try {
+ await window.electronAPI.downloadGiteaFolder({
+ owner: clipboard.item.owner,
+ repo: clipboard.item.repo,
+ giteaPath: clipboard.item.path,
+ localPath: selectedFolder
+ });
+ showSuccess(`"${clipboard.item.name}" nach Lokal kopiert`);
+ refreshLocalTree(selectedFolder);
+ } catch(e) { showError('Cross-Paste fehlgeschlagen'); }
+ finally { hideProgress(); }
+ });
+ }
+ }
+ }
+
+ document.body.appendChild(menu);
+ setTimeout(() => document.addEventListener('click', () => menu.remove(), { once: true }), 10);
+ });
+}
+
function setupGlobalDropZone() {
const main = $('main');
if (!main) return;
@@ -1525,7 +2752,7 @@ function setupGlobalDropZone() {
const files = ev.dataTransfer.files;
if (!files || files.length === 0) {
- setStatus("Keine Dateien zum Upload gefunden.");
+ showWarning("Keine Dateien zum Upload gefunden.");
return;
}
@@ -1547,18 +2774,18 @@ function setupGlobalDropZone() {
owner,
repo,
destPath: targetPath,
- branch: 'main'
+ branch: getDefaultBranch(owner, repo)
});
if (!res.ok) {
console.error("Upload error:", res.error);
- setStatus("Error: " + res.error);
+ showError("Error: " + res.error);
} else {
setStatus(`Uploaded: ${baseName}`);
}
} catch (err) {
console.error("Critical upload error:", err);
- setStatus("Upload failed");
+ showError("Upload failed");
}
}
@@ -1575,6 +2802,8 @@ function setupGlobalDropZone() {
INITIALISIERUNG
------------------------- */
window.addEventListener('DOMContentLoaded', async () => {
+ // Favoriten & Verlauf vorladen
+ await loadFavoritesAndRecent();
// Prevent default drag/drop on document (except in repo view)
document.addEventListener('dragover', e => {
if (currentState.view !== 'gitea-repo') {
@@ -1596,6 +2825,27 @@ window.addEventListener('DOMContentLoaded', async () => {
if ($('githubToken')) $('githubToken').value = creds.githubToken || '';
if ($('giteaToken')) $('giteaToken').value = creds.giteaToken || '';
if ($('giteaURL')) $('giteaURL').value = creds.giteaURL || '';
+
+ // Feature-Flags aus gespeicherten Einstellungen
+ 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 (typeof creds.featureColoredIcons === 'boolean') featureColoredIcons = creds.featureColoredIcons;
+ document.body.classList.toggle('compact-mode', compactMode);
+
+ // Collapse-Zustand wiederherstellen
+ if (typeof creds.favCollapsedFavorites === 'boolean') favSectionCollapsed.favorites = creds.favCollapsedFavorites;
+ if (typeof creds.favCollapsedRecent === 'boolean') favSectionCollapsed.recent = creds.favCollapsedRecent;
+
+ // Settings-Checkboxen befüllen
+ const cbFav = $('settingFavorites');
+ const cbRec = $('settingRecent');
+ const cbCompact = $('settingCompact');
+ if (cbFav) cbFav.checked = featureFavorites;
+ if (cbRec) cbRec.checked = featureRecent;
+ if (cbCompact) cbCompact.checked = compactMode;
+ const cbColorIcons = $('settingColoredIcons');
+ if (cbColorIcons) cbColorIcons.checked = featureColoredIcons;
// 🆕 AUTO-LOGIN: Wenn Gitea-Credentials vorhanden sind, lade sofort die Repos
if (creds.giteaToken && creds.giteaURL) {
@@ -1616,7 +2866,7 @@ window.addEventListener('DOMContentLoaded', async () => {
}
} catch (error) {
console.error('Error loading credentials:', error);
- setStatus('Fehler beim Laden der Einstellungen');
+ showError('Fehler beim Laden der Einstellungen');
}
// Rest of Event Handlers... (bleibt unverändert)
@@ -1680,17 +2930,36 @@ window.addEventListener('DOMContentLoaded', async () => {
if ($('btnSaveSettings')) {
$('btnSaveSettings').onclick = async () => {
try {
+ // Feature-Flags aus Checkboxen lesen
+ const cbFav = $('settingFavorites');
+ const cbRec = $('settingRecent');
+ const cbCompact = $('settingCompact');
+ featureFavorites = cbFav ? cbFav.checked : true;
+ featureRecent = cbRec ? cbRec.checked : true;
+ compactMode = cbCompact ? cbCompact.checked : false;
+ const cbColorIcons2 = $('settingColoredIcons');
+ featureColoredIcons = cbColorIcons2 ? cbColorIcons2.checked : true;
+ document.body.classList.toggle('compact-mode', compactMode);
+
const data = {
githubToken: $('githubToken').value,
giteaToken: $('giteaToken').value,
- giteaURL: $('giteaURL').value
+ giteaURL: $('giteaURL').value,
+ featureFavorites,
+ featureRecent,
+ compactMode,
+ featureColoredIcons,
+ favCollapsedFavorites: favSectionCollapsed.favorites,
+ favCollapsedRecent: favSectionCollapsed.recent
};
await window.electronAPI.saveCredentials(data);
$('settingsModal').classList.add('hidden');
- setStatus('Settings saved');
+ showSuccess('Settings saved');
+ // Ansicht aktualisieren falls Feature-Flags geändert
+ loadGiteaRepos();
} catch (error) {
console.error('Error saving settings:', error);
- setStatus('Save failed');
+ showError('Save failed');
}
};
}
@@ -1777,6 +3046,36 @@ window.addEventListener('DOMContentLoaded', async () => {
$('searchBar').classList.add('hidden');
}
}
+
+ // F2 - Umbenennen
+ if (e.key === 'F2' && lastSelectedItem && !currentActiveTab) {
+ e.preventDefault();
+ if (lastSelectedItem.type === 'gitea') {
+ showGiteaRenameModal(lastSelectedItem.item, lastSelectedItem.owner, lastSelectedItem.repo);
+ } else if (lastSelectedItem.type === 'local') {
+ showLocalRenameModal(lastSelectedItem.node);
+ }
+ }
+
+ // Entf - Löschen mit Bestätigungs-Toast
+ if (e.key === 'Delete' && lastSelectedItem && !currentActiveTab) {
+ e.preventDefault();
+ 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; }
+ else showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
+ });
+ } else if (lastSelectedItem.type === 'local') {
+ const { node } = lastSelectedItem;
+ showDeleteConfirm(`"${node.name}" wirklich löschen?`, async () => {
+ const res = await window.electronAPI.deleteFile({ path: node.path });
+ if (res?.ok) { showSuccess(`"${node.name}" gelöscht`); if (selectedFolder) refreshLocalTree(selectedFolder); lastSelectedItem = null; }
+ else showError('Löschen fehlgeschlagen: ' + (res?.error || ''));
+ });
+ }
+ }
});
// Progress listeners
@@ -1790,6 +3089,7 @@ window.addEventListener('DOMContentLoaded', async () => {
// Setup globalen Drop-Handler für Repo-Ansicht
setupGlobalDropZone();
+ setupBackgroundContextMenu();
setStatus('Ready');
initUpdater(); // Updater initialisieren
@@ -1818,7 +3118,7 @@ async function loadRepoReleases(owner, repo) {
const res = await window.electronAPI.listReleases({ owner, repo });
if (!res.ok) {
- setStatus('Error loading releases: ' + res.error);
+ showError('Error loading releases: ' + res.error);
return;
}
@@ -1877,7 +3177,7 @@ async function loadRepoReleases(owner, repo) {
} catch (error) {
console.error('Error loading releases:', error);
- setStatus('Failed to load releases');
+ showError('Failed to load releases');
}
}
@@ -2317,12 +3617,12 @@ function showCreateReleaseModal(owner, repo) {
} else {
console.error('Asset Upload fehlgeschlagen:', uploadRes.error);
alert(`Release erstellt, aber Asset Upload fehlgeschlagen: ${uploadRes.error}`);
- setStatus('Release erstellt (Upload Fehler)');
+ showWarning('Release erstellt (Upload Fehler)');
}
} catch (uploadErr) {
console.error('Upload error:', uploadErr);
alert('Release erstellt, aber Fehler beim Hochladen der Datei.');
- setStatus('Release erstellt (Upload Fehler)');
+ showWarning('Release erstellt (Upload Fehler)');
} finally {
hideProgress();
}
@@ -2333,12 +3633,12 @@ function showCreateReleaseModal(owner, repo) {
modal.remove();
loadRepoReleases(owner, repo); // Liste neu laden
} else {
- setStatus('Failed: ' + res.error);
+ showError('Failed: ' + res.error);
alert('Fehler beim Erstellen des Releases: ' + res.error);
}
} catch (error) {
console.error('Create release error:', error);
- setStatus('Create failed');
+ showError('Create failed');
alert('Ein unerwarteter Fehler ist aufgetreten.');
}
};
@@ -2382,13 +3682,13 @@ async function showUploadAssetDialog(release) {
// Reload releases to show new asset
loadRepoReleases(currentReleaseView.owner, currentReleaseView.repo);
} else {
- setStatus('Upload failed: ' + uploadRes.error);
+ showError('Upload failed: ' + uploadRes.error);
}
} catch (error) {
console.error('Upload asset error:', error);
hideProgress();
- setStatus('Upload failed');
+ showError('Upload failed');
}
}
@@ -2418,7 +3718,7 @@ function formatBytes(bytes) {
let currentCommitView = {
owner: null,
repo: null,
- branch: 'main',
+ branch: 'HEAD',
commits: [],
selectedCommit: null
};
@@ -2442,7 +3742,7 @@ async function loadCommitHistory(owner, repo, branch = 'main') {
});
if (!res.ok) {
- setStatus('Error loading commits: ' + res.error);
+ showError('Error loading commits: ' + res.error);
return;
}
@@ -2452,7 +3752,7 @@ async function loadCommitHistory(owner, repo, branch = 'main') {
} catch (error) {
console.error('Error loading commit history:', error);
- setStatus('Failed to load commits');
+ showError('Failed to load commits');
}
}
diff --git a/renderer/style.css b/renderer/style.css
index 74bf42b..21e8e11 100644
--- a/renderer/style.css
+++ b/renderer/style.css
@@ -326,6 +326,29 @@ body {
animation: fadeIn var(--transition-slow) ease-out;
}
+/* ── Kompakt-Modus ── */
+body.compact-mode .explorer-grid {
+ grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
+ gap: var(--spacing-sm);
+}
+
+body.compact-mode .item-card {
+ padding: 10px 8px;
+}
+
+body.compact-mode .item-icon {
+ font-size: 32px;
+ margin-bottom: 6px;
+}
+
+body.compact-mode .item-name {
+ font-size: 11px;
+}
+
+body.compact-mode .item-card:hover {
+ transform: translateY(-2px) scale(1.01);
+}
+
@keyframes fadeIn {
from {
opacity: 0;
@@ -389,11 +412,32 @@ body {
box-shadow: 0 0 20px rgba(34, 197, 94, 0.3);
}
+/* Mehrfachauswahl */
+.item-card.selected {
+ border-color: var(--accent-primary);
+ background: rgba(0, 212, 255, 0.12);
+ box-shadow: 0 0 0 2px var(--accent-primary), 0 0 20px rgba(0, 212, 255, 0.2);
+}
+
+.item-card.selected .item-name {
+ color: var(--accent-primary);
+}
+
+/* Commit Quick Buttons */
+.commit-quick-btn:hover {
+ border-color: var(--accent-primary) !important;
+ color: var(--accent-primary) !important;
+}
+
.item-icon {
font-size: 56px;
margin-bottom: var(--spacing-md);
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
transition: transform var(--transition-normal);
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.item-card:hover .item-icon {
@@ -1410,4 +1454,290 @@ progress::-moz-progress-bar {
font-size: 0.9rem;
color: var(--text-secondary);
border: 1px solid rgba(255,255,255,0.05);
+}
+/* =============================================
+ FAVORITEN & ZULETZT GEÖFFNET — Styles
+ ============================================= */
+
+/* Sektions-Überschrift */
+.fav-section-header {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ margin-bottom: 10px;
+ color: var(--text-muted);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 1.5px;
+ text-transform: uppercase;
+}
+
+.fav-section-header--toggle {
+ cursor: pointer;
+ user-select: none;
+ border-radius: var(--radius-sm);
+ padding: 3px 6px 3px 2px;
+ margin-left: -2px;
+ transition: background var(--transition-fast), color var(--transition-fast);
+}
+
+.fav-section-header--toggle:hover {
+ background: rgba(255, 255, 255, 0.06);
+ color: var(--text-primary);
+}
+
+.fav-collapse-arrow {
+ margin-left: auto;
+ font-size: 9px;
+ opacity: 0.5;
+ transition: opacity var(--transition-fast);
+}
+
+.fav-section-header--toggle:hover .fav-collapse-arrow {
+ opacity: 1;
+}
+
+.fav-section-icon {
+ font-size: 13px;
+}
+
+/* Reihe der Chips */
+.fav-chips-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+/* Trennlinie nach den Sektionen */
+.fav-divider {
+ height: 1px;
+ background: rgba(255, 255, 255, 0.07);
+ margin: 4px 0 20px 0;
+}
+
+/* Einzelner Chip */
+.fav-chip {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ padding: 7px 13px 7px 10px;
+ background: var(--bg-tertiary);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 30px;
+ cursor: pointer;
+ transition: border-color var(--transition-fast),
+ background var(--transition-fast),
+ transform var(--transition-fast),
+ box-shadow var(--transition-fast);
+ max-width: 260px;
+ user-select: none;
+}
+
+.fav-chip:hover {
+ border-color: var(--accent-primary);
+ background: rgba(0, 212, 255, 0.06);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 212, 255, 0.12);
+}
+
+.fav-chip--star:hover {
+ border-color: #f59e0b;
+ background: rgba(245, 158, 11, 0.06);
+ box-shadow: 0 4px 12px rgba(245, 158, 11, 0.15);
+}
+
+.fav-chip-icon {
+ font-size: 13px;
+ flex-shrink: 0;
+}
+
+.fav-chip-label {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 160px;
+}
+
+.fav-chip-time {
+ font-size: 11px;
+ color: var(--text-muted);
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+/* Stern-Button auf Repo-Karten */
+.fav-star-btn {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ background: transparent;
+ border: none;
+ font-size: 15px;
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity var(--transition-fast), transform var(--transition-fast);
+ z-index: 5;
+ line-height: 1;
+ padding: 4px;
+ border-radius: 50%;
+ color: inherit;
+}
+
+.item-card:hover .fav-star-btn {
+ opacity: 0.6;
+}
+
+.fav-star-btn:hover,
+.fav-star-btn.active {
+ opacity: 1;
+ transform: scale(1.2);
+}
+
+.fav-star-btn.active {
+ color: #f59e0b;
+}
+
+/* =============================================
+ SETTINGS — Toggle-Switch Styles
+ ============================================= */
+
+.settings-toggle-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ background: rgba(255, 255, 255, 0.03);
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ border-radius: var(--radius-md);
+ margin-bottom: 8px;
+ cursor: pointer;
+ transition: background var(--transition-fast), border-color var(--transition-fast);
+ gap: 16px;
+}
+
+.settings-toggle-row:hover {
+ background: rgba(255, 255, 255, 0.06);
+ border-color: rgba(255, 255, 255, 0.12);
+}
+
+.settings-toggle-info {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.settings-toggle-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.settings-toggle-desc {
+ font-size: 12px;
+ color: var(--text-muted);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: 0;
+}
+
+/* Toggle-Switch */
+.toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 24px;
+ flex-shrink: 0;
+ cursor: pointer;
+}
+
+.toggle-switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ position: absolute;
+}
+
+.toggle-track {
+ position: absolute;
+ inset: 0;
+ background: rgba(255, 255, 255, 0.15);
+ border-radius: 24px;
+ transition: background var(--transition-normal);
+}
+
+.toggle-track::before {
+ content: '';
+ position: absolute;
+ width: 18px;
+ height: 18px;
+ left: 3px;
+ top: 3px;
+ background: #fff;
+ border-radius: 50%;
+ transition: transform var(--transition-normal);
+ box-shadow: 0 1px 4px rgba(0,0,0,0.3);
+}
+
+.toggle-switch input:checked + .toggle-track {
+ background: var(--accent-primary);
+}
+
+.toggle-switch input:checked + .toggle-track::before {
+ transform: translateX(20px);
+}
+/* ===========================
+ FARBIGE DATEI-ICONS
+ =========================== */
+
+.file-type-badge {
+ position: absolute;
+ bottom: -4px;
+ right: -6px;
+ font-size: 9px;
+ font-weight: 800;
+ padding: 1px 4px;
+ border-radius: 4px;
+ letter-spacing: 0.3px;
+ line-height: 14px;
+ pointer-events: none;
+ white-space: nowrap;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.4);
+}
+
+body.compact-mode .file-type-badge {
+ font-size: 8px;
+ padding: 1px 3px;
+}
+
+/* ===========================
+ REPO-GRÖSSE BADGE
+ =========================== */
+.repo-size-badge {
+ font-size: 10px;
+ color: var(--text-muted);
+ margin-top: 4px;
+ opacity: 0.7;
+}
+
+/* ===========================
+ FAVORITEN DRAG-REORDER
+ =========================== */
+.fav-chip--dragging {
+ opacity: 0.4;
+ transform: scale(0.95);
+ cursor: grabbing !important;
+}
+
+.fav-chip--drop-target {
+ border-color: var(--accent-primary) !important;
+ background: rgba(0, 212, 255, 0.12) !important;
+ transform: translateY(-3px);
+ box-shadow: 0 4px 16px rgba(0, 212, 255, 0.25);
+}
+
+.fav-chip[draggable="true"] {
+ cursor: grab;
}
\ No newline at end of file