Compare commits
6 Commits
a5396c0d6e
...
7f48526315
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f48526315 | |||
| 84f8a6bf31 | |||
| 7003c89447 | |||
| d0821db983 | |||
| f0c4c514c4 | |||
| 304a399b85 |
Binary file not shown.
104
app.py
104
app.py
@@ -487,32 +487,94 @@ def settings():
|
||||
if request.method == 'POST':
|
||||
action = request.form.get('action')
|
||||
|
||||
# Bestimme, ob es eine AJAX-Anfrage ist
|
||||
is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.content_type and 'multipart/form-data' in request.content_type
|
||||
|
||||
if action == 'update_profile':
|
||||
current_user.bio = request.form.get('bio')
|
||||
|
||||
# Update avatar if provided
|
||||
avatar_url = request.form.get('avatar_url')
|
||||
if avatar_url:
|
||||
current_user.avatar = avatar_url
|
||||
try:
|
||||
current_user.bio = request.form.get('bio', '')
|
||||
current_user.location = request.form.get('location', '')
|
||||
current_user.website = request.form.get('website', '')
|
||||
|
||||
db.session.commit()
|
||||
flash('Profil erfolgreich aktualisiert!', 'success')
|
||||
# Update avatar if provided
|
||||
avatar_url = request.form.get('avatar_url')
|
||||
if avatar_url:
|
||||
current_user.avatar = avatar_url
|
||||
|
||||
db.session.commit()
|
||||
|
||||
if is_ajax:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Profil erfolgreich aktualisiert!'
|
||||
})
|
||||
else:
|
||||
flash('Profil erfolgreich aktualisiert!', 'success')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
app.logger.error(f"Fehler beim Aktualisieren des Profils: {str(e)}")
|
||||
|
||||
if is_ajax:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Fehler beim Aktualisieren des Profils'
|
||||
}), 500
|
||||
else:
|
||||
flash('Fehler beim Aktualisieren des Profils', 'error')
|
||||
|
||||
elif action == 'update_password':
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
if not current_user.check_password(current_password):
|
||||
flash('Aktuelles Passwort ist nicht korrekt', 'error')
|
||||
elif new_password != confirm_password:
|
||||
flash('Neue Passwörter stimmen nicht überein', 'error')
|
||||
else:
|
||||
current_user.set_password(new_password)
|
||||
db.session.commit()
|
||||
flash('Passwort erfolgreich aktualisiert!', 'success')
|
||||
try:
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
if not current_user.check_password(current_password):
|
||||
if is_ajax:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Aktuelles Passwort ist nicht korrekt'
|
||||
}), 400
|
||||
else:
|
||||
flash('Aktuelles Passwort ist nicht korrekt', 'error')
|
||||
elif new_password != confirm_password:
|
||||
if is_ajax:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Neue Passwörter stimmen nicht überein'
|
||||
}), 400
|
||||
else:
|
||||
flash('Neue Passwörter stimmen nicht überein', 'error')
|
||||
else:
|
||||
current_user.set_password(new_password)
|
||||
db.session.commit()
|
||||
|
||||
if is_ajax:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Passwort erfolgreich aktualisiert!'
|
||||
})
|
||||
else:
|
||||
flash('Passwort erfolgreich aktualisiert!', 'success')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
app.logger.error(f"Fehler beim Aktualisieren des Passworts: {str(e)}")
|
||||
|
||||
if is_ajax:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Fehler beim Aktualisieren des Passworts'
|
||||
}), 500
|
||||
else:
|
||||
flash('Fehler beim Aktualisieren des Passworts', 'error')
|
||||
|
||||
return redirect(url_for('settings'))
|
||||
if not is_ajax:
|
||||
return redirect(url_for('settings'))
|
||||
else:
|
||||
# Standardantwort für AJAX, falls keine spezifische Antwort zurückgegeben wurde
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Einstellungen aktualisiert'
|
||||
})
|
||||
|
||||
return render_template('settings.html')
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from datetime import datetime
|
||||
|
||||
# Erstelle eine temporäre Flask-App, um die Datenbank zu initialisieren
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///systades.db'
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database/systades.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
db.init_app(app)
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,9 @@
|
||||
/* ChatGPT Assistent Styles - Verbesserte Version */
|
||||
#chatgpt-assistant {
|
||||
font-family: 'Inter', sans-serif;
|
||||
bottom: 4.5rem;
|
||||
bottom: 5.5rem;
|
||||
z-index: 100;
|
||||
max-height: 85vh;
|
||||
}
|
||||
|
||||
#assistant-chat {
|
||||
@@ -14,6 +16,14 @@
|
||||
max-height: 80vh !important;
|
||||
}
|
||||
|
||||
#assistant-history {
|
||||
max-height: calc(80vh - 150px);
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
||||
padding-bottom: 2rem; /* Zusätzlicher Abstand unten */
|
||||
}
|
||||
|
||||
#assistant-toggle {
|
||||
transition: transform 0.3s ease, background-color 0.2s ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
@@ -24,11 +34,6 @@
|
||||
transform: scale(1.1) rotate(10deg);
|
||||
}
|
||||
|
||||
#assistant-history {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
||||
}
|
||||
|
||||
#assistant-history::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
@@ -182,12 +187,12 @@ body:not(.dark) .typing-indicator span {
|
||||
@media (max-width: 640px) {
|
||||
#assistant-chat {
|
||||
width: calc(100vw - 2rem) !important;
|
||||
max-height: 70vh !important;
|
||||
max-height: 65vh !important;
|
||||
}
|
||||
|
||||
#chatgpt-assistant {
|
||||
right: 1rem;
|
||||
bottom: 5rem;
|
||||
bottom: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,4 +238,15 @@ body.dark .assistant-message {
|
||||
background-color: rgba(31, 41, 55, 0.5) !important;
|
||||
color: #F9FAFB !important;
|
||||
border-left: 3px solid #8B5CF6;
|
||||
}
|
||||
|
||||
/* Chat-Assistent-Position im Footer-Bereich anpassen */
|
||||
.chat-assistant {
|
||||
max-height: 75vh;
|
||||
bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.chat-assistant .chat-messages {
|
||||
max-height: calc(75vh - 180px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -474,18 +474,19 @@ body:not(.dark) a:hover {
|
||||
}
|
||||
|
||||
/* Light Mode Buttons */
|
||||
body:not(.dark) button:not(.toggle):not(.plain-btn) {
|
||||
color: white !important;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
body:not(.dark) .btn,
|
||||
body:not(.dark) button:not(.toggle) {
|
||||
background: linear-gradient(135deg, #6d28d9, #5b21b6);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(91, 33, 182, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 0.625rem 1.25rem;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
body:not(.dark) .btn-primary,
|
||||
body:not(.dark) .btn-secondary,
|
||||
body:not(.dark) .btn-success,
|
||||
body:not(.dark) .btn-danger,
|
||||
body:not(.dark) .btn-warning,
|
||||
body:not(.dark) .btn-info {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
body:not(.dark) .btn:hover,
|
||||
@@ -1043,4 +1044,25 @@ body:not(.dark) .chat-message-user {
|
||||
.chat-assistant .chat-messages {
|
||||
max-height: calc(85vh - 180px); /* Angepasst für größeres Fenster */
|
||||
overflow-y: auto;
|
||||
padding-bottom: 2rem; /* Zusätzlicher Abstand um Abschneiden zu vermeiden */
|
||||
}
|
||||
|
||||
/* Verbesserungen für das Mobilmenü */
|
||||
@media (max-width: 768px) {
|
||||
.mobile-menu-container {
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#chatgpt-assistant {
|
||||
bottom: 4.5rem !important;
|
||||
}
|
||||
|
||||
.chat-assistant {
|
||||
max-height: 70vh !important;
|
||||
}
|
||||
|
||||
.chat-assistant .chat-messages {
|
||||
max-height: calc(70vh - 160px) !important;
|
||||
}
|
||||
}
|
||||
253
static/css/mindmap.css
Normal file
253
static/css/mindmap.css
Normal file
@@ -0,0 +1,253 @@
|
||||
/* Mindmap Container Styles */
|
||||
.mindmap-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 600px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Toolbar Styles */
|
||||
.mindmap-toolbar {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .mindmap-toolbar {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Toolbar Buttons */
|
||||
.mindmap-toolbar button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.mindmap-toolbar button:hover {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.mindmap-toolbar button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.mindmap-toolbar button i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Export Group Styles */
|
||||
.export-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.export-options {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.dark .export-options {
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.export-group:hover .export-options {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.export-options button {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 8px 12px;
|
||||
justify-content: flex-start;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* Context Menu Styles */
|
||||
.mindmap-context-menu {
|
||||
position: fixed;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
padding: 8px;
|
||||
z-index: 1000;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.dark .mindmap-context-menu {
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.mindmap-context-menu button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.mindmap-context-menu button:hover {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mindmap-context-menu button i {
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
/* Node Styles */
|
||||
.mindmap-node {
|
||||
background-color: var(--bg-secondary);
|
||||
border: 2px solid var(--accent-primary);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mindmap-node:hover {
|
||||
box-shadow: 0 0 0 2px var(--accent-primary);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.mindmap-node.selected {
|
||||
border-color: var(--accent-secondary);
|
||||
box-shadow: 0 0 0 3px var(--accent-secondary);
|
||||
}
|
||||
|
||||
/* Edge Styles */
|
||||
.mindmap-edge {
|
||||
width: 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .mindmap-edge {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.mindmap-edge:hover {
|
||||
width: 3px;
|
||||
background-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Animation Styles */
|
||||
@keyframes nodeAppear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.mindmap-node-new {
|
||||
animation: nodeAppear 0.3s ease forwards;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 768px) {
|
||||
.mindmap-toolbar {
|
||||
flex-wrap: wrap;
|
||||
width: calc(100% - 32px);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.export-options {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.mindmap-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mindmap-loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid var(--bg-secondary);
|
||||
border-top-color: var(--accent-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tooltip Styles */
|
||||
.mindmap-tooltip {
|
||||
position: absolute;
|
||||
background: var(--bg-secondary);
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
max-width: 200px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dark .mindmap-tooltip {
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
Binary file not shown.
@@ -100,6 +100,9 @@
|
||||
<!-- Neural Network Background CSS -->
|
||||
<link href="{{ url_for('static', filename='css/neural-network-background.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- Mindmap CSS -->
|
||||
<link href="{{ url_for('static', filename='css/mindmap.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- D3.js für Visualisierungen -->
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
|
||||
@@ -206,10 +209,10 @@
|
||||
/* Light Mode Buttons */
|
||||
body:not(.dark) .btn,
|
||||
body:not(.dark) button:not(.toggle) {
|
||||
background: linear-gradient(135deg, #6d28d9, #5b21b6);
|
||||
background: linear-gradient(135deg, #7c3aed, #6d28d9);
|
||||
color: white !important;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(91, 33, 182, 0.25);
|
||||
box-shadow: 0 2px 4px rgba(124, 58, 237, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 0.625rem 1.25rem;
|
||||
transition: all 0.2s ease;
|
||||
@@ -220,24 +223,96 @@
|
||||
|
||||
body:not(.dark) .btn:hover,
|
||||
body:not(.dark) button:not(.toggle):hover {
|
||||
background: linear-gradient(135deg, #7c3aed, #6d28d9);
|
||||
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(109, 40, 217, 0.3);
|
||||
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3);
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* KI-Chat Button im Light-Mode */
|
||||
body:not(.dark) [onclick*="MindMap.assistant.toggleAssistant"] {
|
||||
background: linear-gradient(135deg, #7c3aed, #3b82f6);
|
||||
background: linear-gradient(135deg, #7c3aed, #4f46e5);
|
||||
color: white !important;
|
||||
font-weight: 500;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
body:not(.dark) [onclick*="MindMap.assistant.toggleAssistant"]:hover {
|
||||
background: linear-gradient(135deg, #8b5cf6, #4f46e5);
|
||||
background: linear-gradient(135deg, #8b5cf6, #6366f1);
|
||||
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
/* Style improvements for the theme toggle button */
|
||||
.theme-toggle {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 24px;
|
||||
border-radius: 24px;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.dark .theme-toggle {
|
||||
background: linear-gradient(to right, #7c3aed, #3b82f6);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3), 0 0 10px rgba(124, 58, 237, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .theme-toggle {
|
||||
background: linear-gradient(to right, #8b5cf6, #60a5fa);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1), 0 0 10px rgba(124, 58, 237, 0.15);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.theme-toggle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
body.dark .theme-toggle::after {
|
||||
background: #f1f5f9 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237c3aed' width='14' height='14'%3E%3Cpath d='M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||
transform: translateX(24px);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
body:not(.dark) .theme-toggle::after {
|
||||
background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f59e0b' width='14' height='14'%3E%3Cpath d='M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
|
||||
transform: translateX(2px);
|
||||
box-shadow: 0 0 8px rgba(124, 58, 237, 0.2);
|
||||
}
|
||||
|
||||
.theme-toggle:hover::after {
|
||||
box-shadow: 0 0 12px rgba(124, 58, 237, 0.4);
|
||||
}
|
||||
|
||||
/* Fixes for light mode button text colors */
|
||||
body:not(.dark) .btn-primary {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* Fix for KI-Chat container */
|
||||
#chatgpt-assistant {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.chat-assistant {
|
||||
max-height: 80vh !important;
|
||||
}
|
||||
|
||||
.chat-assistant .chat-messages {
|
||||
max-height: calc(80vh - 160px) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="{
|
||||
@@ -285,6 +360,7 @@
|
||||
this.darkMode = !this.darkMode;
|
||||
this.applyDarkMode();
|
||||
|
||||
// Server über Änderung informieren
|
||||
fetch('/api/set_dark_mode', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -295,11 +371,10 @@
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Event auslösen für andere Komponenten
|
||||
document.dispatchEvent(new CustomEvent('darkModeToggled', {
|
||||
detail: { isDark: this.darkMode }
|
||||
}));
|
||||
} else {
|
||||
console.error('Fehler beim Speichern der Dark Mode-Einstellung:', data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -365,13 +440,11 @@
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Dark/Light Mode Schalter -->
|
||||
<button
|
||||
@click="darkMode = !darkMode; toggleDarkMode()"
|
||||
class="theme-toggle relative w-12 h-6 rounded-full bg-gradient-to-r transition-all duration-300 flex items-center"
|
||||
:class="darkMode ? 'from-purple-700 to-indigo-800' : 'from-purple-400 to-indigo-500'"
|
||||
@click="toggleDarkMode()"
|
||||
class="theme-toggle relative w-12 h-6 rounded-full transition-all duration-300 flex items-center overflow-hidden"
|
||||
aria-label="Dark Mode umschalten"
|
||||
>
|
||||
<span class="absolute w-5 h-5 rounded-full bg-white shadow-md transition-transform duration-300"
|
||||
:class="darkMode ? 'translate-x-7' : 'translate-x-1'"></span>
|
||||
<span class="sr-only" x-text="darkMode ? 'Zum Light Mode wechseln' : 'Zum Dark Mode wechseln'"></span>
|
||||
</button>
|
||||
<!-- Profil-Link oder Login -->
|
||||
{% if current_user.is_authenticated %}
|
||||
@@ -697,18 +770,27 @@
|
||||
if (appEl && appEl.__x) {
|
||||
appEl.__x.$data.darkMode = isDarkMode;
|
||||
}
|
||||
|
||||
// Event für andere Komponenten auslösen
|
||||
document.dispatchEvent(new CustomEvent('darkModeToggled', {
|
||||
detail: { isDark: isDarkMode }
|
||||
}));
|
||||
}
|
||||
|
||||
window.MindMap.toggleDarkMode = function() {
|
||||
const isDark = document.body.classList.contains('dark');
|
||||
applyDarkModeClasses(!isDark);
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
const newIsDark = !isDark;
|
||||
|
||||
// DOM aktualisieren
|
||||
applyDarkModeClasses(newIsDark);
|
||||
|
||||
// Server aktualisieren
|
||||
fetch('/api/set_dark_mode', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ darkMode: !isDark })
|
||||
}).catch(console.error);
|
||||
body: JSON.stringify({ darkMode: newIsDark })
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
// Initialisierung beim Laden
|
||||
@@ -729,8 +811,10 @@
|
||||
fetch('/api/get_dark_mode')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const serverDarkMode = data.darkMode === true || data.darkMode === 'true';
|
||||
applyDarkModeClasses(serverDarkMode);
|
||||
if (data.success) {
|
||||
const serverDarkMode = data.darkMode === true || data.darkMode === 'true';
|
||||
applyDarkModeClasses(serverDarkMode);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Fehler beim Abrufen des Dark Mode Status:', error));
|
||||
|
||||
|
||||
@@ -261,6 +261,16 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Mindmap-Vorschau -->
|
||||
<div class="mt-8">
|
||||
<h3 class="text-xl font-semibold mb-4">Vorschau</h3>
|
||||
<div class="mindmap-container">
|
||||
<div id="cy" class="w-full h-[400px] rounded-xl border"
|
||||
x-bind:class="darkMode ? 'border-gray-700' : 'border-gray-200'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -286,6 +296,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape-cose-bilkent/4.1.0/cytoscape-cose-bilkent.min.js"></script>
|
||||
<script nonce="{{ csp_nonce }}">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Einfache Animationen für die Eingabefelder
|
||||
@@ -311,6 +323,43 @@
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Wird erstellt...';
|
||||
submitBtn.disabled = true;
|
||||
});
|
||||
|
||||
// Mindmap-Vorschau initialisieren
|
||||
const mindmap = new MindMap.Visualization('cy', {
|
||||
enableEditing: true,
|
||||
onNodeClick: function(nodeData) {
|
||||
console.log("Knoten ausgewählt:", nodeData);
|
||||
}
|
||||
});
|
||||
|
||||
// Formularfelder mit Mindmap verbinden
|
||||
const nameInput = document.getElementById('name');
|
||||
const descriptionInput = document.getElementById('description');
|
||||
|
||||
// Aktualisiere Mindmap wenn sich die Eingaben ändern
|
||||
nameInput.addEventListener('input', function() {
|
||||
if (mindmap.cy) {
|
||||
const rootNode = mindmap.cy.$('#root');
|
||||
if (rootNode.length > 0) {
|
||||
rootNode.data('name', this.value || 'Neue Mindmap');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialisiere die Mindmap
|
||||
mindmap.initialize().then(() => {
|
||||
console.log("Mindmap-Vorschau initialisiert");
|
||||
|
||||
// Setze initiale Werte
|
||||
if (nameInput.value) {
|
||||
const rootNode = mindmap.cy.$('#root');
|
||||
if (rootNode.length > 0) {
|
||||
rootNode.data('name', nameInput.value);
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("Fehler bei der Initialisierung der Mindmap:", error);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
423
templates/edit_mindmap.html
Normal file
423
templates/edit_mindmap.html
Normal file
@@ -0,0 +1,423 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Mindmap bearbeiten{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* Spezifische Stile für die Mindmap-Bearbeitungsseite */
|
||||
.form-container {
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
body.dark .form-container {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
body:not(.dark) .form-container {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
body:not(.dark) .form-header {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.form-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
body.dark .form-input,
|
||||
body.dark .form-textarea {
|
||||
background-color: rgba(15, 23, 42, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
body:not(.dark) .form-input,
|
||||
body:not(.dark) .form-textarea {
|
||||
background-color: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
body.dark .form-input:focus,
|
||||
body.dark .form-textarea:focus {
|
||||
border-color: #7c3aed;
|
||||
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body:not(.dark) .form-input:focus,
|
||||
body:not(.dark) .form-textarea:focus {
|
||||
border-color: #7c3aed;
|
||||
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-switch input[type="checkbox"] {
|
||||
height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.form-switch label {
|
||||
cursor: pointer;
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
background: rgba(100, 116, 139, 0.3);
|
||||
display: block;
|
||||
border-radius: 25px;
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-switch label:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
background: #fff;
|
||||
border-radius: 19px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.form-switch input:checked + label {
|
||||
background: #7c3aed;
|
||||
}
|
||||
|
||||
.form-switch input:checked + label:after {
|
||||
left: calc(100% - 3px);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background-color: #7c3aed;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-submit:hover {
|
||||
background-color: #6d28d9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(109, 40, 217, 0.2);
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: transparent;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
body.dark .btn-cancel {
|
||||
color: #e2e8f0;
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-cancel {
|
||||
color: #475569;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
body.dark .btn-cancel:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-cancel:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Animation für den Seiteneintritt */
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
animation: slideInUp 0.5s ease forwards;
|
||||
}
|
||||
|
||||
/* Animation für Hover-Effekte */
|
||||
.input-animation {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.input-animation:focus {
|
||||
transform: scale(1.01);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8 animate-fadeIn">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<!-- Titel mit Animation -->
|
||||
<div class="text-center mb-8 animate-pulse">
|
||||
<h1 class="text-3xl font-bold mb-2 mystical-glow gradient-text">
|
||||
Mindmap bearbeiten
|
||||
</h1>
|
||||
<p class="opacity-80">Aktualisiere die Details deiner Mindmap</p>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<div class="form-header">
|
||||
<h2 class="text-xl font-semibold">Mindmap-Details</h2>
|
||||
</div>
|
||||
|
||||
<div class="form-body">
|
||||
<form action="{{ url_for('edit_mindmap', mindmap_id=mindmap.id) }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label">Name der Mindmap</label>
|
||||
<input type="text" id="name" name="name" class="form-input input-animation" required
|
||||
placeholder="z.B. Meine Philosophie-Mindmap" value="{{ mindmap.name }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description" class="form-label">Beschreibung</label>
|
||||
<textarea id="description" name="description" class="form-textarea input-animation"
|
||||
placeholder="Worum geht es in dieser Mindmap?">{{ mindmap.description }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-switch">
|
||||
<input type="checkbox" id="is_private" name="is_private" {% if mindmap.is_private %}checked{% endif %}>
|
||||
<label for="is_private"></label>
|
||||
<span>Private Mindmap (nur für dich sichtbar)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between mt-6">
|
||||
<a href="{{ url_for('mindmap', mindmap_id=mindmap.id) }}" class="btn-cancel">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Zurück
|
||||
</a>
|
||||
<button type="submit" class="btn-submit">
|
||||
<i class="fas fa-save"></i>
|
||||
Änderungen speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Mindmap-Editor -->
|
||||
<div class="mt-8">
|
||||
<h3 class="text-xl font-semibold mb-4">Mindmap bearbeiten</h3>
|
||||
<div class="mindmap-container">
|
||||
<div id="cy" class="w-full h-[600px] rounded-xl border"
|
||||
x-bind:class="darkMode ? 'border-gray-700' : 'border-gray-200'">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bearbeitungshinweise -->
|
||||
<div class="mt-4 text-sm opacity-80">
|
||||
<p><i class="fas fa-info-circle mr-2"></i>Klicke auf Knoten zum Bearbeiten, ziehe sie zum Neuanordnen oder nutze die Toolbar für weitere Funktionen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tipps-Sektion -->
|
||||
<div class="mt-8 p-5 rounded-lg border animate-fadeIn"
|
||||
x-bind:class="darkMode ? 'bg-slate-800/40 border-slate-700/50' : 'bg-white border-slate-200'">
|
||||
<h3 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-lightbulb text-yellow-400 mr-2"></i>Tipps zum Bearbeiten einer Mindmap
|
||||
</h3>
|
||||
<div x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
<ul class="list-disc pl-5 space-y-2">
|
||||
<li>Überprüfe, ob der Name noch zum aktuellen Inhalt passt</li>
|
||||
<li>Aktualisiere die Beschreibung, um neue Aspekte zu berücksichtigen</li>
|
||||
<li>Entscheide, ob die Sichtbarkeitseinstellungen noch passend sind</li>
|
||||
<li>Nutze aussagekräftige Namen für bessere Auffindbarkeit</li>
|
||||
<li>Behalte die Konsistenz mit verknüpften Konzepten im Auge</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape-cose-bilkent/4.1.0/cytoscape-cose-bilkent.min.js"></script>
|
||||
<script nonce="{{ csp_nonce }}">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Einfache Animationen für die Eingabefelder
|
||||
const inputs = document.querySelectorAll('.input-animation');
|
||||
|
||||
inputs.forEach(input => {
|
||||
// Subtile Skalierung bei Fokus
|
||||
input.addEventListener('focus', function() {
|
||||
this.style.transform = 'scale(1.01)';
|
||||
this.style.boxShadow = '0 4px 12px rgba(124, 58, 237, 0.15)';
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
this.style.transform = 'scale(1)';
|
||||
this.style.boxShadow = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Formular-Absenden-Animation
|
||||
const form = document.querySelector('form');
|
||||
form.addEventListener('submit', function(e) {
|
||||
const submitBtn = this.querySelector('.btn-submit');
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Wird gespeichert...';
|
||||
submitBtn.disabled = true;
|
||||
});
|
||||
|
||||
// Mindmap initialisieren
|
||||
const mindmap = new MindMap.Visualization('cy', {
|
||||
enableEditing: true,
|
||||
apiEndpoint: '/api/mindmap/{{ mindmap.id }}',
|
||||
onNodeClick: function(nodeData) {
|
||||
console.log("Knoten ausgewählt:", nodeData);
|
||||
},
|
||||
onChange: function(data) {
|
||||
// Automatisches Speichern bei Änderungen
|
||||
fetch('/api/mindmap/{{ mindmap.id }}/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Netzwerkfehler beim Speichern');
|
||||
}
|
||||
console.log('Änderungen gespeichert');
|
||||
}).catch(error => {
|
||||
console.error('Fehler beim Speichern:', error);
|
||||
alert('Fehler beim Speichern der Änderungen');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Formularfelder mit Mindmap verbinden
|
||||
const nameInput = document.getElementById('name');
|
||||
const descriptionInput = document.getElementById('description');
|
||||
|
||||
// Aktualisiere Mindmap wenn sich die Eingaben ändern
|
||||
nameInput.addEventListener('input', function() {
|
||||
if (mindmap.cy) {
|
||||
const rootNode = mindmap.cy.$('#root');
|
||||
if (rootNode.length > 0) {
|
||||
rootNode.data('name', this.value || 'Mindmap');
|
||||
mindmap.saveToServer();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialisiere die Mindmap mit existierenden Daten
|
||||
mindmap.initialize().then(() => {
|
||||
console.log("Mindmap-Editor initialisiert");
|
||||
|
||||
// Lade existierende Daten
|
||||
fetch('/api/mindmap/{{ mindmap.id }}/data')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
mindmap.loadData(data);
|
||||
console.log("Mindmap-Daten geladen");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Fehler beim Laden der Mindmap-Daten:", error);
|
||||
alert("Fehler beim Laden der Mindmap");
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error("Fehler bei der Initialisierung des Editors:", error);
|
||||
});
|
||||
|
||||
// Autosave-Status Anzeige
|
||||
const statusIndicator = document.createElement('div');
|
||||
statusIndicator.className = 'fixed bottom-4 right-4 px-4 py-2 rounded-full text-sm transition-all duration-300';
|
||||
document.body.appendChild(statusIndicator);
|
||||
|
||||
// Zeige Speicherstatus
|
||||
function showStatus(message, isError = false) {
|
||||
statusIndicator.textContent = message;
|
||||
statusIndicator.className = `fixed bottom-4 right-4 px-4 py-2 rounded-full text-sm transition-all duration-300 ${
|
||||
isError
|
||||
? 'bg-red-500 text-white'
|
||||
: 'bg-green-500 text-white'
|
||||
}`;
|
||||
setTimeout(() => {
|
||||
statusIndicator.className = 'fixed bottom-4 right-4 px-4 py-2 rounded-full text-sm transition-all duration-300 opacity-0';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Event-Listener für Speicherstatus
|
||||
document.addEventListener('mindmapSaved', () => {
|
||||
showStatus('Änderungen gespeichert');
|
||||
});
|
||||
|
||||
document.addEventListener('mindmapError', (event) => {
|
||||
showStatus(event.detail.message, true);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -518,6 +518,61 @@
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(109, 40, 217, 0.25);
|
||||
}
|
||||
|
||||
/* Thought item styling */
|
||||
.thought-border {
|
||||
border-left: 4px solid #B39DDB;
|
||||
}
|
||||
|
||||
/* Light Mode Gedanken */
|
||||
body:not(.dark) .thought-item {
|
||||
background-color: white;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .thought-item h3 {
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
body:not(.dark) .thought-item p {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
/* Verbesserte Kontraste im Light-Mode */
|
||||
body:not(.dark) .profile-tab.active {
|
||||
color: #7c3aed;
|
||||
border-bottom-color: #7c3aed;
|
||||
background-color: rgba(124, 58, 237, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .profile-tab:hover:not(.active) {
|
||||
color: #6d28d9;
|
||||
background-color: rgba(124, 58, 237, 0.05);
|
||||
}
|
||||
|
||||
body:not(.dark) .nav-link-light {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
body:not(.dark) .nav-link-light:hover {
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
body:not(.dark) .edit-profile-btn {
|
||||
background: #7c3aed;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
body:not(.dark) .edit-profile-btn:hover {
|
||||
background: #6d28d9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(109, 40, 217, 0.25);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -657,7 +712,7 @@
|
||||
{% for thought in thoughts %}
|
||||
<div class="thought-item bg-opacity-70 rounded-xl overflow-hidden border transition-all duration-300 hover:transform hover:scale-105 hover:shadow-lg"
|
||||
x-bind:class="darkMode ? 'bg-gray-800/80 border-gray-700/60' : 'bg-white/90 border-gray-200/60'">
|
||||
<div class="p-5" style="border-left: 4px solid {{ thought.color_code|default('#B39DDB') }}">
|
||||
<div class="p-5 thought-border" data-color="{{ thought.color_code|default('#B39DDB') }}">
|
||||
<h3 class="text-xl font-bold mb-2"
|
||||
x-bind:class="darkMode ? 'text-purple-300' : 'text-purple-700'">{{ thought.title }}</h3>
|
||||
<p class="mb-4 text-sm"
|
||||
@@ -876,7 +931,21 @@
|
||||
|
||||
// Entsprechenden Tab-Inhalt anzeigen
|
||||
const tabId = this.getAttribute('data-tab');
|
||||
document.getElementById(`${tabId}-tab`).classList.remove('hidden');
|
||||
const tabContent = document.getElementById(`${tabId}-tab`);
|
||||
|
||||
if (tabContent) {
|
||||
tabContent.classList.remove('hidden');
|
||||
|
||||
// Animation für Tab-Inhalt
|
||||
tabContent.style.opacity = '0';
|
||||
tabContent.style.transform = 'translateY(10px)';
|
||||
|
||||
setTimeout(() => {
|
||||
tabContent.style.transition = 'all 0.3s ease';
|
||||
tabContent.style.opacity = '1';
|
||||
tabContent.style.transform = 'translateY(0)';
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -902,6 +971,280 @@
|
||||
countElement.textContent = count;
|
||||
});
|
||||
});
|
||||
|
||||
// Profilbearbeitung
|
||||
const editProfileBtn = document.querySelector('.edit-profile-btn');
|
||||
|
||||
if (editProfileBtn) {
|
||||
editProfileBtn.addEventListener('click', function() {
|
||||
// Zum Einstellungstab wechseln
|
||||
const settingsTab = document.querySelector('[data-tab="settings"]');
|
||||
if (settingsTab) {
|
||||
settingsTab.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Avatar-Bearbeitung
|
||||
const avatarEditBtn = document.querySelector('.avatar-edit');
|
||||
if (avatarEditBtn) {
|
||||
avatarEditBtn.addEventListener('click', function() {
|
||||
// Dateiauwahl öffnen
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = 'image/*';
|
||||
fileInput.style.display = 'none';
|
||||
document.body.appendChild(fileInput);
|
||||
|
||||
fileInput.click();
|
||||
|
||||
fileInput.addEventListener('change', function() {
|
||||
if (this.files && this.files[0]) {
|
||||
// Anzeigen des gewählten Bildes
|
||||
const avatarImg = document.querySelector('.avatar');
|
||||
|
||||
// FileReader zum Einlesen des Bildes
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
// Vorschau anzeigen
|
||||
avatarImg.src = e.target.result;
|
||||
|
||||
// Avatar-URL im Einstellungsbereich speichern
|
||||
const avatarUrlInput = document.createElement('input');
|
||||
avatarUrlInput.type = 'hidden';
|
||||
avatarUrlInput.name = 'avatar_url';
|
||||
avatarUrlInput.id = 'avatar_url';
|
||||
avatarUrlInput.value = e.target.result;
|
||||
|
||||
// Entferne vorhandenes Input, falls vorhanden
|
||||
const existingInput = document.getElementById('avatar_url');
|
||||
if (existingInput) {
|
||||
existingInput.remove();
|
||||
}
|
||||
|
||||
// Zum Formular hinzufügen
|
||||
const settingsForm = document.querySelector('.settings-card');
|
||||
if (settingsForm) {
|
||||
settingsForm.appendChild(avatarUrlInput);
|
||||
}
|
||||
|
||||
// Erfolgsmeldung anzeigen
|
||||
showNotification('Avatar wurde aktualisiert! Bitte speichere deine Änderungen.', 'success');
|
||||
};
|
||||
|
||||
reader.readAsDataURL(this.files[0]);
|
||||
}
|
||||
|
||||
// Input entfernen
|
||||
document.body.removeChild(fileInput);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Einstellungen-Formular-Handling
|
||||
const saveSettingsBtn = document.querySelectorAll('.settings-card .profile-action-btn.primary');
|
||||
|
||||
saveSettingsBtn.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const isPasswordUpdate = this.textContent.includes('Passwort');
|
||||
|
||||
// Passwort-Update
|
||||
if (isPasswordUpdate) {
|
||||
const currentPassword = document.getElementById('password').value;
|
||||
const newPassword = document.getElementById('password_confirm').value;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
showNotification('Bitte fülle alle Passwortfelder aus', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// AJAX-Anfrage senden
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'update_password');
|
||||
formData.append('current_password', currentPassword);
|
||||
formData.append('new_password', newPassword);
|
||||
formData.append('confirm_password', newPassword);
|
||||
|
||||
// Visuelle Rückmeldung
|
||||
const originalText = this.innerHTML;
|
||||
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
|
||||
this.disabled = true;
|
||||
|
||||
fetch('{{ url_for("settings") }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Aktualisieren des Passworts:', error);
|
||||
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
|
||||
})
|
||||
.then(data => {
|
||||
this.innerHTML = originalText;
|
||||
this.disabled = false;
|
||||
|
||||
if (data && data.success) {
|
||||
showNotification('Passwort erfolgreich aktualisiert!', 'success');
|
||||
document.getElementById('password').value = '';
|
||||
document.getElementById('password_confirm').value = '';
|
||||
} else {
|
||||
showNotification(data?.message || 'Fehler beim Aktualisieren des Passworts', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Profil-Update
|
||||
else {
|
||||
// Sammle Daten aus den Eingabefeldern
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'update_profile');
|
||||
formData.append('bio', document.getElementById('bio').value || '');
|
||||
formData.append('location', document.getElementById('location').value || '');
|
||||
formData.append('website', document.getElementById('website').value || '');
|
||||
|
||||
// Avatar hinzufügen, falls vorhanden
|
||||
const avatarUrlInput = document.getElementById('avatar_url');
|
||||
if (avatarUrlInput) {
|
||||
formData.append('avatar_url', avatarUrlInput.value);
|
||||
}
|
||||
|
||||
// Visuelle Rückmeldung
|
||||
const originalText = this.innerHTML;
|
||||
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
|
||||
this.disabled = true;
|
||||
|
||||
// AJAX-Anfrage senden
|
||||
fetch('{{ url_for("settings") }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Speichern');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Speichern der Profileinstellungen:', error);
|
||||
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
|
||||
})
|
||||
.then(data => {
|
||||
this.innerHTML = originalText;
|
||||
this.disabled = false;
|
||||
|
||||
if (data && data.success) {
|
||||
// Erfolgsanimation
|
||||
showNotification('Profil erfolgreich aktualisiert!', 'success');
|
||||
|
||||
// UI aktualisieren ohne Neuladen
|
||||
const bioElement = document.querySelector('.user-bio');
|
||||
const locationElement = document.querySelector('.user-meta span:first-child');
|
||||
|
||||
if (bioElement) {
|
||||
bioElement.textContent = document.getElementById('bio').value || 'Keine Bio vorhanden. Klicke auf bearbeiten, um eine hinzuzufügen.';
|
||||
}
|
||||
|
||||
if (locationElement) {
|
||||
const location = document.getElementById('location').value;
|
||||
locationElement.innerHTML = `<i class="fas fa-map-marker-alt"></i> ${location || 'Kein Standort angegeben'}`;
|
||||
}
|
||||
} else {
|
||||
showNotification(data?.message || 'Fehler beim Aktualisieren des Profils', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Gedanken-Karten mit Hover-Effekten und Border-Farben
|
||||
const thoughtItems = document.querySelectorAll('.thought-item');
|
||||
thoughtItems.forEach(item => {
|
||||
// Hover-Effekte
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.transform = 'translateY(-5px)';
|
||||
item.style.boxShadow = '0 12px 30px rgba(0, 0, 0, 0.15)';
|
||||
});
|
||||
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.transform = 'translateY(0)';
|
||||
item.style.boxShadow = 'none';
|
||||
});
|
||||
|
||||
// Border-Farben anwenden
|
||||
const borderElem = item.querySelector('.thought-border');
|
||||
if (borderElem && borderElem.dataset.color) {
|
||||
borderElem.style.borderLeftColor = borderElem.dataset.color;
|
||||
}
|
||||
});
|
||||
|
||||
// Mindmap-Karten mit Hover-Effekten
|
||||
const mindmapItems = document.querySelectorAll('.mindmap-item');
|
||||
mindmapItems.forEach(item => {
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.transform = 'translateY(-5px)';
|
||||
item.style.boxShadow = '0 12px 30px rgba(0, 0, 0, 0.15)';
|
||||
});
|
||||
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.transform = 'translateY(0)';
|
||||
item.style.boxShadow = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Benachrichtigungsfunktion
|
||||
function showNotification(message, type = 'info') {
|
||||
// Bestehende Benachrichtigung entfernen
|
||||
const existingNotification = document.getElementById('notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// Neue Benachrichtigung erstellen
|
||||
const notification = document.createElement('div');
|
||||
notification.id = 'notification';
|
||||
notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 flex items-center transform transition-all duration-300 translate-y-0 opacity-0`;
|
||||
|
||||
// Typ-basierte Stile
|
||||
if (type === 'success') {
|
||||
notification.classList.add('bg-green-500', 'text-white');
|
||||
notification.innerHTML = `<i class="fas fa-check-circle mr-2"></i> ${message}`;
|
||||
} else if (type === 'error') {
|
||||
notification.classList.add('bg-red-500', 'text-white');
|
||||
notification.innerHTML = `<i class="fas fa-exclamation-circle mr-2"></i> ${message}`;
|
||||
} else {
|
||||
notification.classList.add('bg-blue-500', 'text-white');
|
||||
notification.innerHTML = `<i class="fas fa-info-circle mr-2"></i> ${message}`;
|
||||
}
|
||||
|
||||
// Close-Button
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'ml-4 text-white opacity-75 hover:opacity-100';
|
||||
closeBtn.innerHTML = '<i class="fas fa-times"></i>';
|
||||
closeBtn.addEventListener('click', () => {
|
||||
notification.classList.add('opacity-0', 'translate-y-[-10px]');
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
});
|
||||
|
||||
notification.appendChild(closeBtn);
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Animation starten
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-0');
|
||||
notification.classList.add('opacity-100');
|
||||
}, 10);
|
||||
|
||||
// Automatisch ausblenden nach 5 Sekunden
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(notification)) {
|
||||
notification.classList.add('opacity-0', 'translate-y-[-10px]');
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(notification)) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user