Sicherheits-Fixes, toter Code entfernt, Optimierungen
Sicherheit: - CSRF-Schutz auf allen API-Routes (admin, proposals, files, stats, export) - authenticateToken vor csrfProtection bei admin/proposals (CSRF-Bypass behoben) - CORS eingeschränkt auf taskmate.aegis-sight.de - JWT_SECRET und SESSION_TIMEOUT nicht mehr exportiert - Tote Auth-Funktionen entfernt (generateCsrfToken, generateToken Legacy) Toter Code entfernt: - 6 ungenutzte JS-Dateien (tour, dashboard, 4x contacts-*) - 2 ungenutzte CSS-Dateien (dashboard, contacts-extended) - backend/migrations/ Verzeichnis, knowledge.js.backup - Doppelter bcrypt require in database.js Optimierung: - Request-Logging filtert statische Assets (nur /api/ wird geloggt) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
274
frontend/js/mobile-swipe.js
Normale Datei
274
frontend/js/mobile-swipe.js
Normale Datei
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* TASKMATE - Mobile Swipe Enhancement
|
||||
* ====================================
|
||||
* Neue Swipe-Funktionalität für bessere mobile Navigation
|
||||
*/
|
||||
|
||||
export function enhanceMobileSwipe(mobileManager) {
|
||||
const SWIPE_THRESHOLD = 50;
|
||||
const SWIPE_VELOCITY = 0.3;
|
||||
|
||||
// State für Column-Navigation
|
||||
let currentColumnIndex = 0;
|
||||
let columnCount = 0;
|
||||
let isColumnSwipeEnabled = false;
|
||||
|
||||
// Column indicator elements
|
||||
let columnIndicator = null;
|
||||
|
||||
/**
|
||||
* Initialize column swipe for board view
|
||||
*/
|
||||
function initColumnSwipe() {
|
||||
const boardContainer = document.querySelector('.board-container');
|
||||
if (!boardContainer || mobileManager.currentView !== 'board') return;
|
||||
|
||||
// Create column indicator
|
||||
if (!columnIndicator) {
|
||||
columnIndicator = document.createElement('div');
|
||||
columnIndicator.className = 'mobile-column-indicator';
|
||||
columnIndicator.innerHTML = `
|
||||
<div class="column-dots"></div>
|
||||
<div class="column-name"></div>
|
||||
`;
|
||||
document.querySelector('.view-board')?.appendChild(columnIndicator);
|
||||
}
|
||||
|
||||
updateColumnInfo();
|
||||
showCurrentColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update column information
|
||||
*/
|
||||
function updateColumnInfo() {
|
||||
const columns = document.querySelectorAll('.column');
|
||||
columnCount = columns.length;
|
||||
|
||||
// Update dots
|
||||
const dotsContainer = columnIndicator?.querySelector('.column-dots');
|
||||
if (dotsContainer) {
|
||||
dotsContainer.innerHTML = Array.from({ length: columnCount }, (_, i) =>
|
||||
`<span class="dot ${i === currentColumnIndex ? 'active' : ''}"></span>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Update column name
|
||||
const nameContainer = columnIndicator?.querySelector('.column-name');
|
||||
if (nameContainer && columns[currentColumnIndex]) {
|
||||
const columnTitle = columns[currentColumnIndex].querySelector('.column-title')?.textContent || '';
|
||||
nameContainer.textContent = columnTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show specific column (hide others)
|
||||
*/
|
||||
function showCurrentColumn() {
|
||||
const columns = document.querySelectorAll('.column');
|
||||
const boardContainer = document.querySelector('.board-container');
|
||||
|
||||
columns.forEach((col, index) => {
|
||||
if (index === currentColumnIndex) {
|
||||
col.style.display = 'flex';
|
||||
col.classList.add('mobile-active');
|
||||
} else {
|
||||
col.style.display = 'none';
|
||||
col.classList.remove('mobile-active');
|
||||
}
|
||||
});
|
||||
|
||||
// Update add column button
|
||||
const addColumnBtn = document.querySelector('.btn-add-column');
|
||||
if (addColumnBtn) {
|
||||
addColumnBtn.style.display = currentColumnIndex === columnCount - 1 ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
updateColumnInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to specific column
|
||||
*/
|
||||
function navigateToColumn(index) {
|
||||
if (index < 0 || index >= columnCount) return;
|
||||
|
||||
currentColumnIndex = index;
|
||||
showCurrentColumn();
|
||||
|
||||
// Haptic feedback
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced board swipe handler
|
||||
*/
|
||||
mobileManager.handleBoardSwipeEnd = function() {
|
||||
if (!this.isSwiping || this.swipeDirection !== 'horizontal' || this.swipeTarget !== 'board') {
|
||||
this.resetSwipe();
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = this.touchCurrentX - this.touchStartX;
|
||||
const deltaTime = Date.now() - this.touchStartTime;
|
||||
const velocity = Math.abs(deltaX) / deltaTime;
|
||||
|
||||
const isValidSwipe = Math.abs(deltaX) > SWIPE_THRESHOLD || velocity > SWIPE_VELOCITY;
|
||||
|
||||
if (isValidSwipe && isColumnSwipeEnabled) {
|
||||
if (deltaX > 0 && currentColumnIndex > 0) {
|
||||
// Swipe right - previous column
|
||||
navigateToColumn(currentColumnIndex - 1);
|
||||
} else if (deltaX < 0 && currentColumnIndex < columnCount - 1) {
|
||||
// Swipe left - next column
|
||||
navigateToColumn(currentColumnIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.resetSwipe();
|
||||
};
|
||||
|
||||
/**
|
||||
* View hint for header swipes
|
||||
*/
|
||||
let viewHint = null;
|
||||
|
||||
function showViewSwipeHint(viewName, direction) {
|
||||
if (!viewHint) {
|
||||
viewHint = document.createElement('div');
|
||||
viewHint.className = 'mobile-view-hint';
|
||||
document.body.appendChild(viewHint);
|
||||
}
|
||||
|
||||
viewHint.textContent = getViewDisplayName(viewName);
|
||||
viewHint.classList.add('visible', direction);
|
||||
}
|
||||
|
||||
function hideViewSwipeHint() {
|
||||
if (viewHint) {
|
||||
viewHint.classList.remove('visible', 'left', 'right');
|
||||
}
|
||||
}
|
||||
|
||||
function getViewDisplayName(view) {
|
||||
const names = {
|
||||
'board': 'Board',
|
||||
'list': 'Liste',
|
||||
'calendar': 'Kalender',
|
||||
'proposals': 'Genehmigungen',
|
||||
'gitea': 'Gitea',
|
||||
'knowledge': 'Wissen'
|
||||
};
|
||||
return names[view] || view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced header swipe handler
|
||||
*/
|
||||
mobileManager.handleHeaderSwipeMove = function(e) {
|
||||
if (!this.isMobile || this.touchStartX === 0 || this.swipeTarget !== 'header') return;
|
||||
|
||||
const touch = e.touches[0];
|
||||
this.touchCurrentX = touch.clientX;
|
||||
this.touchCurrentY = touch.clientY;
|
||||
|
||||
const deltaX = this.touchCurrentX - this.touchStartX;
|
||||
const deltaY = this.touchCurrentY - this.touchStartY;
|
||||
|
||||
// Determine direction
|
||||
if (!this.swipeDirection && (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10)) {
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) * 1.5) {
|
||||
this.swipeDirection = 'horizontal';
|
||||
this.isSwiping = true;
|
||||
} else {
|
||||
this.swipeDirection = 'vertical';
|
||||
this.resetSwipe();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.swipeDirection !== 'horizontal') return;
|
||||
e.preventDefault();
|
||||
|
||||
// Show view hints
|
||||
const currentIndex = this.viewOrder.indexOf(this.currentView);
|
||||
if (deltaX > SWIPE_THRESHOLD && currentIndex > 0) {
|
||||
showViewSwipeHint(this.viewOrder[currentIndex - 1], 'left');
|
||||
} else if (deltaX < -SWIPE_THRESHOLD && currentIndex < this.viewOrder.length - 1) {
|
||||
showViewSwipeHint(this.viewOrder[currentIndex + 1], 'right');
|
||||
} else {
|
||||
hideViewSwipeHint();
|
||||
}
|
||||
};
|
||||
|
||||
mobileManager.handleHeaderSwipeEnd = function() {
|
||||
if (!this.isSwiping || this.swipeDirection !== 'horizontal' || this.swipeTarget !== 'header') {
|
||||
this.resetSwipe();
|
||||
hideViewSwipeHint();
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = this.touchCurrentX - this.touchStartX;
|
||||
const deltaTime = Date.now() - this.touchStartTime;
|
||||
const velocity = Math.abs(deltaX) / deltaTime;
|
||||
|
||||
const isValidSwipe = Math.abs(deltaX) > SWIPE_THRESHOLD || velocity > SWIPE_VELOCITY;
|
||||
|
||||
if (isValidSwipe) {
|
||||
const currentIndex = this.viewOrder.indexOf(this.currentView);
|
||||
if (deltaX > 0 && currentIndex > 0) {
|
||||
// Swipe right - previous view
|
||||
this.switchView(this.viewOrder[currentIndex - 1]);
|
||||
} else if (deltaX < 0 && currentIndex < this.viewOrder.length - 1) {
|
||||
// Swipe left - next view
|
||||
this.switchView(this.viewOrder[currentIndex + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
hideViewSwipeHint();
|
||||
this.resetSwipe();
|
||||
};
|
||||
|
||||
// Listen for view changes
|
||||
document.addEventListener('view:changed', (e) => {
|
||||
if (e.detail?.view === 'board' && mobileManager.isMobile) {
|
||||
isColumnSwipeEnabled = true;
|
||||
setTimeout(initColumnSwipe, 100);
|
||||
} else {
|
||||
isColumnSwipeEnabled = false;
|
||||
if (columnIndicator) {
|
||||
columnIndicator.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for column updates
|
||||
document.addEventListener('columns:updated', () => {
|
||||
if (isColumnSwipeEnabled) {
|
||||
updateColumnInfo();
|
||||
showCurrentColumn();
|
||||
}
|
||||
});
|
||||
|
||||
// Update on resize
|
||||
window.addEventListener('resize', () => {
|
||||
if (mobileManager.isMobile && mobileManager.currentView === 'board') {
|
||||
if (!isColumnSwipeEnabled) {
|
||||
isColumnSwipeEnabled = true;
|
||||
initColumnSwipe();
|
||||
}
|
||||
} else {
|
||||
isColumnSwipeEnabled = false;
|
||||
// Show all columns on desktop
|
||||
document.querySelectorAll('.column').forEach(col => {
|
||||
col.style.display = '';
|
||||
col.classList.remove('mobile-active');
|
||||
});
|
||||
if (columnIndicator) {
|
||||
columnIndicator.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren