diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9e241a1..ebb126d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,41 @@ TASKMATE - CHANGELOG ==================== +================================================================================ +11.01.2025 - FEATURE: Coding-Modul - Freie Pfad-Eingabe +================================================================================ + +## ÄNDERUNG +Ordnerpfad für Coding-Anwendungen kann jetzt frei definiert werden + +## IMPLEMENTIERUNG +✅ Neues Eingabefeld für Ordnerpfad im Modal +✅ Pfad wird nicht mehr automatisch aus Namen generiert +✅ Volle Flexibilität für beliebige Pfadangaben +✅ Validierung auf leeren Pfad + +================================================================================ +10.01.2026 - FEATURE: Optionales zweispaltiges Layout für Aufgabenansicht +================================================================================ + +## NEUE FUNKTION +Möglichkeit, Aufgaben in der Board-Ansicht mehrspaltig anzuzeigen + +## IMPLEMENTIERUNG +✅ Toggle-Button in der Filter-Leiste hinzugefügt +✅ Dynamisches Grid-Layout: Spalten erweitern sich nur bei Bedarf +✅ Automatische Erkennung wenn Scrollen nötig wäre +✅ Layout-Präferenz wird in localStorage gespeichert +✅ 2 Spalten wenn Inhalt > 120% der Höhe (ab 1400px Breite) +✅ 3 Spalten wenn Inhalt > 250% der Höhe (ab 1800px Breite) +✅ Spalten-Breite passt sich dynamisch an +✅ Cache-Version erhöht auf 303 + +## BEDIENUNG +- Button mit Raster-Icon in der Filter-Leiste schaltet Layout um +- Layout bleibt über Browser-Sessions erhalten +- Aufgaben werden bei aktivem Layout nebeneinander statt untereinander angezeigt + ================================================================================ 10.01.2026 - BUGFIX: Download von Dateianhängen funktioniert wieder ================================================================================ diff --git a/data/taskmate.db b/data/taskmate.db index bb1c53d..4fe697e 100644 Binary files a/data/taskmate.db and b/data/taskmate.db differ diff --git a/frontend/css/board.css b/frontend/css/board.css index 5720a5d..0c6c714 100644 --- a/frontend/css/board.css +++ b/frontend/css/board.css @@ -648,6 +648,7 @@ min-height: 0; overflow-x: auto; padding: var(--spacing-4); + align-items: flex-start; } /* Column */ @@ -662,6 +663,7 @@ border-radius: var(--radius-xl); box-shadow: var(--shadow-sm); flex-shrink: 0; + transition: width var(--transition-default), min-width var(--transition-default); } .column-header { @@ -1259,3 +1261,90 @@ font-size: var(--text-2xs); flex-shrink: 0; } + +/* ======================================== + MULTI-COLUMN LAYOUT + ======================================== */ + +/* Standard Layout (einspalltig) */ +.board .column-body { + flex: 1; + padding: var(--spacing-2); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--spacing-2); +} + +/* Base Multi-Column Layout - aktiviert das Feature, aber zeigt noch einspaltig */ +.board.multi-column-layout .column-body { + /* Bleibt erstmal bei flex layout bis Inhalt zu lang wird */ + display: flex; + flex-direction: column; + gap: var(--spacing-2); +} + +/* Dynamisch aktivierte 2-spaltige Ansicht (wenn Scrollen nötig wäre) */ +.board.multi-column-layout .column-body.dynamic-2-columns { + display: grid; + grid-template-columns: 1fr 1fr; + grid-auto-flow: row; + gap: var(--spacing-2); + align-content: start; + overflow-x: hidden; + overflow-y: auto; +} + +/* Dynamisch aktivierte 3-spaltige Ansicht (wenn viel Inhalt) */ +.board.multi-column-layout .column-body.dynamic-3-columns { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-auto-flow: row; + gap: var(--spacing-2); + align-content: start; + overflow-x: hidden; + overflow-y: auto; +} + +/* Spalten-Breite wenn erweitert */ +.board.multi-column-layout .column { + transition: width var(--transition-default), min-width var(--transition-default); +} + +.board.multi-column-layout .column.expanded-2x { + width: auto; + min-width: 560px; + max-width: 640px; +} + +.board.multi-column-layout .column.expanded-3x { + width: auto; + min-width: 840px; + max-width: 960px; +} + +/* Task Cards im Multi-Column Layout */ +.board.multi-column-layout .task-card { + width: 100%; + box-sizing: border-box; +} + +/* Hover-Effekt für Layout-Toggle Button */ +#btn-toggle-layout { + transition: all var(--transition-fast); +} + +#btn-toggle-layout:hover { + transform: rotate(90deg); +} + +/* Active state indicator - korrigiert für richtige Selektion */ +.view-board.active .board.multi-column-layout ~ * { + /* Dummy rule to ensure the layout class is applied */ +} + +/* Layout toggle button active state */ +#btn-toggle-layout.active { + color: var(--primary); + background: var(--primary-light); +} diff --git a/frontend/index.html b/frontend/index.html index 6f8c463..af53c46 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -357,6 +357,14 @@
+
@@ -1696,7 +1704,11 @@
- Ordner: /home/claude-dev/... +
+
+ + + Vollständiger Pfad zum Projekt-Ordner
diff --git a/frontend/js/board.js b/frontend/js/board.js index 19ac77b..8f93f0e 100644 --- a/frontend/js/board.js +++ b/frontend/js/board.js @@ -25,6 +25,9 @@ class BoardManager { // Week strip calendar this.weekStripDate = this.getMonday(new Date()); this.tooltip = null; + + // Layout preferences + this.multiColumnLayout = this.loadLayoutPreference(); this.init(); } @@ -52,6 +55,21 @@ class BoardManager { this.loadStats(); this.renderWeekStrip(); }); + + // Apply initial layout preference + setTimeout(() => { + this.applyLayoutClass(); + if (this.multiColumnLayout) { + this.checkAndApplyDynamicLayout(); + } + }, 100); + + // Re-check layout on window resize + window.addEventListener('resize', debounce(() => { + if (this.multiColumnLayout) { + this.checkAndApplyDynamicLayout(); + } + }, 250)); } // ===================== @@ -144,6 +162,13 @@ class BoardManager { // Keyboard navigation document.addEventListener('keydown', (e) => this.handleKeyboard(e)); + + // Layout toggle button - use delegated event handling + document.addEventListener('click', (e) => { + if (e.target.closest('#btn-toggle-layout')) { + this.toggleLayout(); + } + }); } // ===================== @@ -155,6 +180,9 @@ class BoardManager { const columns = store.get('columns'); clearElement(this.boardElement); + + // Apply layout class + this.applyLayoutClass(); columns.forEach(column => { const columnElement = this.createColumnElement(column); @@ -170,6 +198,9 @@ class BoardManager { 'Statuskarte hinzufügen' ]); this.boardElement.appendChild(addColumnBtn); + + // Check dynamic layout after render + setTimeout(() => this.checkAndApplyDynamicLayout(), 100); } createColumnElement(column) { @@ -475,6 +506,9 @@ class BoardManager { countElement.textContent = filteredTasks.length.toString(); } }); + + // Check if dynamic layout adjustment is needed + setTimeout(() => this.checkAndApplyDynamicLayout(), 100); } // ===================== @@ -1461,6 +1495,103 @@ class BoardManager { div.textContent = str; return div.innerHTML; } + + // ===================== + // LAYOUT PREFERENCES + // ===================== + + loadLayoutPreference() { + const stored = localStorage.getItem('taskmate:boardLayout'); + return stored === 'multiColumn'; + } + + saveLayoutPreference(multiColumn) { + localStorage.setItem('taskmate:boardLayout', multiColumn ? 'multiColumn' : 'single'); + } + + toggleLayout() { + this.multiColumnLayout = !this.multiColumnLayout; + this.saveLayoutPreference(this.multiColumnLayout); + this.applyLayoutClass(); + this.checkAndApplyDynamicLayout(); + + this.showSuccess(this.multiColumnLayout + ? 'Mehrspalten-Layout aktiviert' + : 'Einspalten-Layout aktiviert' + ); + } + + applyLayoutClass() { + const toggleBtn = $('#btn-toggle-layout'); + + if (this.multiColumnLayout) { + this.boardElement?.classList.add('multi-column-layout'); + toggleBtn?.classList.add('active'); + } else { + this.boardElement?.classList.remove('multi-column-layout'); + toggleBtn?.classList.remove('active'); + + // Remove all dynamic classes when disabled + const columns = this.boardElement?.querySelectorAll('.column'); + columns?.forEach(column => { + const columnBody = column.querySelector('.column-body'); + columnBody?.classList.remove('dynamic-2-columns', 'dynamic-3-columns'); + column.classList.remove('expanded-2x', 'expanded-3x'); + }); + } + } + + checkAndApplyDynamicLayout() { + if (!this.multiColumnLayout || !this.boardElement) return; + + // Debug logging + console.log('[Layout Check] Checking dynamic layout...'); + + // Check each column to see if scrolling is needed + const columns = this.boardElement.querySelectorAll('.column'); + + columns.forEach(column => { + const columnBody = column.querySelector('.column-body'); + if (!columnBody) return; + + // Remove dynamic classes first + columnBody.classList.remove('dynamic-2-columns', 'dynamic-3-columns'); + column.classList.remove('expanded-2x', 'expanded-3x'); + + // Force reflow to get accurate measurements + columnBody.offsetHeight; + + const scrollHeight = columnBody.scrollHeight; + const clientHeight = columnBody.clientHeight; + const hasOverflow = scrollHeight > clientHeight; + + console.log('[Layout Check]', { + column: column.dataset.columnId, + scrollHeight, + clientHeight, + hasOverflow, + ratio: scrollHeight / clientHeight + }); + + // Check if content overflows + if (hasOverflow && clientHeight > 0) { + // Calculate how many columns we need based on content height + const ratio = scrollHeight / clientHeight; + + if (ratio > 2.5 && window.innerWidth >= 1800) { + // Need 3 columns + console.log('[Layout] Applying 3 columns'); + columnBody.classList.add('dynamic-3-columns'); + column.classList.add('expanded-3x'); + } else if (ratio > 1.1 && window.innerWidth >= 1400) { + // Need 2 columns (reduced threshold to 1.1) + console.log('[Layout] Applying 2 columns'); + columnBody.classList.add('dynamic-2-columns'); + column.classList.add('expanded-2x'); + } + } + }); + } } // Create and export singleton diff --git a/frontend/js/coding.js b/frontend/js/coding.js index b48ff09..770f71f 100644 --- a/frontend/js/coding.js +++ b/frontend/js/coding.js @@ -83,11 +83,8 @@ class CodingManager { // Farb-Presets this.renderColorPresets(); - // Name-Eingabe für Pfad-Preview + // Name-Eingabe (kein Pfad-Preview mehr nötig) const nameInput = document.getElementById('coding-name'); - if (nameInput) { - nameInput.addEventListener('input', () => this.updatePathPreview()); - } // CLAUDE.md Link Event const claudeLink = document.getElementById('coding-claude-link'); @@ -165,17 +162,6 @@ class CodingManager { }); } - /** - * Pfad-Preview aktualisieren - */ - updatePathPreview() { - const nameInput = document.getElementById('coding-name'); - const preview = document.getElementById('coding-path-preview'); - if (!nameInput || !preview) return; - - const name = nameInput.value.trim(); - preview.textContent = name || '...'; - } // switchClaudeTab entfernt - CLAUDE.md ist jetzt nur readonly @@ -602,6 +588,7 @@ class CodingManager { // Felder füllen document.getElementById('coding-name').value = directory?.name || ''; + document.getElementById('coding-path').value = directory?.localPath || ''; document.getElementById('coding-description').value = directory?.description || ''; document.getElementById('coding-branch').value = directory?.defaultBranch || 'main'; @@ -609,8 +596,6 @@ class CodingManager { const claudeContent = directory?.claudeMdFromDisk || ''; this.updateClaudeLink(claudeContent, directory?.name); - // Pfad-Preview aktualisieren - this.updatePathPreview(); // Farbe setzen const color = directory?.color || '#4F46E5'; @@ -663,13 +648,11 @@ class CodingManager { */ async handleSave() { const name = document.getElementById('coding-name').value.trim(); + const localPath = document.getElementById('coding-path').value.trim(); const description = document.getElementById('coding-description').value.trim(); // CLAUDE.md wird nicht mehr gespeichert - nur readonly const defaultBranch = document.getElementById('coding-branch').value.trim() || 'main'; - // Pfad automatisch aus Name generieren - const localPath = `${BASE_PATH}/${name}`; - // Farbe ermitteln const selectedPreset = document.querySelector('.color-preset.selected'); const color = selectedPreset?.dataset.color || document.getElementById('coding-color-custom').value; @@ -685,6 +668,11 @@ class CodingManager { return; } + if (!localPath) { + showToast('Ordnerpfad ist erforderlich', 'error'); + return; + } + const data = { name, localPath, diff --git a/frontend/sw.js b/frontend/sw.js index 1175f6a..fba1440 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -4,7 +4,7 @@ * Offline support and caching */ -const CACHE_VERSION = '300'; +const CACHE_VERSION = '306'; const CACHE_NAME = 'taskmate-v' + CACHE_VERSION; const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION; const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;