diff --git a/renderer/index.html b/renderer/index.html
index cf0be02..a632886 100644
--- a/renderer/index.html
+++ b/renderer/index.html
@@ -1,89 +1,169 @@
-
+
+
-
-
-
-
-
-
Branch:
-
Repository Name:
-
-
-
- License:
-
- No License
- MIT License
- Apache License 2.0
- GNU General Public License v3.0
- BSD 3-Clause "New" or "Revised" License
- BSD 2-Clause "Simplified" License
- GNU Lesser General Public License v3.0
- Mozilla Public License 2.0
- The Unlicense
-
-
-
-
-
-
-
- Initialize Repository with a README
-
-
-
-
Create Repo
-
-
-
-
-
Preview
-
Select a file to preview
-
-
-
-
Commit Logs
-
No commits yet.
-
-
-
+
+
+
+
+
+
-
Settings
-
GitHub Token
-
Gitea Token
-
Gitea URL
-
-
Save
-
Close
+
⚙️ Einstellungen
+
+
+ GitHub Token
+
+
+
+
+ Gitea Token
+
+
+
+
+ Gitea URL
+
+
+
+
+ Speichern
+ Abbrechen
+
+
+
+
+
+
+
+
🚀 Neues Repository erstellen
+
+
+ Repository Name
+
+
+
+
+ Target Branch
+
+
+
+
+ Lizenz
+
+ Keine Lizenz
+ MIT License
+ Apache 2.0
+ GPL v3
+ BSD 3-Clause
+ AGPL v3
+
+
+
+
+
+
+ README.md initialisieren
+
+
+
+
+ Lokaler Push-Branch
+
+ main
+
+
+
+
+ Erstellen
+ Abbrechen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ersetzen
+ Alle
+ ✕
+ 0/0
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/renderer/renderer.js b/renderer/renderer.js
index 891e681..733cc26 100644
--- a/renderer/renderer.js
+++ b/renderer/renderer.js
@@ -1,621 +1,2960 @@
-// renderer.js — Explorer with repo drag-export and folder upload+git push + progress UI
+// renderer.js — Grid-UI + Navigation + Drag'n'Drop mit Fehlerbehandlung
const $ = id => document.getElementById(id);
let selectedFolder = null;
-let giteaCache = {}; // cache repo contents
+let giteaCache = {};
-function setStatus(txt) { const s = $('status'); if (s) s.innerText = txt || ''; }
+// Navigations-Status für die Explorer-Ansicht
+let currentState = {
+ view: 'none', // 'local', 'gitea-list', 'gitea-repo'
+ owner: null,
+ repo: null,
+ path: ''
+};
+
+function setStatus(txt) {
+ const s = $('status');
+ if (s) s.innerText = txt || '';
+}
/* -------------------------
- Small dynamic progress UI (created if not present)
+ PROGRESS UI
------------------------- */
function ensureProgressUI() {
- if (document.getElementById('folderProgressContainer')) return;
- const container = document.createElement('div');
- container.id = 'folderProgressContainer';
- container.style.position = 'fixed';
- container.style.left = '50%';
- container.style.top = '12px';
- container.style.transform = 'translateX(-50%)';
- container.style.zIndex = '10000';
- container.style.width = '480px';
- container.style.maxWidth = '90%';
- container.style.padding = '6px';
- container.style.background = 'rgba(20,20,30,0.95)';
- container.style.borderRadius = '8px';
- container.style.boxShadow = '0 6px 18px rgba(0,0,0,0.45)';
- container.style.color = '#fff';
- container.style.fontFamily = 'sans-serif';
- container.style.display = 'none';
+ if ($('folderProgressContainer')) return;
+ const container = document.createElement('div');
+ container.id = 'folderProgressContainer';
+ container.style.cssText = `
+ position: fixed;
+ left: 50%;
+ top: 12px;
+ transform: translateX(-50%);
+ z-index: 10000;
+ width: 480px;
+ max-width: 90%;
+ padding: 12px 16px;
+ background: rgba(20,20,30,0.98);
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.6);
+ color: #fff;
+ font-family: sans-serif;
+ display: none;
+ backdrop-filter: blur(10px);
+ border: 1px solid rgba(0, 212, 255, 0.2);
+ `;
- const text = document.createElement('div');
- text.id = 'folderProgressText';
- text.style.marginBottom = '6px';
- text.style.fontSize = '13px';
- container.appendChild(text);
+ const text = document.createElement('div');
+ text.id = 'folderProgressText';
+ text.style.cssText = `
+ margin-bottom: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ color: #00d4ff;
+ `;
+ container.appendChild(text);
- const barWrap = document.createElement('div');
- barWrap.style.width = '100%';
- barWrap.style.height = '10px';
- barWrap.style.background = '#333';
- barWrap.style.borderRadius = '6px';
- barWrap.style.overflow = 'hidden';
+ const barWrap = document.createElement('div');
+ barWrap.style.cssText = `
+ width: 100%;
+ height: 12px;
+ background: rgba(255,255,255,0.1);
+ border-radius: 6px;
+ overflow: hidden;
+ position: relative;
+ `;
- const bar = document.createElement('div');
- bar.id = 'folderProgressBar';
- bar.style.width = '0%';
- bar.style.height = '100%';
- bar.style.background = '#4caf50';
- bar.style.transition = 'width 150ms linear';
+ const bar = document.createElement('div');
+ bar.id = 'folderProgressBar';
+ bar.style.cssText = `
+ width: 0%;
+ height: 100%;
+ background: linear-gradient(90deg, #00d4ff, #8b5cf6);
+ transition: width 200ms ease-out;
+ border-radius: 6px;
+ `;
- barWrap.appendChild(bar);
- container.appendChild(barWrap);
- document.body.appendChild(container);
+ barWrap.appendChild(bar);
+ container.appendChild(barWrap);
+ document.body.appendChild(container);
}
function showProgress(percent, text) {
- ensureProgressUI();
- const container = document.getElementById('folderProgressContainer');
- const bar = document.getElementById('folderProgressBar');
- const txt = document.getElementById('folderProgressText');
- txt.innerText = text || '';
- bar.style.width = `${percent}%`;
- container.style.display = 'block';
+ ensureProgressUI();
+ const container = $('folderProgressContainer');
+ const bar = $('folderProgressBar');
+ const txt = $('folderProgressText');
+ if (txt) txt.innerText = text || '';
+ if (bar) bar.style.width = `${Math.min(100, Math.max(0, percent))}%`;
+ if (container) container.style.display = 'block';
}
function hideProgress() {
- const container = document.getElementById('folderProgressContainer');
- if (container) container.style.display = 'none';
+ const container = $('folderProgressContainer');
+ if (container) {
+ setTimeout(() => {
+ container.style.display = 'none';
+ }, 500);
+ }
}
/* -------------------------
- Initialization & wiring
+ ADVANCED FILE EDITOR - WITH TABS, UNDO/REDO, AUTO-SAVE, LINE NUMBERS
------------------------- */
-async function init() {
- const creds = await window.electronAPI.loadCredentials();
- if (creds) {
- if ($('githubToken')) $('githubToken').value = creds.githubToken || '';
- if ($('giteaToken')) $('giteaToken').value = creds.giteaToken || '';
- if ($('giteaURL')) $('giteaURL').value = creds.giteaURL || '';
- }
- if ($('btnLoadGiteaRepos')) $('btnLoadGiteaRepos').addEventListener('click', loadGiteaRepos);
- if ($('btnSelectFolder')) $('btnSelectFolder').addEventListener('click', selectLocalFolder);
- if ($('btnPush')) $('btnPush').addEventListener('click', pushLocalFolder);
- if ($('btnCreateRepo')) $('btnCreateRepo').addEventListener('click', createRepoHandler);
+// Editor State
+let openTabs = {}; // { filePath: { name, content, originalContent, dirty, icon, history, historyIndex } }
+let currentActiveTab = null;
+let autoSaveTimer = null;
+let autoSaveInterval = 3000; // 3 sekunden
- if ($('btnSettings')) $('btnSettings').addEventListener('click', () => $('settingsModal').classList.remove('hidden'));
- if ($('btnCloseSettings')) $('btnCloseSettings').addEventListener('click', () => $('settingsModal').classList.add('hidden'));
- if ($('btnSaveSettings')) $('btnSaveSettings').addEventListener('click', saveSettings);
+// Initialize editor
+function initEditor() {
+ const textarea = $('fileEditorContent');
+ if (!textarea) return;
+
+ textarea.addEventListener('input', () => {
+ updateCurrentTab();
+ updateLineNumbers();
+ updateEditorStats();
+ triggerAutoSave();
+ });
+
+ textarea.addEventListener('scroll', () => {
+ const lineNumbers = $('lineNumbers');
+ if (lineNumbers) lineNumbers.scrollTop = textarea.scrollTop;
+ });
+
+ textarea.addEventListener('keydown', (e) => {
+ // Ctrl+Z - Undo
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
+ e.preventDefault();
+ undoChange();
+ }
+ // Ctrl+Shift+Z or Ctrl+Y - Redo
+ if (((e.ctrlKey || e.metaKey) && e.key === 'z' && e.shiftKey) ||
+ ((e.ctrlKey || e.metaKey) && e.key === 'y')) {
+ e.preventDefault();
+ redoChange();
+ }
+ // Tab insertion
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+ textarea.value = textarea.value.substring(0, start) + '\t' + textarea.value.substring(end);
+ textarea.selectionStart = textarea.selectionEnd = start + 1;
+ updateCurrentTab();
+ updateLineNumbers();
+ }
+ });
+}
- // global drag-over/drop: prevent default
- document.addEventListener('dragover', e => e.preventDefault());
- document.addEventListener('drop', e => e.preventDefault());
+// Add new tab
+function addTab(filePath, fileName, content, isGitea = false, owner = null, repo = null) {
+ openTabs[filePath] = {
+ name: fileName,
+ content: content,
+ originalContent: content,
+ dirty: false,
+ icon: getFileIcon(fileName),
+ isGitea,
+ owner,
+ repo,
+ history: [content],
+ historyIndex: 0
+ };
+
+ currentActiveTab = filePath;
+ renderTabs();
+ updateEditor(); // Kann async sein
+}
- // subscribe to progress events
- window.electronAPI.onFolderUploadProgress((payload) => {
- const { processed, total, percent } = payload;
- showProgress(percent, `Upload: ${processed}/${total} (${percent}%)`);
- if (processed >= total) setTimeout(hideProgress, 600);
- });
- window.electronAPI.onFolderDownloadProgress((payload) => {
- const { processed, total, percent } = payload;
- showProgress(percent, `Download: ${processed}/${total} (${percent}%)`);
- if (processed >= total) setTimeout(hideProgress, 600);
- });
+// Remove tab
+function removeTab(filePath) {
+ delete openTabs[filePath];
+
+ if (currentActiveTab === filePath) {
+ const paths = Object.keys(openTabs);
+ currentActiveTab = paths.length > 0 ? paths[0] : null;
+ }
+
+ if (Object.keys(openTabs).length === 0) {
+ closeFileEditor();
+ } else {
+ renderTabs();
+ updateEditor();
+ }
+}
- window.electronAPI.onPushProgress(p => setStatus('Pushing... ' + p + '%'));
- ensureProgressUI();
+// Switch tab
+function switchTab(filePath) {
+ if (openTabs[filePath]) {
+ currentActiveTab = filePath;
+ renderTabs();
+ updateEditor(); // Kann async sein, aber wir warten nicht
+ }
+}
+
+// Render tabs
+function renderTabs() {
+ const tabsContainer = $('fileEditorTabs');
+ if (!tabsContainer) return;
+
+ tabsContainer.innerHTML = '';
+
+ Object.entries(openTabs).forEach(([filePath, tab]) => {
+ const tabEl = document.createElement('div');
+ tabEl.className = `editor-tab ${currentActiveTab === filePath ? 'active' : ''}`;
+
+ const nameEl = document.createElement('div');
+ nameEl.className = 'editor-tab-name';
+ const iconEl = document.createElement('span');
+ iconEl.textContent = tab.icon;
+ const nameSpan = document.createElement('span');
+ nameSpan.textContent = tab.name;
+ if (tab.dirty) {
+ const dirtyEl = document.createElement('span');
+ dirtyEl.className = 'editor-tab-dirty';
+ dirtyEl.textContent = '●';
+ nameEl.appendChild(iconEl);
+ nameEl.appendChild(nameSpan);
+ nameEl.appendChild(dirtyEl);
+ } else {
+ nameEl.appendChild(iconEl);
+ nameEl.appendChild(nameSpan);
+ }
+
+ const closeBtn = document.createElement('button');
+ closeBtn.className = 'editor-tab-close';
+ closeBtn.textContent = '✕';
+ closeBtn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ if (tab.dirty && !confirm(`${tab.name} hat ungespeicherte Änderungen. Wirklich schließen?`)) {
+ return;
+ }
+ removeTab(filePath);
+ });
+
+ tabEl.appendChild(nameEl);
+ tabEl.appendChild(closeBtn);
+ tabEl.addEventListener('click', () => switchTab(filePath));
+
+ tabsContainer.appendChild(tabEl);
+ });
+}
+
+// Update current tab content
+function updateCurrentTab() {
+ if (!currentActiveTab) return;
+
+ const textarea = $('fileEditorContent');
+ const content = textarea.value;
+ const tab = openTabs[currentActiveTab];
+
+ if (!tab) return;
+
+ tab.content = content;
+ tab.dirty = (content !== tab.originalContent);
+
+ renderTabs();
+}
+
+// Update editor display
+async function updateEditor() {
+ if (!currentActiveTab || !openTabs[currentActiveTab]) return;
+
+ const tab = openTabs[currentActiveTab];
+ const textarea = $('fileEditorContent');
+ const imagePreview = $('imagePreview');
+
+ // Prüfe ob es eine Bilddatei ist
+ const isImage = /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(currentActiveTab);
+
+ if (isImage) {
+ // Zeige Bild statt Textarea
+ if (textarea) textarea.classList.add('hidden');
+ if (imagePreview) {
+ imagePreview.classList.remove('hidden');
+
+ let imgSrc = '';
+
+ if (tab.isGitea) {
+ // Gitea-Bild: Lade via API
+ try {
+ const filePath = currentActiveTab.replace(`gitea://${tab.owner}/${tab.repo}/`, '');
+ const response = await window.electronAPI.readGiteaFile({
+ owner: tab.owner,
+ repo: tab.repo,
+ path: filePath,
+ ref: 'main'
+ });
+
+ if (response.ok) {
+ // Content ist Base64 Text, konvertiere zu Data URL
+ const imageData = response.content;
+ const ext = filePath.split('.').pop().toLowerCase();
+ const mimeType = {
+ 'png': 'image/png',
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'gif': 'image/gif',
+ 'webp': 'image/webp',
+ 'svg': 'image/svg+xml'
+ }[ext] || 'image/png';
+
+ imgSrc = `data:${mimeType};base64,${imageData}`;
+ }
+ } catch (error) {
+ console.error('Error loading Gitea image:', error);
+ imagePreview.innerHTML = '
Fehler beim Laden des Bildes
';
+ return;
+ }
+ } else {
+ // Lokale Datei
+ imgSrc = 'file:///' + currentActiveTab.replace(/\\/g, '/');
+ }
+
+ imagePreview.innerHTML = `
Bild konnte nicht geladen werden
';">`;
+ }
+ } else {
+ // Zeige Text Editor
+ if (textarea) {
+ textarea.classList.remove('hidden');
+ textarea.value = tab.content;
+ updateLineNumbers();
+ updateEditorStats();
+ }
+ if (imagePreview) imagePreview.classList.add('hidden');
+ }
+
+ updateTabInfo();
+}
+
+// Update tab info header
+function updateTabInfo() {
+ const tab = openTabs[currentActiveTab];
+ if (!tab) return;
+
+ $('fileEditorName').textContent = tab.name;
+ $('fileEditorIcon').textContent = tab.icon;
+
+ const pathText = tab.isGitea ? `Gitea: ${tab.owner}/${tab.repo}/${currentActiveTab}` : `Pfad: ${currentActiveTab}`;
+ $('fileEditorPath').textContent = pathText;
+
+ const lines = tab.content.split('\n').length;
+ const bytes = new Blob([tab.content]).size;
+ $('fileEditorStats').textContent = `${lines} Zeilen • ${bytes} Bytes`;
+}
+
+// Update line numbers
+function updateLineNumbers() {
+ const textarea = $('fileEditorContent');
+ const lineNumbers = $('lineNumbers');
+ if (!textarea || !lineNumbers) return;
+
+ const lines = textarea.value.split('\n').length;
+ let html = '';
+ for (let i = 1; i <= lines; i++) {
+ html += i + '\n';
+ }
+ lineNumbers.textContent = html;
+ lineNumbers.scrollTop = textarea.scrollTop;
+}
+
+// Update editor stats (cursor position)
+function updateEditorStats() {
+ const textarea = $('fileEditorContent');
+ if (!textarea) return;
+
+ const lines = textarea.value.split('\n').length;
+ const startPos = textarea.selectionStart;
+ const textBeforeCursor = textarea.value.substring(0, startPos);
+ const line = textBeforeCursor.split('\n').length;
+ const col = startPos - textBeforeCursor.lastIndexOf('\n');
+
+ $('fileEditorCursor').textContent = `Zeile ${line}, Spalte ${col}`;
+}
+
+// Undo
+function undoChange() {
+ if (!currentActiveTab) return;
+
+ const tab = openTabs[currentActiveTab];
+ if (tab.historyIndex > 0) {
+ tab.historyIndex--;
+ const textarea = $('fileEditorContent');
+ textarea.value = tab.history[tab.historyIndex];
+ updateCurrentTab();
+ updateLineNumbers();
+ updateEditorStats();
+ }
+}
+
+// Redo
+function redoChange() {
+ if (!currentActiveTab) return;
+
+ const tab = openTabs[currentActiveTab];
+ if (tab.historyIndex < tab.history.length - 1) {
+ tab.historyIndex++;
+ const textarea = $('fileEditorContent');
+ textarea.value = tab.history[tab.historyIndex];
+ updateCurrentTab();
+ updateLineNumbers();
+ updateEditorStats();
+ }
+}
+
+// Push to history
+function pushToHistory(content) {
+ if (!currentActiveTab) return;
+
+ const tab = openTabs[currentActiveTab];
+ // Remove any redo history
+ tab.history = tab.history.slice(0, tab.historyIndex + 1);
+ tab.history.push(content);
+ tab.historyIndex++;
+
+ // Limit history to 50 items
+ if (tab.history.length > 50) {
+ tab.history.shift();
+ tab.historyIndex--;
+ }
+}
+
+// Auto-Save
+function triggerAutoSave() {
+ clearTimeout(autoSaveTimer);
+ autoSaveTimer = setTimeout(() => {
+ saveCurrentFile(true);
+ }, autoSaveInterval);
+}
+
+function showAutoSaveIndicator() {
+ const indicator = $('autoSaveStatus');
+ if (indicator) {
+ indicator.style.display = 'inline';
+ setTimeout(() => {
+ indicator.style.display = 'none';
+ }, 2000);
+ }
+}
+
+function closeFileEditor() {
+ // Überprüfe auf ungespeicherte Änderungen
+ const unsaved = Object.entries(openTabs).filter(([_, tab]) => tab.dirty);
+
+ if (unsaved.length > 0) {
+ if (!confirm(`${unsaved.length} Datei(en) haben ungespeicherte Änderungen. Wirklich schließen?`)) {
+ return;
+ }
+ }
+
+ openTabs = {};
+ currentActiveTab = null;
+ clearTimeout(autoSaveTimer);
+ const modal = $('fileEditorModal');
+ if (modal) modal.classList.add('hidden');
+}
+
+async function openFileEditor(filePath, fileName) {
+ try {
+ console.log('🔍 Opening file:', filePath);
+
+ // Wenn bereits offen, nur switchen
+ if (openTabs[filePath]) {
+ switchTab(filePath);
+ const modal = $('fileEditorModal');
+ if (modal) modal.classList.remove('hidden');
+ return;
+ }
+
+ // Prüfe ob es eine Bilddatei ist
+ const isImage = /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(fileName);
+
+ if (isImage) {
+ // Für Bilder brauchen wir keinen Content zu lesen
+ addTab(filePath, fileName, '', false, null, null);
+ } else {
+ // Lese Text-Datei
+ const response = await window.electronAPI.readFile({ path: filePath });
+
+ if (response.ok) {
+ addTab(filePath, fileName, response.content);
+ } else {
+ alert(`Fehler: ${response.error}`);
+ return;
+ }
+ }
+
+ const modal = $('fileEditorModal');
+ if (modal) {
+ modal.classList.remove('hidden');
+ initEditor();
+ $('fileEditorContent').focus();
+ }
+
+ setStatus(`Editiere: ${fileName}`);
+ console.log('✅ File opened');
+ } catch (error) {
+ console.error('Error opening file:', error);
+ alert('Fehler beim Öffnen der Datei');
+ }
+}
+
+async function openGiteaFileInEditor(owner, repo, filePath, fileName) {
+ try {
+ console.log('🔍 Loading Gitea file:', owner, repo, filePath);
+ setStatus('Lädt Datei...');
+
+ // Wenn bereits offen, nur switchen
+ const vPath = `gitea://${owner}/${repo}/${filePath}`;
+ if (openTabs[vPath]) {
+ switchTab(vPath);
+ const modal = $('fileEditorModal');
+ if (modal) modal.classList.remove('hidden');
+ return;
+ }
+
+ // Lade Datei-Content vom Gitea Handler
+ const response = await window.electronAPI.readGiteaFile({
+ owner,
+ repo,
+ path: filePath,
+ ref: 'main'
+ });
+
+ if (response.ok) {
+ addTab(vPath, fileName, response.content, true, owner, repo);
+
+ const modal = $('fileEditorModal');
+ if (modal) {
+ modal.classList.remove('hidden');
+ initEditor();
+ $('fileEditorContent').focus();
+ }
+
+ setStatus(`Editiere: ${fileName}`);
+ console.log('✅ Gitea file opened');
+ } else {
+ alert(`Fehler: ${response.error}`);
+ setStatus('Fehler beim Laden der Datei');
+ }
+ } catch (error) {
+ console.error('Error opening Gitea file:', error);
+ alert('Fehler beim Öffnen der Datei');
+ setStatus('Fehler');
+ }
+}
+
+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] || '📄';
}
/* -------------------------
- Settings / buttons
+ SEARCH & REPLACE
------------------------- */
-async function saveSettings() {
- const data = { githubToken: $('githubToken')?.value, giteaToken: $('giteaToken')?.value, giteaURL: $('giteaURL')?.value };
- const r = await window.electronAPI.saveCredentials(data);
- setStatus(r.ok ? 'Settings saved' : 'Save failed: ' + r.error);
- $('settingsModal').classList.add('hidden');
+function toggleSearch() {
+ const searchBar = $('searchBar');
+ if (searchBar.classList.contains('hidden')) {
+ searchBar.classList.remove('hidden');
+ $('searchInput').focus();
+ } else {
+ searchBar.classList.add('hidden');
+ }
}
+function performSearch() {
+ const searchTerm = $('searchInput').value;
+ const textarea = $('fileEditorContent');
+
+ if (!searchTerm || !textarea) return;
+
+ const text = textarea.value;
+ const regex = new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
+ const matches = [...text.matchAll(regex)];
+
+ $('searchInfo').textContent = matches.length > 0 ? `${matches.length} gefunden` : '0 gefunden';
+
+ if (matches.length > 0) {
+ const firstMatch = matches[0];
+ textarea.setSelectionRange(firstMatch.index, firstMatch.index + firstMatch[0].length);
+ textarea.focus();
+ }
+}
+
+function replaceOnce() {
+ const searchTerm = $('searchInput').value;
+ const replaceTerm = $('replaceInput').value;
+ const textarea = $('fileEditorContent');
+
+ if (!searchTerm || !textarea) return;
+
+ const text = textarea.value;
+ const newText = text.replace(new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'), replaceTerm);
+
+ textarea.value = newText;
+ pushToHistory(newText);
+ updateCurrentTab();
+ updateLineNumbers();
+ performSearch();
+}
+
+function replaceAll() {
+ const searchTerm = $('searchInput').value;
+ const replaceTerm = $('replaceInput').value;
+ const textarea = $('fileEditorContent');
+
+ if (!searchTerm || !textarea) return;
+
+ const text = textarea.value;
+ const newText = text.replace(new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), replaceTerm);
+
+ textarea.value = newText;
+ pushToHistory(newText);
+ updateCurrentTab();
+ updateLineNumbers();
+ performSearch();
+}
+
+async function saveCurrentFile(isAutoSave = false) {
+ if (!currentActiveTab) return;
+
+ const tab = openTabs[currentActiveTab];
+
+ // Prüfe ob es eine Bilddatei ist
+ if (/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(currentActiveTab)) {
+ alert('Bilder können nicht bearbeitet werden');
+ return;
+ }
+
+ const textarea = $('fileEditorContent');
+ const content = textarea.value;
+
+ if (!isAutoSave) setStatus('Speichert...');
+
+ try {
+ let response;
+
+ // Prüfe ob es eine Gitea-Datei ist
+ if (tab.isGitea) {
+ response = await window.electronAPI.writeGiteaFile({
+ owner: tab.owner,
+ repo: tab.repo,
+ path: currentActiveTab.replace(`gitea://${tab.owner}/${tab.repo}/`, ''),
+ content: content,
+ ref: 'main'
+ });
+ } else {
+ // Lokale Datei
+ response = await window.electronAPI.writeFile({
+ path: currentActiveTab,
+ content: content
+ });
+ }
+
+ if (response.ok) {
+ tab.originalContent = content;
+ tab.dirty = false;
+ // Push current state to history
+ pushToHistory(content);
+ renderTabs();
+
+ if (isAutoSave) {
+ showAutoSaveIndicator();
+ } else {
+ setStatus(`✓ Gespeichert: ${tab.name}`);
+ }
+ console.log('✅ File saved');
+ } else {
+ alert(`Fehler: ${response.error}`);
+ }
+ } catch (error) {
+ console.error('Error saving file:', error);
+ alert('Fehler beim Speichern');
+ }
+}
+
+function updateEditorStats() {
+ const textarea = $('fileEditorContent');
+ if (!textarea) return;
+
+ const lines = textarea.value.split('\n').length;
+ const startPos = textarea.selectionStart;
+ const textBeforeCursor = textarea.value.substring(0, startPos);
+ const line = textBeforeCursor.split('\n').length;
+ const col = startPos - textBeforeCursor.lastIndexOf('\n');
+
+ $('fileEditorCursor').textContent = `Zeile ${line}, Spalte ${col}`;
+}
+
+/* -------------------------
+ MARKDOWN PARSER
+ ------------------------- */
+function parseMarkdownToHTML(markdown) {
+ if (!markdown) return '';
+
+ let html = markdown;
+
+ // Check if content already contains HTML (starts with < or has closing tags)
+ const hasHTML = /<[a-zA-Z][\s\S]*>/.test(html);
+
+ if (!hasHTML) {
+ // Only escape and parse if no HTML present
+ html = html.replace(/&/g, '&')
+ .replace(//g, '>');
+ }
+
+ // Convert markdown patterns
+ // Headings: ### Title →