Files
website/templates/mindmap.html

852 lines
25 KiB
HTML

{% extends "base.html" %}
{% block title %}Mindmap{% endblock %}
{% block extra_css %}
<style>
/* Full page background */
html, body {
min-height: 100vh;
width: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
/* Mindmap Container */
#mindmap-container {
position: relative;
min-height: calc(100vh - 160px);
z-index: 1;
}
/* Control Panel */
.control-panel {
position: fixed;
top: 100px;
left: 20px;
z-index: 10;
transition: all 0.3s ease;
border-radius: 1rem;
overflow: hidden;
border: 1px solid;
}
.dark .control-panel {
background-color: rgba(17, 24, 39, 0.8);
border-color: rgba(109, 40, 217, 0.3);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.control-panel {
background-color: rgba(255, 255, 255, 0.85);
border-color: rgba(139, 92, 246, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* Control Panel Toggle */
.panel-toggle {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
}
.dark .panel-toggle {
background-color: rgba(109, 40, 217, 0.2);
color: rgba(255, 255, 255, 0.8);
}
.panel-toggle {
background-color: rgba(139, 92, 246, 0.1);
color: rgba(30, 41, 59, 0.8);
}
.panel-toggle:hover {
transform: rotate(180deg);
}
/* Category Tree */
.category-tree {
max-height: 70vh;
overflow-y: auto;
}
.category-item {
transition: all 0.3s ease;
border-left: 2px solid transparent;
cursor: pointer;
}
.dark .category-item:hover {
background-color: rgba(109, 40, 217, 0.1);
border-left-color: rgba(139, 92, 246, 0.5);
}
.category-item:hover {
background-color: rgba(139, 92, 246, 0.05);
border-left-color: rgba(139, 92, 246, 0.5);
}
/* Node List */
.node-list {
max-height: 300px;
overflow-y: auto;
}
.node-item {
transition: all 0.3s ease;
border-radius: 0.5rem;
cursor: pointer;
}
.dark .node-item {
background-color: rgba(31, 41, 55, 0.5);
border: 1px solid rgba(55, 65, 81, 0.5);
}
.node-item {
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid rgba(226, 232, 240, 0.5);
}
.dark .node-item:hover {
background-color: rgba(55, 65, 81, 0.7);
border-color: rgba(139, 92, 246, 0.5);
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.node-item:hover {
background-color: rgba(255, 255, 255, 0.8);
border-color: rgba(139, 92, 246, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
/* Node Counter Badge */
.node-counter {
min-width: 20px;
height: 20px;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 500;
padding: 0 6px;
}
.dark .node-counter {
background-color: rgba(109, 40, 217, 0.3);
color: rgba(255, 255, 255, 0.9);
}
.node-counter {
background-color: rgba(139, 92, 246, 0.1);
color: rgba(109, 40, 217, 0.9);
}
/* Canvas area */
#mindmap-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 1;
}
/* Tooltip */
.tooltip-container {
position: absolute;
pointer-events: none;
z-index: 1000;
max-width: 300px;
opacity: 0;
transition: opacity 0.3s ease;
}
.dark .tooltip-container {
background-color: rgba(17, 24, 39, 0.9);
border: 1px solid rgba(109, 40, 217, 0.3);
color: rgba(255, 255, 255, 0.9);
}
.tooltip-container {
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(139, 92, 246, 0.2);
color: rgba(30, 41, 59, 0.9);
}
/* Search input */
.search-input {
transition: all 0.3s ease;
width: 100%;
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
outline: none;
}
.dark .search-input {
background-color: rgba(31, 41, 55, 0.7);
border: 1px solid rgba(55, 65, 81, 0.5);
color: rgba(255, 255, 255, 0.9);
}
.search-input {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(226, 232, 240, 0.8);
color: rgba(30, 41, 59, 0.9);
}
.dark .search-input:focus {
border-color: rgba(139, 92, 246, 0.5);
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
}
.search-input:focus {
border-color: rgba(139, 92, 246, 0.3);
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1);
}
/* Mode toggle */
.mode-toggle {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
}
.dark .mode-toggle {
background-color: rgba(31, 41, 55, 0.5);
border: 1px solid rgba(55, 65, 81, 0.5);
color: rgba(255, 255, 255, 0.9);
}
.mode-toggle {
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(226, 232, 240, 0.8);
color: rgba(30, 41, 59, 0.9);
}
.dark .mode-toggle.active {
background-color: rgba(109, 40, 217, 0.2);
border-color: rgba(139, 92, 246, 0.4);
color: rgba(255, 255, 255, 1);
}
.mode-toggle.active {
background-color: rgba(139, 92, 246, 0.1);
border-color: rgba(139, 92, 246, 0.3);
color: rgba(109, 40, 217, 1);
}
/* User Mindmaps */
.user-mindmap-section {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10;
transition: all 0.3s ease;
border-radius: 1rem;
overflow: hidden;
max-width: 350px;
}
.dark .user-mindmap-section {
background-color: rgba(17, 24, 39, 0.85);
border: 1px solid rgba(109, 40, 217, 0.3);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.user-mindmap-section {
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(139, 92, 246, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* User Mindmap List */
.user-mindmap-item {
transition: all 0.3s ease;
border-radius: 0.5rem;
cursor: pointer;
}
.dark .user-mindmap-item {
background-color: rgba(31, 41, 55, 0.5);
border: 1px solid rgba(55, 65, 81, 0.5);
}
.user-mindmap-item {
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(226, 232, 240, 0.7);
}
.dark .user-mindmap-item:hover {
background-color: rgba(55, 65, 81, 0.7);
border-color: rgba(139, 92, 246, 0.4);
transform: translateY(-2px);
}
.user-mindmap-item:hover {
background-color: rgba(255, 255, 255, 0.9);
border-color: rgba(139, 92, 246, 0.3);
transform: translateY(-2px);
}
/* Zoom Controls */
.zoom-controls {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 10;
display: flex;
gap: 0.5rem;
border-radius: 2rem;
padding: 0.5rem;
transition: all 0.3s ease;
}
.dark .zoom-controls {
background-color: rgba(17, 24, 39, 0.7);
border: 1px solid rgba(55, 65, 81, 0.5);
}
.zoom-controls {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(226, 232, 240, 0.7);
}
.zoom-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
}
.dark .zoom-btn {
background-color: rgba(31, 41, 55, 0.7);
color: rgba(255, 255, 255, 0.9);
}
.zoom-btn {
background-color: rgba(255, 255, 255, 0.9);
color: rgba(30, 41, 59, 0.9);
}
.dark .zoom-btn:hover {
background-color: rgba(139, 92, 246, 0.3);
color: rgba(255, 255, 255, 1);
}
.zoom-btn:hover {
background-color: rgba(139, 92, 246, 0.1);
color: rgba(109, 40, 217, 1);
}
/* Loading spinner */
.spinner {
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 3px solid;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
}
.dark .spinner {
border-top-color: rgba(139, 92, 246, 0.7);
}
.spinner {
border-top-color: rgba(109, 40, 217, 0.7);
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<!-- Mindmap Container -->
<div id="mindmap-container">
<!-- Main Canvas -->
<div id="mindmap-canvas"></div>
<!-- Control Panel -->
<div class="control-panel p-4 w-64" x-data="{ isExpanded: true }">
<div class="panel-toggle" @click="isExpanded = !isExpanded">
<i class="fa-solid" :class="isExpanded ? 'fa-chevron-left' : 'fa-chevron-right'"></i>
</div>
<div x-show="isExpanded">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Wissensbereiche</h2>
<!-- Search Box -->
<div class="mb-4">
<input type="text" id="category-search" class="search-input" placeholder="Bereich suchen...">
</div>
<!-- Category Tree -->
<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>
</div>
</div>
<!-- View Mode Toggle -->
<div class="mt-4">
<h3 class="text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">Ansicht</h3>
<div class="flex justify-between gap-2">
<button id="view-all" class="mode-toggle text-sm flex-1 active">
<i class="fa-solid fa-diagram-project mr-1"></i> Alles
</button>
<button id="view-focused" class="mode-toggle text-sm flex-1">
<i class="fa-solid fa-bullseye mr-1"></i> Fokus
</button>
</div>
</div>
</div>
</div>
<!-- User Mindmaps Section -->
{% if current_user.is_authenticated %}
<div class="user-mindmap-section p-4 w-64">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Meine Mindmaps</h2>
<button @click="isExpanded = !isExpanded" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<i class="fa-solid" :class="isExpanded ? 'fa-chevron-down' : 'fa-chevron-up'"></i>
</button>
</div>
<div x-show="isExpanded">
<!-- 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 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 -->
<a href="{{ url_for('create_mindmap') }}" class="mystical-button mystical-button-primary w-full text-center text-sm">
<i class="fa-solid fa-plus mr-1"></i> Neue Mindmap erstellen
</a>
</div>
</div>
{% endif %}
<!-- Zoom Controls -->
<div class="zoom-controls">
<button id="zoom-in" class="zoom-btn">
<i class="fa-solid fa-plus"></i>
</button>
<button id="zoom-out" class="zoom-btn">
<i class="fa-solid fa-minus"></i>
</button>
<button id="reset-view" class="zoom-btn">
<i class="fa-solid fa-home"></i>
</button>
</div>
<!-- Node Tooltip -->
<div id="node-tooltip" class="tooltip-container rounded-lg p-4 shadow-lg"></div>
</div>
{% endblock %}
{% block extra_js %}
<!-- D3.js for Mindmap Visualization -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Custom D3 Extensions -->
<script src="{{ url_for('static', filename='d3-extensions.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');
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');
// 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');
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();
}
// 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>
<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>
`;
mindmapsList.appendChild(item);
});
})
.catch(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>
`;
}
});
});
</script>
{% endif %}
{% endblock %}