Add user account route and bookmark functionality: Implement '/my-account' route for user bookmarks and personal mindmap, enhance mindmap visualization with bookmark management, and update UI elements for better user experience.
This commit is contained in:
@@ -822,6 +822,12 @@ def chat_with_assistant():
|
|||||||
print(f"Fehler bei der KI-Anfrage: {str(e)}")
|
print(f"Fehler bei der KI-Anfrage: {str(e)}")
|
||||||
return jsonify({"success": False, "error": "Fehler bei der Verarbeitung der Anfrage"}), 500
|
return jsonify({"success": False, "error": "Fehler bei der Verarbeitung der Anfrage"}), 500
|
||||||
|
|
||||||
|
# Route für Benutzer-Merkliste und persönliche Mindmap
|
||||||
|
@app.route('/my-account')
|
||||||
|
@login_required
|
||||||
|
def my_account():
|
||||||
|
return render_template('my_account.html', current_year=current_year)
|
||||||
|
|
||||||
# Flask starten
|
# Flask starten
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class MindMapVisualization {
|
|||||||
this.tooltipDiv = null;
|
this.tooltipDiv = null;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
|
// Lade die gemerkten Knoten
|
||||||
|
this.bookmarkedNodes = this.loadBookmarkedNodes();
|
||||||
|
|
||||||
// Sicherstellen, dass der Container bereit ist
|
// Sicherstellen, dass der Container bereit ist
|
||||||
if (this.container.node()) {
|
if (this.container.node()) {
|
||||||
this.init();
|
this.init();
|
||||||
@@ -154,10 +157,13 @@ class MindMapVisualization {
|
|||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.updateVisualization();
|
this.updateVisualization();
|
||||||
|
|
||||||
// API-Aufruf mit längeren Timeout im Hintergrund durchführen
|
// Status auf bereit setzen - don't wait for API
|
||||||
|
this.container.attr('data-status', 'ready');
|
||||||
|
|
||||||
|
// API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 Sekunden Timeout
|
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout - reduced from 10
|
||||||
|
|
||||||
const response = await fetch('/api/mindmap', {
|
const response = await fetch('/api/mindmap', {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
@@ -169,14 +175,15 @@ class MindMapVisualization {
|
|||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP Fehler: ${response.status}`);
|
console.warn(`HTTP Fehler: ${response.status}, verwende Standarddaten`);
|
||||||
|
return; // Keep using default data
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!data || !data.nodes || data.nodes.length === 0) {
|
if (!data || !data.nodes || data.nodes.length === 0) {
|
||||||
console.warn('Keine Mindmap-Daten vorhanden, verwende weiterhin Standard-Daten.');
|
console.warn('Keine Mindmap-Daten vorhanden, verwende weiterhin Standard-Daten.');
|
||||||
return; // Behalte Standarddaten bei
|
return; // Keep using default data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flache Liste von Knoten und Verbindungen erstellen
|
// Flache Liste von Knoten und Verbindungen erstellen
|
||||||
@@ -187,14 +194,9 @@ class MindMapVisualization {
|
|||||||
// Visualisierung aktualisieren mit den tatsächlichen Daten
|
// Visualisierung aktualisieren mit den tatsächlichen Daten
|
||||||
this.updateVisualization();
|
this.updateVisualization();
|
||||||
|
|
||||||
// Status auf bereit setzen
|
|
||||||
this.container.attr('data-status', 'ready');
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Fehler beim Laden der Mindmap-Daten, verwende Standarddaten:', error);
|
console.warn('Fehler beim Laden der Mindmap-Daten, verwende Standarddaten:', error);
|
||||||
// Fallback zu Standarddaten ist bereits geschehen
|
// Already using default data, no action needed
|
||||||
// Stellen Sie sicher, dass der Status korrekt gesetzt wird
|
|
||||||
this.container.attr('data-status', 'ready');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -366,11 +368,11 @@ class MindMapVisualization {
|
|||||||
|
|
||||||
// Kreis für jeden Knoten
|
// Kreis für jeden Knoten
|
||||||
group.append('circle')
|
group.append('circle')
|
||||||
.attr('class', 'node')
|
.attr('class', d => `node ${this.isNodeBookmarked(d.id) ? 'bookmarked' : ''}`)
|
||||||
.attr('r', this.nodeRadius)
|
.attr('r', this.nodeRadius)
|
||||||
.attr('fill', d => d.color || this.generateColorFromString(d.name))
|
.attr('fill', d => d.color || this.generateColorFromString(d.name))
|
||||||
.attr('stroke', '#ffffff50')
|
.attr('stroke', d => this.isNodeBookmarked(d.id) ? '#FFD700' : '#ffffff50')
|
||||||
.attr('stroke-width', 2)
|
.attr('stroke-width', d => this.isNodeBookmarked(d.id) ? 3 : 2)
|
||||||
.attr('filter', 'url(#glow)');
|
.attr('filter', 'url(#glow)');
|
||||||
|
|
||||||
// Text-Label mit besserem Kontrast
|
// Text-Label mit besserem Kontrast
|
||||||
@@ -398,7 +400,10 @@ class MindMapVisualization {
|
|||||||
update => {
|
update => {
|
||||||
// Knoten aktualisieren
|
// Knoten aktualisieren
|
||||||
update.select('.node')
|
update.select('.node')
|
||||||
.attr('fill', d => d.color || this.generateColorFromString(d.name));
|
.attr('class', d => `node ${this.isNodeBookmarked(d.id) ? 'bookmarked' : ''}`)
|
||||||
|
.attr('fill', d => d.color || this.generateColorFromString(d.name))
|
||||||
|
.attr('stroke', d => this.isNodeBookmarked(d.id) ? '#FFD700' : '#ffffff50')
|
||||||
|
.attr('stroke-width', d => this.isNodeBookmarked(d.id) ? 3 : 2);
|
||||||
|
|
||||||
// Text aktualisieren
|
// Text aktualisieren
|
||||||
update.select('.node-label')
|
update.select('.node-label')
|
||||||
@@ -465,6 +470,7 @@ class MindMapVisualization {
|
|||||||
|
|
||||||
// Tooltip anzeigen
|
// Tooltip anzeigen
|
||||||
if (this.tooltipEnabled) {
|
if (this.tooltipEnabled) {
|
||||||
|
const isBookmarked = this.isNodeBookmarked(d.id);
|
||||||
const tooltipContent = `
|
const tooltipContent = `
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<strong>${d.name}</strong>
|
<strong>${d.name}</strong>
|
||||||
@@ -472,6 +478,12 @@ class MindMapVisualization {
|
|||||||
<div class="text-xs text-gray-300 mt-1">
|
<div class="text-xs text-gray-300 mt-1">
|
||||||
Gedanken: ${d.thought_count}
|
Gedanken: ${d.thought_count}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<button id="bookmark-button" class="px-2 py-1 text-xs rounded bg-gray-700 hover:bg-gray-600 text-white"
|
||||||
|
data-nodeid="${d.id}">
|
||||||
|
${isBookmarked ? '<i class="fas fa-bookmark mr-1"></i> Gemerkt' : '<i class="far fa-bookmark mr-1"></i> Merken'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -482,6 +494,20 @@ class MindMapVisualization {
|
|||||||
.transition()
|
.transition()
|
||||||
.duration(200)
|
.duration(200)
|
||||||
.style('opacity', 1);
|
.style('opacity', 1);
|
||||||
|
|
||||||
|
// Event-Listener für den Bookmark-Button hinzufügen
|
||||||
|
document.getElementById('bookmark-button').addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const nodeId = e.currentTarget.getAttribute('data-nodeid');
|
||||||
|
const isNowBookmarked = this.toggleBookmark(nodeId);
|
||||||
|
|
||||||
|
// Button-Text aktualisieren
|
||||||
|
if (isNowBookmarked) {
|
||||||
|
e.currentTarget.innerHTML = '<i class="fas fa-bookmark mr-1"></i> Gemerkt';
|
||||||
|
} else {
|
||||||
|
e.currentTarget.innerHTML = '<i class="far fa-bookmark mr-1"></i> Merken';
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Knoten visuell hervorheben
|
// Knoten visuell hervorheben
|
||||||
@@ -489,7 +515,7 @@ class MindMapVisualization {
|
|||||||
.transition()
|
.transition()
|
||||||
.duration(200)
|
.duration(200)
|
||||||
.attr('r', this.nodeRadius * 1.2)
|
.attr('r', this.nodeRadius * 1.2)
|
||||||
.attr('stroke', '#ffffff');
|
.attr('stroke', this.isNodeBookmarked(d.id) ? '#FFD700' : '#ffffff');
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeMouseout(event, d) {
|
nodeMouseout(event, d) {
|
||||||
@@ -506,11 +532,13 @@ class MindMapVisualization {
|
|||||||
// Knoten-Stil zurücksetzen, wenn nicht ausgewählt
|
// Knoten-Stil zurücksetzen, wenn nicht ausgewählt
|
||||||
const nodeElement = d3.select(event.currentTarget).select('circle');
|
const nodeElement = d3.select(event.currentTarget).select('circle');
|
||||||
if (d !== this.selectedNode) {
|
if (d !== this.selectedNode) {
|
||||||
|
const isBookmarked = this.isNodeBookmarked(d.id);
|
||||||
nodeElement
|
nodeElement
|
||||||
.transition()
|
.transition()
|
||||||
.duration(200)
|
.duration(200)
|
||||||
.attr('r', this.nodeRadius)
|
.attr('r', this.nodeRadius)
|
||||||
.attr('stroke', '#ffffff50');
|
.attr('stroke', isBookmarked ? '#FFD700' : '#ffffff50')
|
||||||
|
.attr('stroke-width', isBookmarked ? 3 : 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,6 +699,78 @@ class MindMapVisualization {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lädt gemerkete Knoten aus dem LocalStorage
|
||||||
|
loadBookmarkedNodes() {
|
||||||
|
try {
|
||||||
|
const bookmarked = localStorage.getItem('bookmarkedNodes');
|
||||||
|
return bookmarked ? JSON.parse(bookmarked) : [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der gemerkten Knoten:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speichert gemerkete Knoten im LocalStorage
|
||||||
|
saveBookmarkedNodes() {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('bookmarkedNodes', JSON.stringify(this.bookmarkedNodes));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Speichern der gemerkten Knoten:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüft, ob ein Knoten gemerkt ist
|
||||||
|
isNodeBookmarked(nodeId) {
|
||||||
|
return this.bookmarkedNodes.includes(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merkt einen Knoten oder hebt die Markierung auf
|
||||||
|
toggleBookmark(nodeId) {
|
||||||
|
const index = this.bookmarkedNodes.indexOf(nodeId);
|
||||||
|
if (index === -1) {
|
||||||
|
// Node hinzufügen
|
||||||
|
this.bookmarkedNodes.push(nodeId);
|
||||||
|
this.updateNodeAppearance(nodeId, true);
|
||||||
|
} else {
|
||||||
|
// Node entfernen
|
||||||
|
this.bookmarkedNodes.splice(index, 1);
|
||||||
|
this.updateNodeAppearance(nodeId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Änderungen speichern
|
||||||
|
this.saveBookmarkedNodes();
|
||||||
|
|
||||||
|
// Event auslösen für andere Komponenten
|
||||||
|
const event = new CustomEvent('nodeBookmarkToggled', {
|
||||||
|
detail: {
|
||||||
|
nodeId: nodeId,
|
||||||
|
isBookmarked: index === -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
return index === -1; // true wenn jetzt gemerkt, false wenn Markierung aufgehoben
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiert das Aussehen eines Knotens basierend auf Bookmark-Status
|
||||||
|
updateNodeAppearance(nodeId, isBookmarked) {
|
||||||
|
this.g.selectAll('.node-group')
|
||||||
|
.filter(d => d.id === nodeId)
|
||||||
|
.select('.node')
|
||||||
|
.classed('bookmarked', isBookmarked)
|
||||||
|
.attr('stroke', isBookmarked ? '#FFD700' : '#ffffff50')
|
||||||
|
.attr('stroke-width', isBookmarked ? 3 : 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiert das Aussehen aller gemerkten Knoten
|
||||||
|
updateAllBookmarkedNodes() {
|
||||||
|
this.g.selectAll('.node-group')
|
||||||
|
.each((d) => {
|
||||||
|
const isBookmarked = this.isNodeBookmarked(d.id);
|
||||||
|
this.updateNodeAppearance(d.id, isBookmarked);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exportiere die Klasse für die Verwendung in anderen Modulen
|
// Exportiere die Klasse für die Verwendung in anderen Modulen
|
||||||
|
|||||||
@@ -601,7 +601,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Benutzermenü oder Login -->
|
<!-- Profil-Link oder Login -->
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<div class="relative" x-data="{ open: false }">
|
<div class="relative" x-data="{ open: false }">
|
||||||
<button @click="open = !open"
|
<button @click="open = !open"
|
||||||
@@ -642,6 +642,13 @@
|
|||||||
: 'text-gray-700 hover:bg-purple-500/10'">
|
: 'text-gray-700 hover:bg-purple-500/10'">
|
||||||
<i class="fa-solid fa-user mr-2 text-purple-400"></i>Profil
|
<i class="fa-solid fa-user mr-2 text-purple-400"></i>Profil
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ url_for('my_account') }}"
|
||||||
|
class="block px-4 py-3 transition-colors duration-200 flex items-center"
|
||||||
|
x-bind:class="darkMode
|
||||||
|
? 'text-white/90 hover:bg-purple-500/20'
|
||||||
|
: 'text-gray-700 hover:bg-purple-500/10'">
|
||||||
|
<i class="fa-solid fa-bookmark mr-2 text-purple-400"></i>Meine Merkliste
|
||||||
|
</a>
|
||||||
<a href="{{ url_for('settings') }}"
|
<a href="{{ url_for('settings') }}"
|
||||||
class="block px-4 py-3 transition-colors duration-200 flex items-center"
|
class="block px-4 py-3 transition-colors duration-200 flex items-center"
|
||||||
x-bind:class="darkMode
|
x-bind:class="darkMode
|
||||||
@@ -663,9 +670,9 @@
|
|||||||
<a href="{{ url_for('login') }}"
|
<a href="{{ url_for('login') }}"
|
||||||
class="flex items-center px-4 py-2.5 rounded-xl font-medium transition-all duration-300"
|
class="flex items-center px-4 py-2.5 rounded-xl font-medium transition-all duration-300"
|
||||||
x-bind:class="darkMode
|
x-bind:class="darkMode
|
||||||
? 'bg-purple-500/30 text-white hover:bg-purple-600/40 shadow-md hover:shadow-lg hover:-translate-y-0.5'
|
? 'bg-gray-800/80 text-white hover:bg-gray-700/80 shadow-md hover:shadow-lg hover:-translate-y-0.5'
|
||||||
: 'bg-purple-500/20 text-gray-800 hover:bg-purple-500/30 shadow-sm hover:shadow-md hover:-translate-y-0.5'">
|
: 'bg-gray-200/80 text-gray-800 hover:bg-gray-300/80 shadow-sm hover:shadow-md hover:-translate-y-0.5'">
|
||||||
<i class="fa-solid fa-right-to-bracket mr-2"></i>Anmelden
|
<i class="fa-solid fa-user mr-2"></i>Mein Konto
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -859,6 +859,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Aktualisiere das Aussehen von Bookmarks, sobald die Mindmap vollständig geladen ist
|
||||||
|
setTimeout(() => {
|
||||||
|
if (mindmap && typeof mindmap.updateAllBookmarkedNodes === 'function') {
|
||||||
|
mindmap.updateAllBookmarkedNodes();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Animationen für die Hintergrundeffekte
|
// Animationen für die Hintergrundeffekte
|
||||||
|
|||||||
320
website/templates/my_account.html
Normal file
320
website/templates/my_account.html
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Meine Merkliste{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
/* Hintergrund über die gesamte Seite erstrecken */
|
||||||
|
html, body {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-mindmap-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 1rem;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .personal-mindmap-container {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-item {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-item:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .empty-state {
|
||||||
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Hauptbereich -->
|
||||||
|
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 pt-8 pb-12">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
|
<span class="gradient-text">Meine Merkliste</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-gray-700 dark:text-gray-300">
|
||||||
|
Deine persönliche Sammlung gemerkter Wissensbereiche und Beiträge
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Persönliche Mindmap -->
|
||||||
|
<div class="mb-12">
|
||||||
|
<h2 class="text-xl font-bold text-gray-800 dark:text-white mb-4 flex items-center">
|
||||||
|
<i class="fas fa-project-diagram mr-3 text-purple-500"></i>
|
||||||
|
Meine Mindmap
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div id="personal-mindmap" class="personal-mindmap-container glass-morphism">
|
||||||
|
<div id="empty-mindmap-message" class="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div class="text-center p-6">
|
||||||
|
<div class="text-6xl mb-4 opacity-20">
|
||||||
|
<i class="fas fa-bookmark"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold mb-2 text-gray-700 dark:text-gray-300">Deine persönliche Mindmap ist leer</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 max-w-md mx-auto">
|
||||||
|
Merke dir Wissensbereiche und Gedanken in der Hauptmindmap, um deine persönliche Mindmap zu erstellen.
|
||||||
|
</p>
|
||||||
|
<a href="{{ url_for('mindmap') }}" class="mt-4 inline-block px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors">
|
||||||
|
Zur Mindmap
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gemerkte Inhalte -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
|
||||||
|
<!-- Wissensbereiche -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-800 dark:text-white mb-4 flex items-center">
|
||||||
|
<i class="fas fa-folder-open mr-3 text-blue-500"></i>
|
||||||
|
Gemerkte Wissensbereiche
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div id="bookmarked-areas-container" class="space-y-4">
|
||||||
|
<!-- Ladezustand -->
|
||||||
|
<div class="animate-pulse space-y-4">
|
||||||
|
<div class="h-16 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
|
||||||
|
<div class="h-16 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
|
||||||
|
<div class="h-16 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gedanken -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-800 dark:text-white mb-4 flex items-center">
|
||||||
|
<i class="fas fa-lightbulb mr-3 text-amber-500"></i>
|
||||||
|
Gemerkte Gedanken
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div id="bookmarked-thoughts-container" class="space-y-4">
|
||||||
|
<!-- Ladezustand -->
|
||||||
|
<div class="animate-pulse space-y-4">
|
||||||
|
<div class="h-16 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
|
||||||
|
<div class="h-16 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
|
||||||
|
<div class="h-16 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript für persönliche Mindmap -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Lade gespeicherte Bookmarks aus dem LocalStorage
|
||||||
|
function loadBookmarkedNodes() {
|
||||||
|
try {
|
||||||
|
const bookmarked = localStorage.getItem('bookmarkedNodes');
|
||||||
|
return bookmarked ? JSON.parse(bookmarked) : [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der gemerkten Knoten:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookmarkedNodeIds = loadBookmarkedNodes();
|
||||||
|
|
||||||
|
// Prüfe, ob es gemerkte Knoten gibt
|
||||||
|
if (bookmarkedNodeIds && bookmarkedNodeIds.length > 0) {
|
||||||
|
// Verstecke die Leer-Nachricht
|
||||||
|
document.getElementById('empty-mindmap-message').style.display = 'none';
|
||||||
|
|
||||||
|
// Initialisiere die persönliche Mindmap
|
||||||
|
const personalMindmap = new MindMapVisualization('#personal-mindmap', {
|
||||||
|
width: document.getElementById('personal-mindmap').clientWidth,
|
||||||
|
height: 400,
|
||||||
|
nodeRadius: 18,
|
||||||
|
selectedNodeRadius: 22,
|
||||||
|
linkDistance: 120,
|
||||||
|
chargeStrength: -800,
|
||||||
|
centerForce: 0.1,
|
||||||
|
tooltipEnabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lade Daten für die Mindmap - hier müssten wir normalerweise die API nutzen,
|
||||||
|
// aber als Fallback laden wir die Standarddaten und filtern sie
|
||||||
|
window.setTimeout(() => {
|
||||||
|
// Wenn die Hauptmindmap geladen ist, können wir deren Daten verwenden
|
||||||
|
if (window.mindmapInstance) {
|
||||||
|
// Filtere nur gemerkte Knoten und ihre Verbindungen
|
||||||
|
const nodes = window.mindmapInstance.nodes.filter(node =>
|
||||||
|
bookmarkedNodeIds.includes(node.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Finde Verbindungen zwischen den gemerkten Knoten
|
||||||
|
const links = window.mindmapInstance.links.filter(link =>
|
||||||
|
bookmarkedNodeIds.includes(link.source.id || link.source) &&
|
||||||
|
bookmarkedNodeIds.includes(link.target.id || link.target)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setze Daten und aktualisiere die Visualisierung
|
||||||
|
personalMindmap.nodes = nodes;
|
||||||
|
personalMindmap.links = links;
|
||||||
|
personalMindmap.isLoading = false;
|
||||||
|
personalMindmap.updateVisualization();
|
||||||
|
} else {
|
||||||
|
// Fallback: Leere Mindmap anzeigen
|
||||||
|
document.getElementById('empty-mindmap-message').style.display = 'flex';
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
|
||||||
|
// Lade die gemerkten Inhalte
|
||||||
|
loadBookmarkedContent(bookmarkedNodeIds);
|
||||||
|
} else {
|
||||||
|
// Zeige Leerzustand an
|
||||||
|
const areasContainer = document.getElementById('bookmarked-areas-container');
|
||||||
|
const thoughtsContainer = document.getElementById('bookmarked-thoughts-container');
|
||||||
|
|
||||||
|
areasContainer.innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="text-4xl mb-2 opacity-20">
|
||||||
|
<i class="fas fa-folder-open"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">Keine gemerkten Wissensbereiche</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
thoughtsContainer.innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="text-4xl mb-2 opacity-20">
|
||||||
|
<i class="fas fa-lightbulb"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">Keine gemerkten Gedanken</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Funktion zum Laden der gemerkten Inhalte
|
||||||
|
function loadBookmarkedContent(nodeIds) {
|
||||||
|
if (!nodeIds || nodeIds.length === 0) return;
|
||||||
|
|
||||||
|
// In einer vollständigen Implementierung würden wir hier einen API-Aufruf machen
|
||||||
|
// Für diese Demo erstellen wir Beispielinhalte
|
||||||
|
|
||||||
|
const areasContainer = document.getElementById('bookmarked-areas-container');
|
||||||
|
const thoughtsContainer = document.getElementById('bookmarked-thoughts-container');
|
||||||
|
|
||||||
|
// Verschiedene Beispiel-Farben
|
||||||
|
const colors = ['purple', 'blue', 'green', 'indigo', 'amber'];
|
||||||
|
|
||||||
|
// Leere die Container
|
||||||
|
areasContainer.innerHTML = '';
|
||||||
|
thoughtsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Beispielinhalte für Wissensbereiche
|
||||||
|
const areaTemplates = [
|
||||||
|
{ name: 'Philosophie', description: 'Grundlagen philosophischen Denkens', count: 24 },
|
||||||
|
{ name: 'Wissenschaft', description: 'Wissenschaftliche Methoden und Erkenntnisse', count: 42 },
|
||||||
|
{ name: 'Technologie', description: 'Zukunftsweisende Technologien', count: 36 },
|
||||||
|
{ name: 'Kunst', description: 'Künstlerische Ausdrucksformen', count: 18 },
|
||||||
|
{ name: 'Psychologie', description: 'Menschliches Verhalten verstehen', count: 30 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Beispielinhalte für Gedanken
|
||||||
|
const thoughtTemplates = [
|
||||||
|
{ title: 'Quantenphysik und Bewusstsein', author: 'Maria Schmidt', date: '12.04.2023' },
|
||||||
|
{ title: 'Ethik in der künstlichen Intelligenz', author: 'Thomas Weber', date: '23.02.2023' },
|
||||||
|
{ title: 'Die Rolle der Kunst in der Gesellschaft', author: 'Lena Müller', date: '05.06.2023' },
|
||||||
|
{ title: 'Nachhaltige Entwicklung im 21. Jahrhundert', author: 'Michael Bauer', date: '18.08.2023' },
|
||||||
|
{ title: 'Kognitive Verzerrungen im Alltag', author: 'Sophie Klein', date: '30.09.2023' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Zeige nur so viele Elemente wie wir Knoten haben (bis zu 5)
|
||||||
|
const areaCount = Math.min(nodeIds.length, 5);
|
||||||
|
|
||||||
|
if (areaCount > 0) {
|
||||||
|
// Wissensbereiche hinzufügen
|
||||||
|
for (let i = 0; i < areaCount; i++) {
|
||||||
|
const area = areaTemplates[i];
|
||||||
|
const colorClass = colors[i % colors.length];
|
||||||
|
|
||||||
|
areasContainer.innerHTML += `
|
||||||
|
<a href="{{ url_for('mindmap') }}" class="bookmark-item block p-4 rounded-xl bg-white/80 dark:bg-gray-800/80 shadow-sm hover:shadow-md transition-all">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-${colorClass}-100 dark:bg-${colorClass}-900/30 flex items-center justify-center mr-3">
|
||||||
|
<i class="fas fa-bookmark text-${colorClass}-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<h3 class="font-semibold text-gray-900 dark:text-white">${area.name}</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">${area.description}</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
${area.count} Einträge
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
areasContainer.innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="text-4xl mb-2 opacity-20">
|
||||||
|
<i class="fas fa-folder-open"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">Keine gemerkten Wissensbereiche</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeige bis zu 5 Gedanken an (oder weniger, falls weniger gemerkt wurden)
|
||||||
|
const thoughtCount = Math.min(nodeIds.length, 5);
|
||||||
|
|
||||||
|
if (thoughtCount > 0) {
|
||||||
|
// Gedanken hinzufügen
|
||||||
|
for (let i = 0; i < thoughtCount; i++) {
|
||||||
|
const thought = thoughtTemplates[i];
|
||||||
|
const colorClass = colors[(i + 2) % colors.length]; // Andere Farben als für Bereiche
|
||||||
|
|
||||||
|
thoughtsContainer.innerHTML += `
|
||||||
|
<a href="{{ url_for('mindmap') }}" class="bookmark-item block p-4 rounded-xl bg-white/80 dark:bg-gray-800/80 shadow-sm hover:shadow-md transition-all">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-${colorClass}-100 dark:bg-${colorClass}-900/30 flex items-center justify-center mr-3">
|
||||||
|
<i class="fas fa-lightbulb text-${colorClass}-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<h3 class="font-semibold text-gray-900 dark:text-white">${thought.title}</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">Von ${thought.author} • ${thought.date}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thoughtsContainer.innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="text-4xl mb-2 opacity-20">
|
||||||
|
<i class="fas fa-lightbulb"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">Keine gemerkten Gedanken</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user