🎨 style: update styles and layout in base templates and CSS files
This commit is contained in:
10
app.py
10
app.py
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, g
|
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, g
|
||||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
@@ -292,7 +292,7 @@ def login():
|
|||||||
if user and user.check_password(password):
|
if user and user.check_password(password):
|
||||||
login_user(user)
|
login_user(user)
|
||||||
# Aktualisiere letzten Login-Zeitpunkt
|
# Aktualisiere letzten Login-Zeitpunkt
|
||||||
user.last_login = datetime.utcnow()
|
user.last_login = datetime.now(timezone.utc)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
next_page = request.args.get('next')
|
next_page = request.args.get('next')
|
||||||
@@ -708,7 +708,7 @@ def update_public_node(node_id):
|
|||||||
node.color_code = data['color_code']
|
node.color_code = data['color_code']
|
||||||
|
|
||||||
# Als bearbeitet markieren
|
# Als bearbeitet markieren
|
||||||
node.last_modified = datetime.utcnow()
|
node.last_modified = datetime.now(timezone.utc)
|
||||||
node.last_modified_by = current_user.id
|
node.last_modified_by = current_user.id
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -1057,7 +1057,7 @@ def update_note(note_id):
|
|||||||
if color_code:
|
if color_code:
|
||||||
note.color_code = color_code
|
note.color_code = color_code
|
||||||
|
|
||||||
note.last_modified = datetime.utcnow()
|
note.last_modified = datetime.now(timezone.utc)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
@@ -1276,7 +1276,7 @@ def update_thought(thought_id):
|
|||||||
if 'color_code' in data:
|
if 'color_code' in data:
|
||||||
thought.color_code = data['color_code']
|
thought.color_code = data['color_code']
|
||||||
|
|
||||||
thought.last_modified = datetime.utcnow()
|
thought.last_modified = datetime.now(timezone.utc)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ html.dark ::-webkit-scrollbar-thumb:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-heading {
|
.section-heading {
|
||||||
font-size: 1.5rem;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,3 +820,198 @@ body:not(.dark) .user-dropdown {
|
|||||||
body:not(.dark) .user-dropdown-item:hover {
|
body:not(.dark) .user-dropdown-item:hover {
|
||||||
background-color: #f3f4f6;
|
background-color: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Medienabfragen für Responsivität */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
/* Optimierungen für Smartphones */
|
||||||
|
body {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-heading {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card, .panel, .glass-card {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optimierte Touch-Ziele für mobile Geräte */
|
||||||
|
button, .btn, .nav-link, .menu-item {
|
||||||
|
min-height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserte Lesbarkeit auf kleinen Bildschirmen */
|
||||||
|
p, li, input, textarea, button, .text-sm {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anpassungen für Tabellen auf kleinen Bildschirmen */
|
||||||
|
table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optimierte Formulare */
|
||||||
|
input, select, textarea {
|
||||||
|
font-size: 16px; /* Verhindert iOS-Zoom bei Fokus */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserter Abstand für Touch-Targets */
|
||||||
|
nav a, nav button, .menu-item {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) and (max-width: 1024px) {
|
||||||
|
/* Optimierungen für Tablets */
|
||||||
|
.container {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zweispaltige Layouts für mittlere Bildschirme */
|
||||||
|
.grid-cols-1 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optimierte Navigationsleiste */
|
||||||
|
.navbar {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1025px) {
|
||||||
|
/* Optimierungen für Desktop */
|
||||||
|
.container {
|
||||||
|
padding-left: 2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mehrspaltige Layouts für große Bildschirme */
|
||||||
|
.grid-cols-1 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover-Effekte nur auf Desktop-Geräten */
|
||||||
|
.card:hover, .panel:hover, .glass-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop-spezifische Animationen */
|
||||||
|
.animate-hover {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-hover:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design improvements */
|
||||||
|
.responsive-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-flex > * {
|
||||||
|
flex: 1 1 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accessibility improvements */
|
||||||
|
.focus-visible:focus-visible {
|
||||||
|
outline: 2px solid var(--accent-primary-light);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .focus-visible:focus-visible {
|
||||||
|
outline: 2px solid var(--accent-primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print styles */
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
background: white !important;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav, footer, button, .no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
main, article, .card, .panel, .container {
|
||||||
|
width: 100% !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: black !important;
|
||||||
|
background: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: black !important;
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a::after {
|
||||||
|
content: " (" attr(href) ")";
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
page-break-after: avoid;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
margin: 2cm;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1018,5 +1018,232 @@ class MindMap {
|
|||||||
// Implementierung für das Erstellen von Verbindungen
|
// Implementierung für das Erstellen von Verbindungen
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... existing code ...
|
// Zeigt Gedanken für einen Knoten an
|
||||||
|
showThoughtsForNode(node) {
|
||||||
|
const nodeId = node.id().split('_')[1]; // "node_123" => "123"
|
||||||
|
|
||||||
|
// Wir verwenden die fetchThoughtsForNode-Methode, die wir bereits implementiert haben
|
||||||
|
this.fetchThoughtsForNode(nodeId)
|
||||||
|
.then(thoughts => {
|
||||||
|
// Nur fortfahren, wenn wir tatsächlich Gedanken haben
|
||||||
|
if (thoughts.length === 0) {
|
||||||
|
this.showFlash('Keine Gedanken für diesen Knoten gefunden', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstelle einen Gedanken-Viewer, wenn er nicht existiert
|
||||||
|
let thoughtsViewer = document.getElementById('thoughts-viewer');
|
||||||
|
if (!thoughtsViewer) {
|
||||||
|
thoughtsViewer = document.createElement('div');
|
||||||
|
thoughtsViewer.id = 'thoughts-viewer';
|
||||||
|
thoughtsViewer.className = 'fixed inset-0 flex items-center justify-center z-50';
|
||||||
|
thoughtsViewer.innerHTML = `
|
||||||
|
<div class="fixed inset-0 bg-black bg-opacity-50 thoughts-backdrop"></div>
|
||||||
|
<div class="bg-slate-800 rounded-lg w-full max-w-3xl mx-4 relative z-10 border border-purple-500/20 thoughts-content overflow-hidden">
|
||||||
|
<div class="flex justify-between items-center p-4 border-b border-gray-700">
|
||||||
|
<h3 class="text-xl font-semibold text-white">Gedanken zu: <span class="node-name"></span></h3>
|
||||||
|
<button id="close-thoughts" class="text-gray-400 hover:text-white">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 thoughts-container max-h-[70vh] overflow-y-auto"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(thoughtsViewer);
|
||||||
|
|
||||||
|
// Event-Listener für Schließen-Button
|
||||||
|
document.getElementById('close-thoughts').addEventListener('click', () => {
|
||||||
|
// Animation für das Schließen
|
||||||
|
const content = thoughtsViewer.querySelector('.thoughts-content');
|
||||||
|
const backdrop = thoughtsViewer.querySelector('.thoughts-backdrop');
|
||||||
|
|
||||||
|
content.style.transform = 'scale(0.9)';
|
||||||
|
content.style.opacity = '0';
|
||||||
|
backdrop.style.opacity = '0';
|
||||||
|
|
||||||
|
setTimeout(() => thoughtsViewer.remove(), 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event-Listener für Backdrop-Klick
|
||||||
|
thoughtsViewer.querySelector('.thoughts-backdrop').addEventListener('click', () => {
|
||||||
|
document.getElementById('close-thoughts').click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiere den Titel mit dem Knotennamen
|
||||||
|
thoughtsViewer.querySelector('.node-name').textContent = node.data('name');
|
||||||
|
|
||||||
|
// Container für die Gedanken
|
||||||
|
const thoughtsContainer = thoughtsViewer.querySelector('.thoughts-container');
|
||||||
|
thoughtsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Gedanken rendern
|
||||||
|
this.renderThoughts(thoughts, thoughtsContainer);
|
||||||
|
|
||||||
|
// Animation für das Öffnen
|
||||||
|
const content = thoughtsViewer.querySelector('.thoughts-content');
|
||||||
|
const backdrop = thoughtsViewer.querySelector('.thoughts-backdrop');
|
||||||
|
|
||||||
|
content.style.transform = 'scale(0.9)';
|
||||||
|
content.style.opacity = '0';
|
||||||
|
backdrop.style.opacity = '0';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
content.style.transition = 'all 0.3s ease';
|
||||||
|
backdrop.style.transition = 'opacity 0.3s ease';
|
||||||
|
content.style.transform = 'scale(1)';
|
||||||
|
content.style.opacity = '1';
|
||||||
|
backdrop.style.opacity = '1';
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendert die Gedanken in der UI mit Animationen
|
||||||
|
renderThoughts(thoughts, container) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Animation delay counter
|
||||||
|
let delay = 0;
|
||||||
|
|
||||||
|
thoughts.forEach(thought => {
|
||||||
|
const thoughtCard = document.createElement('div');
|
||||||
|
thoughtCard.className = 'thought-card mb-4 bg-slate-700/50 rounded-lg overflow-hidden border border-slate-600/50 transition-all duration-300 hover:border-purple-500/30';
|
||||||
|
thoughtCard.setAttribute('data-id', thought.id);
|
||||||
|
thoughtCard.style.opacity = '0';
|
||||||
|
thoughtCard.style.transform = 'translateY(20px)';
|
||||||
|
|
||||||
|
const cardColor = thought.color_code || this.colorPalette.default;
|
||||||
|
|
||||||
|
thoughtCard.innerHTML = `
|
||||||
|
<div class="thought-card-header p-4" style="border-left: 4px solid ${cardColor}">
|
||||||
|
<h3 class="thought-title text-lg font-semibold text-white">${thought.title}</h3>
|
||||||
|
<div class="thought-meta flex gap-3 text-sm text-gray-400 mt-1">
|
||||||
|
<span class="thought-date"><i class="far fa-calendar-alt mr-1"></i>${new Date(thought.created_at).toLocaleDateString('de-DE')}</span>
|
||||||
|
${thought.author ? `<span class="thought-author"><i class="far fa-user mr-1"></i>${thought.author.username}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="thought-content p-4 text-gray-200">
|
||||||
|
<p>${thought.abstract || thought.content.substring(0, 150) + '...'}</p>
|
||||||
|
</div>
|
||||||
|
<div class="thought-footer p-4 pt-0 flex justify-between items-center">
|
||||||
|
<div class="thought-keywords flex flex-wrap gap-1">
|
||||||
|
${thought.keywords ? thought.keywords.split(',').map(kw =>
|
||||||
|
`<span class="keyword text-xs px-2 py-1 bg-purple-800/30 text-purple-200 rounded-full">${kw.trim()}</span>`).join('') : ''}
|
||||||
|
</div>
|
||||||
|
<a href="/thoughts/${thought.id}" class="thought-link text-purple-400 hover:text-purple-300 text-sm flex items-center">
|
||||||
|
Mehr lesen <i class="fas fa-arrow-right ml-1"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Animation-Effekt mit Verzögerung für jede Karte
|
||||||
|
setTimeout(() => {
|
||||||
|
thoughtCard.style.transition = 'all 0.5s ease';
|
||||||
|
thoughtCard.style.opacity = '1';
|
||||||
|
thoughtCard.style.transform = 'translateY(0)';
|
||||||
|
}, delay);
|
||||||
|
delay += 100; // Jede Karte erscheint mit 100ms Verzögerung
|
||||||
|
|
||||||
|
// Event-Listener für Klick auf Gedanken
|
||||||
|
thoughtCard.addEventListener('click', (e) => {
|
||||||
|
// Verhindern, dass der Link-Klick den Kartenklick auslöst
|
||||||
|
if (e.target.tagName === 'A' || e.target.closest('a')) return;
|
||||||
|
window.location.href = `/thoughts/${thought.id}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hover-Animation für Karten
|
||||||
|
thoughtCard.addEventListener('mouseenter', () => {
|
||||||
|
thoughtCard.style.transform = 'translateY(-5px)';
|
||||||
|
thoughtCard.style.boxShadow = '0 10px 25px rgba(0, 0, 0, 0.2)';
|
||||||
|
});
|
||||||
|
|
||||||
|
thoughtCard.addEventListener('mouseleave', () => {
|
||||||
|
thoughtCard.style.transform = 'translateY(0)';
|
||||||
|
thoughtCard.style.boxShadow = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(thoughtCard);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash-Nachrichten mit Animationen
|
||||||
|
showFlash(message, type = 'info') {
|
||||||
|
if (!this.flashContainer) {
|
||||||
|
this.flashContainer = document.createElement('div');
|
||||||
|
this.flashContainer.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2';
|
||||||
|
document.body.appendChild(this.flashContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flash = document.createElement('div');
|
||||||
|
flash.className = `flash-message p-3 rounded-lg shadow-lg flex items-center gap-3 max-w-xs text-white`;
|
||||||
|
|
||||||
|
// Verschiedene Stile je nach Typ
|
||||||
|
let backgroundColor, icon;
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
backgroundColor = 'bg-green-500';
|
||||||
|
icon = 'fa-check-circle';
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
backgroundColor = 'bg-red-500';
|
||||||
|
icon = 'fa-exclamation-circle';
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
backgroundColor = 'bg-yellow-500';
|
||||||
|
icon = 'fa-exclamation-triangle';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
backgroundColor = 'bg-blue-500';
|
||||||
|
icon = 'fa-info-circle';
|
||||||
|
}
|
||||||
|
|
||||||
|
flash.classList.add(backgroundColor);
|
||||||
|
|
||||||
|
flash.innerHTML = `
|
||||||
|
<div class="flash-icon"><i class="fas ${icon} text-lg"></i></div>
|
||||||
|
<div class="flash-text">${message}</div>
|
||||||
|
<button class="flash-close ml-auto text-white/80 hover:text-white"><i class="fas fa-times"></i></button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Füge den Flash zum Container hinzu
|
||||||
|
this.flashContainer.appendChild(flash);
|
||||||
|
|
||||||
|
// Einblend-Animation
|
||||||
|
flash.style.opacity = '0';
|
||||||
|
flash.style.transform = 'translateX(20px)';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
flash.style.transition = 'all 0.3s ease';
|
||||||
|
flash.style.opacity = '1';
|
||||||
|
flash.style.transform = 'translateX(0)';
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// Schließen-Button
|
||||||
|
const closeBtn = flash.querySelector('.flash-close');
|
||||||
|
closeBtn.addEventListener('click', () => {
|
||||||
|
// Ausblend-Animation
|
||||||
|
flash.style.opacity = '0';
|
||||||
|
flash.style.transform = 'translateX(20px)';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
flash.remove();
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Automatisches Ausblenden nach 5 Sekunden
|
||||||
|
setTimeout(() => {
|
||||||
|
if (flash.parentNode) {
|
||||||
|
flash.style.opacity = '0';
|
||||||
|
flash.style.transform = 'translateX(20px)';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (flash.parentNode) flash.remove();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return flash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -638,17 +638,28 @@
|
|||||||
// Globaler Zugriff für externe Skripte
|
// Globaler Zugriff für externe Skripte
|
||||||
window.MindMap = window.MindMap || {};
|
window.MindMap = window.MindMap || {};
|
||||||
|
|
||||||
window.MindMap.toggleDarkMode = function() {
|
// Funktion zum Anwenden des Dark Mode, strikt getrennt
|
||||||
// Alpine.js-Instanz benutzen, wenn verfügbar
|
function applyDarkModeClasses(isDarkMode) {
|
||||||
|
if (isDarkMode) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
document.body.classList.add('dark');
|
||||||
|
localStorage.setItem('colorMode', 'dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
document.body.classList.remove('dark');
|
||||||
|
localStorage.setItem('colorMode', 'light');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alpine.js darkMode-Variable aktualisieren, falls zutreffend
|
||||||
const appEl = document.querySelector('body');
|
const appEl = document.querySelector('body');
|
||||||
if (appEl && appEl.__x) {
|
if (appEl && appEl.__x) {
|
||||||
appEl.__x.$data.toggleDarkMode();
|
appEl.__x.$data.darkMode = isDarkMode;
|
||||||
} else {
|
}
|
||||||
// Fallback: Nur classList und localStorage
|
}
|
||||||
const isDark = document.documentElement.classList.contains('dark');
|
|
||||||
document.documentElement.classList.toggle('dark', !isDark);
|
window.MindMap.toggleDarkMode = function() {
|
||||||
document.body.classList.toggle('dark', !isDark);
|
const isDark = document.body.classList.contains('dark');
|
||||||
localStorage.setItem('colorMode', !isDark ? 'dark' : 'light');
|
applyDarkModeClasses(!isDark);
|
||||||
|
|
||||||
// Server aktualisieren
|
// Server aktualisieren
|
||||||
fetch('/api/set_dark_mode', {
|
fetch('/api/set_dark_mode', {
|
||||||
@@ -656,19 +667,38 @@
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ darkMode: !isDark })
|
body: JSON.stringify({ darkMode: !isDark })
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallback für Browser-Präferenz, falls keine Einstellung geladen werden konnte
|
// Initialisierung beim Laden
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
if (!document.body.classList.contains('dark') && !document.documentElement.classList.contains('dark')) {
|
// Reihenfolge der Prüfungen: Serverseitige Einstellung > Lokale Einstellung > Browser-Präferenz
|
||||||
|
|
||||||
|
// 1. Zuerst lokale Einstellung prüfen
|
||||||
|
const storedMode = localStorage.getItem('colorMode');
|
||||||
|
if (storedMode) {
|
||||||
|
applyDarkModeClasses(storedMode === 'dark');
|
||||||
|
} else {
|
||||||
|
// 2. Fallback auf Browser-Präferenz
|
||||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
if (prefersDark) {
|
applyDarkModeClasses(prefersDark);
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
document.body.classList.add('dark');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Serverseitige Einstellung abrufen und anwenden
|
||||||
|
fetch('/api/get_dark_mode')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const serverDarkMode = data.darkMode === true || data.darkMode === 'true';
|
||||||
|
applyDarkModeClasses(serverDarkMode);
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Fehler beim Abrufen des Dark Mode Status:', error));
|
||||||
|
|
||||||
|
// Listener für Änderungen der Browser-Präferenz
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||||
|
if (localStorage.getItem('colorMode') === null) {
|
||||||
|
applyDarkModeClasses(e.matches);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user