fix Coding/UI
Dieser Commit ist enthalten in:
committet von
Server Deploy
Ursprung
671aaadc26
Commit
99a6b7437b
@ -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
|
||||
================================================================================
|
||||
|
||||
BIN
data/taskmate.db
BIN
data/taskmate.db
Binäre Datei nicht angezeigt.
@ -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);
|
||||
}
|
||||
|
||||
@ -357,6 +357,14 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<button id="btn-toggle-layout" class="btn btn-icon" title="Layout umschalten">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="3" y="14" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="btn-clear-filters" class="btn btn-text">Filter zurücksetzen</button>
|
||||
<button id="btn-show-archived" class="btn btn-text">Archiv anzeigen</button>
|
||||
</div>
|
||||
@ -1696,7 +1704,11 @@
|
||||
<div class="form-group">
|
||||
<label for="coding-name">Anwendungsname *</label>
|
||||
<input type="text" id="coding-name" class="form-control" placeholder="z.B. TaskMate" required>
|
||||
<small class="form-hint" id="coding-path-hint">Ordner: /home/claude-dev/<span id="coding-path-preview">...</span></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="coding-path">Ordnerpfad *</label>
|
||||
<input type="text" id="coding-path" class="form-control" placeholder="/home/claude-dev/TaskMate" required>
|
||||
<small class="form-hint">Vollständiger Pfad zum Projekt-Ordner</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="coding-description">Beschreibung</label>
|
||||
|
||||
@ -26,6 +26,9 @@ class BoardManager {
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =====================
|
||||
@ -156,6 +181,9 @@ class BoardManager {
|
||||
const columns = store.get('columns');
|
||||
clearElement(this.boardElement);
|
||||
|
||||
// Apply layout class
|
||||
this.applyLayoutClass();
|
||||
|
||||
columns.forEach(column => {
|
||||
const columnElement = this.createColumnElement(column);
|
||||
this.boardElement.appendChild(columnElement);
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren