Update OpenAI API key and enhance app functionality: Replace the OpenAI API key in the .env file for improved access. Refactor app.py to include error handling for missing API keys and implement dark mode functionality with session management. Update README.md to reflect the use of Tailwind CSS via CDN and document the Content Security Policy (CSP) adjustments. Enhance mindmap data loading with a new API endpoint for refreshing data, ensuring better user experience during database connection issues. Update styles and templates for improved UI consistency and responsiveness.
This commit is contained in:
@@ -405,44 +405,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Category Tree -->
|
||||
<div class="category-tree">
|
||||
<!-- Recursive template for categories -->
|
||||
<script type="text/x-template" id="category-template">
|
||||
<div class="category-item pl-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
:class="[level > 0 ? 'ml-' + (level * 2) : '']"
|
||||
@click="toggleCategory(category.id)">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-solid" :class="[isExpanded ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 text-sm transition-transform']"></i>
|
||||
<span :class="{'font-medium': isActive}">
|
||||
<i class="fa-solid mr-2" :class="category.icon || 'fa-folder'"></i>
|
||||
${category.name}
|
||||
</span>
|
||||
</div>
|
||||
<span class="node-counter">${category.nodes.length}</span>
|
||||
</div>
|
||||
<!-- Nodes for this category -->
|
||||
<div x-show="isExpanded && isActive" class="mt-2 node-list pl-4">
|
||||
<div v-for="node in category.nodes" class="node-item p-2 mb-2"
|
||||
:style="{ borderLeftColor: node.color_code }"
|
||||
@click.stop="addNodeToCanvas(node)">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>${node.name}</div>
|
||||
<span class="node-counter">${node.thought_count}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Subcategories recursive -->
|
||||
<div x-show="isExpanded" class="mt-2">
|
||||
<template v-for="child in category.children">
|
||||
<category-item :category="child" :level="level + 1"></category-item>
|
||||
</template>
|
||||
</div>
|
||||
<div class="category-tree" id="category-tree">
|
||||
<div id="categories-container" class="space-y-1">
|
||||
<!-- Categories will be loaded dynamically -->
|
||||
<div class="py-3 text-center text-gray-500 dark:text-gray-400" id="loading-categories">
|
||||
<div class="spinner mx-auto mb-2"></div>
|
||||
<p>Kategorien werden geladen...</p>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Root categories rendered here -->
|
||||
<div id="categories-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View Mode Toggle -->
|
||||
@@ -474,7 +444,12 @@
|
||||
<!-- User Mindmap List -->
|
||||
<div class="space-y-2 max-h-60 overflow-y-auto mb-3">
|
||||
<!-- Will be populated by JS -->
|
||||
<div id="user-mindmaps-list" class="space-y-2"></div>
|
||||
<div id="user-mindmaps-list" class="space-y-2">
|
||||
<div class="py-3 text-center text-gray-500 dark:text-gray-400" id="loading-mindmaps">
|
||||
<div class="spinner mx-auto mb-2"></div>
|
||||
<p>Mindmaps werden geladen...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add New Mindmap Button -->
|
||||
@@ -510,93 +485,349 @@
|
||||
<!-- Custom D3 Extensions -->
|
||||
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
|
||||
|
||||
<!-- Mindmap Script -->
|
||||
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
|
||||
<!-- Mindmap Visualisierungsmodul -->
|
||||
<script src="{{ url_for('static', filename='js/modules/mindmap.js') }}"></script>
|
||||
|
||||
<script>
|
||||
// Initialize the public mindmap
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Mindmap-Seite wird initialisiert...');
|
||||
|
||||
// Prüfe, ob D3.js geladen ist
|
||||
if (typeof d3 === 'undefined') {
|
||||
console.error('D3.js Bibliothek ist nicht geladen!');
|
||||
document.getElementById('mindmap-container').innerHTML =
|
||||
'<div class="glass-effect p-6 text-center">' +
|
||||
'<div class="text-red-500 mb-4">' +
|
||||
'<i class="fa-solid fa-triangle-exclamation text-4xl"></i>' +
|
||||
'</div>' +
|
||||
'<p class="text-xl">D3.js konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>' +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe, ob MindMapVisualization geladen ist
|
||||
if (typeof MindMapVisualization === 'undefined') {
|
||||
console.error('MindMapVisualization-Klasse ist nicht geladen!');
|
||||
document.getElementById('mindmap-container').innerHTML =
|
||||
'<div class="glass-effect p-6 text-center">' +
|
||||
'<div class="text-red-500 mb-4">' +
|
||||
'<i class="fa-solid fa-triangle-exclamation text-4xl"></i>' +
|
||||
'</div>' +
|
||||
'<p class="text-xl">Die Mindmap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>' +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up for dark/light mode changes
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
|
||||
// Initialize the node tooltip
|
||||
const tooltip = document.getElementById('node-tooltip');
|
||||
|
||||
// Initialize mindmap
|
||||
const mindmap = new MindMap({
|
||||
container: '#mindmap-canvas',
|
||||
apiEndpoint: '/api/mindmap/public',
|
||||
isDarkMode: isDarkMode,
|
||||
tooltip: tooltip
|
||||
});
|
||||
try {
|
||||
console.log('Erstelle MindMapVisualization...');
|
||||
// Mindmap-Visualisierung initialisieren
|
||||
const mindmap = new MindMapVisualization('#mindmap-canvas', {
|
||||
height: document.getElementById('mindmap-container').clientHeight || 600,
|
||||
tooltipEnabled: true,
|
||||
onNodeClick: function(node) {
|
||||
console.log('Knoten geklickt:', node);
|
||||
}
|
||||
});
|
||||
|
||||
// Speichere als globale Instanz für Zugriff über Buttons
|
||||
window.mindmapInstance = mindmap;
|
||||
|
||||
// Lade die Mindmap-Daten
|
||||
mindmap.loadData();
|
||||
console.log('Mindmap-Visualisierung erfolgreich erstellt');
|
||||
|
||||
// Load public mindmap data
|
||||
mindmap.loadData().then(() => {
|
||||
console.log('Mindmap data loaded');
|
||||
});
|
||||
// View mode toggle
|
||||
document.getElementById('view-all').addEventListener('click', function() {
|
||||
this.classList.add('active');
|
||||
document.getElementById('view-focused').classList.remove('active');
|
||||
if (window.mindmapInstance && window.mindmapInstance.setViewMode) {
|
||||
window.mindmapInstance.setViewMode('all');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('view-focused').addEventListener('click', function() {
|
||||
this.classList.add('active');
|
||||
document.getElementById('view-all').classList.remove('active');
|
||||
if (window.mindmapInstance && window.mindmapInstance.setViewMode) {
|
||||
window.mindmapInstance.setViewMode('focus');
|
||||
}
|
||||
});
|
||||
|
||||
// Zoom controls
|
||||
document.getElementById('zoom-in').addEventListener('click', function() {
|
||||
if (window.mindmapInstance) {
|
||||
const svg = d3.select('#mindmap-container svg');
|
||||
const currentZoom = d3.zoomTransform(svg.node());
|
||||
const newScale = currentZoom.k * 1.3;
|
||||
svg.transition().duration(300).call(
|
||||
d3.zoom().transform,
|
||||
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('zoom-out').addEventListener('click', function() {
|
||||
if (window.mindmapInstance) {
|
||||
const svg = d3.select('#mindmap-container svg');
|
||||
const currentZoom = d3.zoomTransform(svg.node());
|
||||
const newScale = currentZoom.k / 1.3;
|
||||
svg.transition().duration(300).call(
|
||||
d3.zoom().transform,
|
||||
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('reset-view').addEventListener('click', function() {
|
||||
if (window.mindmapInstance) {
|
||||
const svg = d3.select('#mindmap-container svg');
|
||||
svg.transition().duration(500).call(
|
||||
d3.zoom().transform,
|
||||
d3.zoomIdentity.scale(1)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle dark mode toggle
|
||||
document.addEventListener('darkModeToggled', function(event) {
|
||||
const isDark = event.detail.isDark;
|
||||
if (window.mindmapInstance && window.mindmapInstance.updateTheme) {
|
||||
window.mindmapInstance.updateTheme(isDark);
|
||||
}
|
||||
});
|
||||
|
||||
// Load categories
|
||||
loadCategories();
|
||||
|
||||
// Search functionality
|
||||
const searchInput = document.getElementById('category-search');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
if (window.mindmapInstance && window.mindmapInstance.filterBySearchTerm) {
|
||||
window.mindmapInstance.filterBySearchTerm(searchTerm);
|
||||
}
|
||||
|
||||
// Filter categories in the sidebar
|
||||
filterCategoriesInSidebar(searchTerm);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Initialisierung der Mindmap:', error);
|
||||
const errorContainer = document.getElementById('mindmap-container');
|
||||
errorContainer.innerHTML = '<div class="glass-effect p-6 text-center">' +
|
||||
'<div class="text-red-500 mb-4">' +
|
||||
'<i class="fa-solid fa-triangle-exclamation text-4xl"></i>' +
|
||||
'</div>' +
|
||||
'<p class="text-xl">Fehler bei der Initialisierung der Mindmap:</p>' +
|
||||
'<p class="text-md mt-2">' + (error.message || 'Unbekannter Fehler') + '</p>' +
|
||||
'</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Kategorien laden
|
||||
function loadCategories() {
|
||||
const categoriesContainer = document.getElementById('categories-container');
|
||||
const loadingElement = document.getElementById('loading-categories');
|
||||
|
||||
// View mode toggle
|
||||
document.getElementById('view-all').addEventListener('click', function() {
|
||||
this.classList.add('active');
|
||||
document.getElementById('view-focused').classList.remove('active');
|
||||
mindmap.setViewMode('all');
|
||||
});
|
||||
|
||||
document.getElementById('view-focused').addEventListener('click', function() {
|
||||
this.classList.add('active');
|
||||
document.getElementById('view-all').classList.remove('active');
|
||||
mindmap.setViewMode('focus');
|
||||
});
|
||||
|
||||
// Zoom controls
|
||||
document.getElementById('zoom-in').addEventListener('click', function() {
|
||||
mindmap.zoomIn();
|
||||
});
|
||||
|
||||
document.getElementById('zoom-out').addEventListener('click', function() {
|
||||
mindmap.zoomOut();
|
||||
});
|
||||
|
||||
document.getElementById('reset-view').addEventListener('click', function() {
|
||||
mindmap.resetView();
|
||||
});
|
||||
|
||||
// Handle dark mode toggle
|
||||
document.addEventListener('darkModeToggled', function(event) {
|
||||
const isDark = event.detail.isDark;
|
||||
mindmap.updateTheme(isDark);
|
||||
});
|
||||
|
||||
// Search functionality
|
||||
const searchInput = document.getElementById('category-search');
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
mindmap.searchCategories(searchTerm);
|
||||
});
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
// Load user mindmaps
|
||||
fetch('/api/mindmap/user')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const mindmapsList = document.getElementById('user-mindmaps-list');
|
||||
mindmapsList.innerHTML = '';
|
||||
fetch('/api/categories')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Kategorien konnten nicht geladen werden');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(categories => {
|
||||
// Loading-Anzeige entfernen
|
||||
if (loadingElement) {
|
||||
loadingElement.remove();
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
mindmapsList.innerHTML = `
|
||||
<div class="text-center text-gray-500 dark:text-gray-400 py-2">
|
||||
Keine Mindmaps gefunden
|
||||
// Kategorien rendern
|
||||
renderCategories(categories, categoriesContainer);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Kategorien:', error);
|
||||
if (loadingElement) {
|
||||
loadingElement.innerHTML = `
|
||||
<div class="text-red-500 mb-2">
|
||||
<i class="fa-solid fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<p>Fehler beim Laden der Kategorien</p>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Kategorien rekursiv rendern
|
||||
function renderCategories(categories, container, level = 0) {
|
||||
categories.forEach(category => {
|
||||
const categoryElement = document.createElement('div');
|
||||
categoryElement.className = 'category-item pl-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700';
|
||||
categoryElement.setAttribute('data-category-id', category.id);
|
||||
categoryElement.style.marginLeft = level > 0 ? `${level * 12}px` : '0';
|
||||
|
||||
const hasChildren = category.children && category.children.length > 0;
|
||||
const hasNodes = category.nodes && category.nodes.length > 0;
|
||||
|
||||
categoryElement.innerHTML = `
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-solid fa-chevron-right mr-2 text-sm transition-transform ${hasChildren ? '' : 'opacity-0'}"></i>
|
||||
<span>
|
||||
<i class="fa-solid ${category.icon || 'fa-folder'} mr-2"></i>
|
||||
${category.name}
|
||||
</span>
|
||||
</div>
|
||||
<span class="node-counter">${hasNodes ? category.nodes.length : 0}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(categoryElement);
|
||||
|
||||
// Bereich für Knoten und Unterkategorien erstellen
|
||||
const expandableArea = document.createElement('div');
|
||||
expandableArea.className = 'hidden mt-2';
|
||||
expandableArea.setAttribute('data-category-expand', category.id);
|
||||
container.appendChild(expandableArea);
|
||||
|
||||
// Knoten für diese Kategorie anzeigen
|
||||
if (hasNodes) {
|
||||
const nodesContainer = document.createElement('div');
|
||||
nodesContainer.className = 'node-list pl-4';
|
||||
|
||||
category.nodes.forEach(node => {
|
||||
const nodeItem = document.createElement('div');
|
||||
nodeItem.className = 'node-item p-2 mb-2';
|
||||
nodeItem.style.borderLeft = `3px solid ${node.color_code || '#9F7AEA'}`;
|
||||
nodeItem.setAttribute('data-node-id', node.id);
|
||||
|
||||
nodeItem.innerHTML = `
|
||||
<div class="flex items-center justify-between">
|
||||
<div>${node.name}</div>
|
||||
<span class="node-counter">${node.thought_count || 0}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
nodeItem.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (window.mindmapInstance && window.mindmapInstance.focusNode) {
|
||||
window.mindmapInstance.focusNode(node.id);
|
||||
}
|
||||
});
|
||||
|
||||
nodesContainer.appendChild(nodeItem);
|
||||
});
|
||||
|
||||
expandableArea.appendChild(nodesContainer);
|
||||
}
|
||||
|
||||
// Unterkategorien rekursiv rendern
|
||||
if (hasChildren) {
|
||||
const childrenContainer = document.createElement('div');
|
||||
childrenContainer.className = 'mt-2';
|
||||
expandableArea.appendChild(childrenContainer);
|
||||
|
||||
renderCategories(category.children, childrenContainer, level + 1);
|
||||
}
|
||||
|
||||
// Event-Listener für Aufklappen/Zuklappen
|
||||
categoryElement.addEventListener('click', function() {
|
||||
const expandArea = document.querySelector(`[data-category-expand="${category.id}"]`);
|
||||
const chevron = this.querySelector('.fa-chevron-right');
|
||||
|
||||
if (expandArea) {
|
||||
if (expandArea.classList.contains('hidden')) {
|
||||
expandArea.classList.remove('hidden');
|
||||
if (chevron) chevron.style.transform = 'rotate(90deg)';
|
||||
} else {
|
||||
expandArea.classList.add('hidden');
|
||||
if (chevron) chevron.style.transform = 'rotate(0)';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Categories filtering
|
||||
function filterCategoriesInSidebar(searchTerm) {
|
||||
if (!searchTerm) {
|
||||
// Show all categories
|
||||
document.querySelectorAll('.category-item').forEach(el => {
|
||||
el.style.display = '';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide/show categories based on search
|
||||
document.querySelectorAll('.category-item').forEach(el => {
|
||||
const categoryName = el.querySelector('span').textContent.trim().toLowerCase();
|
||||
if (categoryName.includes(searchTerm)) {
|
||||
el.style.display = '';
|
||||
|
||||
// Show parent categories
|
||||
let parent = el.parentElement;
|
||||
while (parent && !parent.matches('#categories-container')) {
|
||||
if (parent.hasAttribute('data-category-expand')) {
|
||||
parent.classList.remove('hidden');
|
||||
const parentId = parent.getAttribute('data-category-expand');
|
||||
const parentCategory = document.querySelector(`[data-category-id="${parentId}"]`);
|
||||
if (parentCategory) {
|
||||
const chevron = parentCategory.querySelector('.fa-chevron-right');
|
||||
if (chevron) chevron.style.transform = 'rotate(90deg)';
|
||||
}
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- Script für eingeloggte Benutzer -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load user mindmaps
|
||||
const mindmapsList = document.getElementById('user-mindmaps-list');
|
||||
const loadingMindmaps = document.getElementById('loading-mindmaps');
|
||||
|
||||
fetch('/api/mindmap/user')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Benutzer-Mindmaps konnten nicht geladen werden');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (!mindmapsList) return;
|
||||
|
||||
// Loading-Anzeige entfernen
|
||||
if (loadingMindmaps) {
|
||||
loadingMindmaps.remove();
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
mindmapsList.innerHTML = '<div class="text-center text-gray-500 dark:text-gray-400 py-2">Keine Mindmaps gefunden</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Mindmaps anzeigen
|
||||
data.forEach(mindmap => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'user-mindmap-item p-3';
|
||||
item.innerHTML = `
|
||||
<div class="font-medium text-gray-800 dark:text-gray-200">${mindmap.name}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-2">${mindmap.description}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-2">${mindmap.description || ''}</div>
|
||||
<a href="/my-mindmap/${mindmap.id}" class="text-purple-600 dark:text-purple-400 text-xs hover:underline">
|
||||
<i class="fa-solid fa-arrow-right mr-1"></i> Öffnen
|
||||
</a>
|
||||
@@ -605,9 +836,17 @@
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading user mindmaps:', error);
|
||||
console.error('Fehler beim Laden der Benutzer-Mindmaps:', error);
|
||||
if (loadingMindmaps) {
|
||||
loadingMindmaps.innerHTML = `
|
||||
<div class="text-red-500 mb-2">
|
||||
<i class="fa-solid fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<p>Fehler beim Laden der Mindmaps</p>
|
||||
`;
|
||||
}
|
||||
});
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user