Statuskarten via Drag&Drop verschiebbar
Unteraufgaben lassen sich verschieben und bearbeiten
Dieser Commit ist enthalten in:
@ -1,6 +1,59 @@
|
||||
TASKMATE - CHANGELOG
|
||||
====================
|
||||
|
||||
================================================================================
|
||||
30.12.2025 - Checklisten-Unteraufgaben: Drag & Drop und Bearbeiten
|
||||
================================================================================
|
||||
|
||||
FEATURE: UNTERAUFGABEN REIHENFOLGE ÄNDERN (DRAG & DROP)
|
||||
--------------------------------------------------------------------------------
|
||||
- Drag-Handle (⋮⋮) links neben jeder Unteraufgabe
|
||||
- Visueller Indikator beim Ziehen (farbiger Rand oben/unten)
|
||||
- Reihenfolge wird in der Datenbank gespeichert (position-Feld)
|
||||
|
||||
FEATURE: UNTERAUFGABEN BEARBEITEN
|
||||
--------------------------------------------------------------------------------
|
||||
- Doppelklick auf den Titel zum Bearbeiten
|
||||
- Bearbeiten-Icon (✎) erscheint beim Hover
|
||||
- Enter speichert, Escape bricht ab
|
||||
- Änderungen werden sofort in der Datenbank gespeichert
|
||||
|
||||
BETROFFENE DATEIEN
|
||||
--------------------------------------------------------------------------------
|
||||
- frontend/js/task-modal.js: renderSubtasks() erweitert, Drag & Drop Events,
|
||||
startEditSubtask() Methode hinzugefügt
|
||||
- frontend/css/modal.css: Styles für Drag-Handle, Drag-Indikatoren, Edit-Button,
|
||||
Edit-Input
|
||||
- frontend/sw.js: Cache-Version auf 138 erhöht
|
||||
|
||||
|
||||
================================================================================
|
||||
30.12.2025 - Bugfix: Statuskarten Drag & Drop Reihenfolge
|
||||
================================================================================
|
||||
|
||||
BUGFIX & VERBESSERUNG: STATUSKARTEN DRAG & DROP
|
||||
--------------------------------------------------------------------------------
|
||||
- Drag & Drop von Statuskarten funktionierte nicht korrekt
|
||||
- Ursache 1: API-Aufruf mit falschen Parametern (Array statt columnId + Position)
|
||||
- Ursache 2: Kein visuelles Feedback beim Ziehen (CSS fehlte)
|
||||
- Ursache 3: DragLeave entfernte Indikatoren zu früh
|
||||
|
||||
ÄNDERUNGEN
|
||||
--------------------------------------------------------------------------------
|
||||
- board.js: reorderColumns() übergibt jetzt korrekt moved.id und toIndex
|
||||
- board.js: handleDragOver() nutzt jetzt CSS-Klassen statt inline-styles
|
||||
- board.js: handleDragLeave() prüft jetzt ob Spalte wirklich verlassen wird
|
||||
- board.js: handleDrop() berechnet Position basierend auf Maus-Position (links/rechts)
|
||||
- board.css: Neue Styles für .column.dragging (Opacity, Scale, Shadow)
|
||||
- board.css: Neue Styles für .column.drag-over-left/right (farbiger Rand)
|
||||
|
||||
BETROFFENE DATEIEN
|
||||
--------------------------------------------------------------------------------
|
||||
- frontend/js/board.js: Drag & Drop Logik komplett überarbeitet
|
||||
- frontend/css/board.css: Column Dragging Styles hinzugefügt
|
||||
- frontend/sw.js: Cache-Version auf 135 erhöht
|
||||
|
||||
|
||||
================================================================================
|
||||
30.12.2025 - Browser-Upload: Lokale Verzeichnisse ins Gitea pushen
|
||||
================================================================================
|
||||
|
||||
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
BIN
data/taskmate.db
BIN
data/taskmate.db
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
@ -609,12 +609,19 @@
|
||||
border-bottom: none;
|
||||
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.column-header:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.column-header.dragging,
|
||||
.column-header[draggable="true"]:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.column-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -750,6 +757,24 @@
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
|
||||
/* Column Dragging */
|
||||
.column.dragging {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.98);
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.column.drag-over-left {
|
||||
border-left: 4px solid var(--primary) !important;
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
.column.drag-over-right {
|
||||
border-right: 4px solid var(--primary) !important;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
.task-card.overdue {
|
||||
border-left: 4px solid var(--error);
|
||||
}
|
||||
|
||||
@ -270,10 +270,44 @@
|
||||
.subtask-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-3);
|
||||
padding: var(--spacing-3);
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: var(--radius-lg);
|
||||
transition: all var(--transition-fast);
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.subtask-item.dragging {
|
||||
opacity: 0.5;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.subtask-item.drag-over-top {
|
||||
border-top-color: var(--primary);
|
||||
}
|
||||
|
||||
.subtask-item.drag-over-bottom {
|
||||
border-bottom-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Drag Handle */
|
||||
.subtask-drag-handle {
|
||||
cursor: grab;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px 4px;
|
||||
user-select: none;
|
||||
opacity: 0.5;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.subtask-item:hover .subtask-drag-handle {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.subtask-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.subtask-item input[type="checkbox"] {
|
||||
@ -299,19 +333,33 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Subtask Actions */
|
||||
.subtask-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-1);
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.subtask-item:hover .subtask-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.subtask-edit,
|
||||
.subtask-delete {
|
||||
padding: 4px;
|
||||
padding: 4px 6px;
|
||||
color: var(--text-muted);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all var(--transition-fast);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.subtask-item:hover .subtask-delete {
|
||||
opacity: 1;
|
||||
.subtask-edit:hover {
|
||||
color: var(--primary);
|
||||
background: var(--primary-light);
|
||||
}
|
||||
|
||||
.subtask-delete:hover {
|
||||
@ -319,6 +367,17 @@
|
||||
background: var(--error-bg);
|
||||
}
|
||||
|
||||
/* Edit Input */
|
||||
.subtask-edit-input {
|
||||
flex: 1;
|
||||
padding: var(--spacing-1) var(--spacing-2);
|
||||
font-size: var(--text-sm);
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: var(--radius-md);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Add Subtask Form */
|
||||
.add-subtask-form {
|
||||
display: flex;
|
||||
@ -333,6 +392,7 @@
|
||||
flex: 1;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
|
||||
@ -179,12 +179,14 @@ class BoardManager {
|
||||
|
||||
const columnEl = createElement('div', {
|
||||
className: 'column',
|
||||
dataset: { columnId: column.id },
|
||||
draggable: 'true'
|
||||
dataset: { columnId: column.id }
|
||||
});
|
||||
|
||||
// Header with column color
|
||||
const header = createElement('div', { className: 'column-header' });
|
||||
// Header with column color - draggable
|
||||
const header = createElement('div', {
|
||||
className: 'column-header',
|
||||
draggable: 'true'
|
||||
});
|
||||
|
||||
// Apply column color to header background
|
||||
const columnColor = column.color || '#6B7280';
|
||||
@ -536,6 +538,7 @@ class BoardManager {
|
||||
|
||||
handleDragStart(e) {
|
||||
const taskCard = e.target.closest('.task-card');
|
||||
const header = e.target.closest('.column-header');
|
||||
const column = e.target.closest('.column');
|
||||
|
||||
if (taskCard) {
|
||||
@ -548,7 +551,8 @@ class BoardManager {
|
||||
type: 'task',
|
||||
taskId: parseInt(taskCard.dataset.taskId)
|
||||
});
|
||||
} else if (column && e.target.closest('.column-header')) {
|
||||
} else if (header && column) {
|
||||
// Header wird gezogen -> ganze Spalte verschieben
|
||||
this.draggedColumn = column;
|
||||
column.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
@ -575,6 +579,10 @@ class BoardManager {
|
||||
// Remove all drop indicators
|
||||
$$('.column-body.drag-over').forEach(el => el.classList.remove('drag-over'));
|
||||
$$('.drop-indicator').forEach(el => el.remove());
|
||||
// Remove column drag indicators
|
||||
$$('.column.drag-over-left, .column.drag-over-right').forEach(el => {
|
||||
el.classList.remove('drag-over-left', 'drag-over-right');
|
||||
});
|
||||
|
||||
store.setDragState(null);
|
||||
}
|
||||
@ -597,32 +605,44 @@ class BoardManager {
|
||||
}
|
||||
} else if (dragState.type === 'column') {
|
||||
const column = e.target.closest('.column');
|
||||
|
||||
// Entferne alle vorherigen Drag-Over-Klassen
|
||||
$$('.column.drag-over-left, .column.drag-over-right').forEach(col => {
|
||||
if (col !== column) {
|
||||
col.classList.remove('drag-over-left', 'drag-over-right');
|
||||
}
|
||||
});
|
||||
|
||||
if (column && column !== this.draggedColumn) {
|
||||
const rect = column.getBoundingClientRect();
|
||||
const midpoint = rect.left + rect.width / 2;
|
||||
|
||||
if (e.clientX < midpoint) {
|
||||
column.style.borderLeft = '3px solid var(--accent)';
|
||||
column.style.borderRight = '';
|
||||
column.classList.add('drag-over-left');
|
||||
column.classList.remove('drag-over-right');
|
||||
} else {
|
||||
column.style.borderRight = '3px solid var(--accent)';
|
||||
column.style.borderLeft = '';
|
||||
column.classList.add('drag-over-right');
|
||||
column.classList.remove('drag-over-left');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleDragLeave(e) {
|
||||
const dragState = store.get('dragState');
|
||||
|
||||
if (dragState?.type === 'task') {
|
||||
const columnBody = e.target.closest('.column-body');
|
||||
if (columnBody && !columnBody.contains(e.relatedTarget)) {
|
||||
columnBody.classList.remove('drag-over');
|
||||
$$('.drop-indicator', columnBody).forEach(el => el.remove());
|
||||
}
|
||||
|
||||
} else if (dragState?.type === 'column') {
|
||||
const column = e.target.closest('.column');
|
||||
if (column) {
|
||||
column.style.borderLeft = '';
|
||||
column.style.borderRight = '';
|
||||
// Nur entfernen wenn wirklich die Spalte verlassen wird
|
||||
if (column && !column.contains(e.relatedTarget)) {
|
||||
column.classList.remove('drag-over-left', 'drag-over-right');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -664,18 +684,45 @@ class BoardManager {
|
||||
this.moveTask(taskId, columnId, position);
|
||||
} else if (dragState.type === 'column') {
|
||||
const targetColumn = e.target.closest('.column');
|
||||
if (!targetColumn || targetColumn === this.draggedColumn) return;
|
||||
|
||||
if (!targetColumn || targetColumn === this.draggedColumn) {
|
||||
// Cleanup
|
||||
$$('.column.drag-over-left, .column.drag-over-right').forEach(el => {
|
||||
el.classList.remove('drag-over-left', 'drag-over-right');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = store.get('columns');
|
||||
const fromIndex = columns.findIndex(c => c.id === dragState.columnId);
|
||||
const toIndex = columns.findIndex(c => c.id === parseInt(targetColumn.dataset.columnId));
|
||||
let toIndex = columns.findIndex(c => c.id === parseInt(targetColumn.dataset.columnId));
|
||||
|
||||
if (fromIndex !== -1 && toIndex !== -1) {
|
||||
// Berechne Position basierend auf Maus-Position (links oder rechts der Ziel-Spalte)
|
||||
const rect = targetColumn.getBoundingClientRect();
|
||||
const midpoint = rect.left + rect.width / 2;
|
||||
const dropOnRight = e.clientX > midpoint;
|
||||
|
||||
// Wenn rechts gedroppt und von links kommend, nach rechts verschieben
|
||||
if (dropOnRight && fromIndex < toIndex) {
|
||||
// toIndex bleibt gleich (Position nach der Ziel-Spalte)
|
||||
} else if (dropOnRight && fromIndex > toIndex) {
|
||||
// Von rechts kommend, rechts droppend -> nach der Ziel-Spalte
|
||||
toIndex = toIndex + 1;
|
||||
} else if (!dropOnRight && fromIndex > toIndex) {
|
||||
// toIndex bleibt gleich (Position vor der Ziel-Spalte)
|
||||
} else if (!dropOnRight && fromIndex < toIndex) {
|
||||
// Von links kommend, links droppend -> vor der Ziel-Spalte
|
||||
toIndex = toIndex - 1;
|
||||
}
|
||||
|
||||
if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) {
|
||||
this.reorderColumns(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
targetColumn.style.borderLeft = '';
|
||||
targetColumn.style.borderRight = '';
|
||||
// Cleanup
|
||||
$$('.column.drag-over-left, .column.drag-over-right').forEach(el => {
|
||||
el.classList.remove('drag-over-left', 'drag-over-right');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -948,7 +995,8 @@ class BoardManager {
|
||||
store.reorderColumns(columnIds);
|
||||
|
||||
try {
|
||||
await api.reorderColumns(projectId, columnIds);
|
||||
// API erwartet: columnId und newPosition
|
||||
await api.reorderColumns(projectId, moved.id, toIndex);
|
||||
syncManager.notifyColumnsReordered(columnIds);
|
||||
} catch (error) {
|
||||
console.error('Failed to reorder columns:', error);
|
||||
|
||||
@ -909,26 +909,54 @@ class TaskModalManager {
|
||||
|
||||
clearElement(container);
|
||||
|
||||
this.subtasks.forEach(subtask => {
|
||||
this.subtasks.forEach((subtask, index) => {
|
||||
const item = createElement('div', {
|
||||
className: `subtask-item ${subtask.completed ? 'completed' : ''}`,
|
||||
dataset: { subtaskId: subtask.id }
|
||||
dataset: { subtaskId: subtask.id, position: index },
|
||||
draggable: 'true'
|
||||
}, [
|
||||
// Drag Handle
|
||||
createElement('span', {
|
||||
className: 'subtask-drag-handle',
|
||||
title: 'Ziehen zum Verschieben'
|
||||
}, ['⋮⋮']),
|
||||
// Checkbox
|
||||
createElement('input', {
|
||||
type: 'checkbox',
|
||||
checked: subtask.completed,
|
||||
onchange: () => this.toggleSubtask(subtask.id)
|
||||
}),
|
||||
// Titel (Doppelklick zum Bearbeiten)
|
||||
createElement('span', {
|
||||
className: 'subtask-title'
|
||||
className: 'subtask-title',
|
||||
ondblclick: (e) => this.startEditSubtask(subtask.id, e.target)
|
||||
}, [subtask.title]),
|
||||
// Aktionen
|
||||
createElement('div', {
|
||||
className: 'subtask-actions'
|
||||
}, [
|
||||
createElement('button', {
|
||||
type: 'button',
|
||||
className: 'subtask-edit',
|
||||
title: 'Bearbeiten',
|
||||
onclick: (e) => this.startEditSubtask(subtask.id, e.target.closest('.subtask-item').querySelector('.subtask-title'))
|
||||
}, ['✎']),
|
||||
createElement('button', {
|
||||
type: 'button',
|
||||
className: 'subtask-delete',
|
||||
title: 'Löschen',
|
||||
onclick: () => this.deleteSubtask(subtask.id)
|
||||
}, ['×'])
|
||||
])
|
||||
]);
|
||||
|
||||
// Drag & Drop Events
|
||||
item.addEventListener('dragstart', (e) => this.handleSubtaskDragStart(e, subtask.id, index));
|
||||
item.addEventListener('dragover', (e) => this.handleSubtaskDragOver(e));
|
||||
item.addEventListener('dragleave', (e) => this.handleSubtaskDragLeave(e));
|
||||
item.addEventListener('drop', (e) => this.handleSubtaskDrop(e, subtask.id, index));
|
||||
item.addEventListener('dragend', (e) => this.handleSubtaskDragEnd(e));
|
||||
|
||||
container.appendChild(item);
|
||||
});
|
||||
|
||||
@ -936,6 +964,136 @@ class TaskModalManager {
|
||||
this.updateSubtaskProgress();
|
||||
}
|
||||
|
||||
// Subtask Drag & Drop
|
||||
handleSubtaskDragStart(e, subtaskId, position) {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', JSON.stringify({ subtaskId, position }));
|
||||
e.target.classList.add('dragging');
|
||||
this.draggedSubtaskId = subtaskId;
|
||||
}
|
||||
|
||||
handleSubtaskDragOver(e) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
const item = e.target.closest('.subtask-item');
|
||||
if (item && !item.classList.contains('dragging')) {
|
||||
const rect = item.getBoundingClientRect();
|
||||
const midpoint = rect.top + rect.height / 2;
|
||||
item.classList.remove('drag-over-top', 'drag-over-bottom');
|
||||
if (e.clientY < midpoint) {
|
||||
item.classList.add('drag-over-top');
|
||||
} else {
|
||||
item.classList.add('drag-over-bottom');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleSubtaskDragLeave(e) {
|
||||
const item = e.target.closest('.subtask-item');
|
||||
if (item) {
|
||||
item.classList.remove('drag-over-top', 'drag-over-bottom');
|
||||
}
|
||||
}
|
||||
|
||||
handleSubtaskDragEnd(e) {
|
||||
e.target.classList.remove('dragging');
|
||||
$$('.subtask-item').forEach(item => {
|
||||
item.classList.remove('drag-over-top', 'drag-over-bottom');
|
||||
});
|
||||
this.draggedSubtaskId = null;
|
||||
}
|
||||
|
||||
async handleSubtaskDrop(e, targetSubtaskId, targetPosition) {
|
||||
e.preventDefault();
|
||||
const item = e.target.closest('.subtask-item');
|
||||
if (item) {
|
||||
item.classList.remove('drag-over-top', 'drag-over-bottom');
|
||||
}
|
||||
|
||||
if (!this.draggedSubtaskId || this.draggedSubtaskId === targetSubtaskId) return;
|
||||
|
||||
const draggedIndex = this.subtasks.findIndex(s => s.id === this.draggedSubtaskId);
|
||||
if (draggedIndex === -1) return;
|
||||
|
||||
// Berechne neue Position basierend auf Drop-Position
|
||||
const rect = item.getBoundingClientRect();
|
||||
const midpoint = rect.top + rect.height / 2;
|
||||
let newPosition = targetPosition;
|
||||
if (e.clientY > midpoint && draggedIndex < targetPosition) {
|
||||
// Nach unten, hinter das Ziel
|
||||
newPosition = targetPosition;
|
||||
} else if (e.clientY > midpoint && draggedIndex > targetPosition) {
|
||||
newPosition = targetPosition + 1;
|
||||
} else if (e.clientY <= midpoint && draggedIndex > targetPosition) {
|
||||
newPosition = targetPosition;
|
||||
} else if (e.clientY <= midpoint && draggedIndex < targetPosition) {
|
||||
newPosition = targetPosition - 1;
|
||||
}
|
||||
|
||||
if (newPosition === draggedIndex) return;
|
||||
|
||||
// Lokale Reihenfolge aktualisieren
|
||||
const [moved] = this.subtasks.splice(draggedIndex, 1);
|
||||
this.subtasks.splice(newPosition, 0, moved);
|
||||
this.renderSubtasks();
|
||||
|
||||
// API-Call
|
||||
if (this.mode === 'edit' && this.taskId) {
|
||||
const projectId = store.get('currentProjectId');
|
||||
try {
|
||||
await api.reorderSubtasks(projectId, this.taskId, this.draggedSubtaskId, newPosition);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Neuordnen der Subtask:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subtask bearbeiten
|
||||
startEditSubtask(subtaskId, titleElement) {
|
||||
const subtask = this.subtasks.find(s => s.id === subtaskId);
|
||||
if (!subtask) return;
|
||||
|
||||
const currentTitle = subtask.title;
|
||||
const input = createElement('input', {
|
||||
type: 'text',
|
||||
className: 'subtask-edit-input',
|
||||
value: currentTitle
|
||||
});
|
||||
|
||||
// Titel durch Input ersetzen
|
||||
titleElement.replaceWith(input);
|
||||
input.focus();
|
||||
input.select();
|
||||
|
||||
const saveEdit = async () => {
|
||||
const newTitle = input.value.trim();
|
||||
if (newTitle && newTitle !== currentTitle) {
|
||||
subtask.title = newTitle;
|
||||
if (this.mode === 'edit' && this.taskId) {
|
||||
const projectId = store.get('currentProjectId');
|
||||
try {
|
||||
await api.updateSubtask(projectId, this.taskId, subtaskId, { title: newTitle });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Subtask:', error);
|
||||
subtask.title = currentTitle; // Rollback
|
||||
}
|
||||
}
|
||||
}
|
||||
this.renderSubtasks();
|
||||
};
|
||||
|
||||
input.addEventListener('blur', saveEdit);
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
input.blur();
|
||||
} else if (e.key === 'Escape') {
|
||||
subtask.title = currentTitle; // Keine Änderung
|
||||
this.renderSubtasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateSubtaskProgress() {
|
||||
const progressContainer = $('#subtask-progress');
|
||||
if (!progressContainer) return;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Offline support and caching
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = '133';
|
||||
const CACHE_VERSION = '138';
|
||||
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;
|
||||
|
||||
1226
logs/app.log
1226
logs/app.log
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
In neuem Issue referenzieren
Einen Benutzer sperren