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>
274 Zeilen
7.9 KiB
JavaScript
274 Zeilen
7.9 KiB
JavaScript
/**
|
|
* 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';
|
|
}
|
|
}
|
|
});
|
|
} |