Compare commits

...

22 Commits

Author SHA1 Message Date
f69356473b Entferne nicht mehr benötigte Dateien: Lösche docker-compose.yml, Dockerfile, README.md, requirements.txt, start_server.bat, start-flask-server.py, start.sh, test_server.py, sowie alle zugehörigen Datenbank- und Website-Dateien. Diese Bereinigung optimiert die Projektstruktur und entfernt veraltete Komponenten. 2025-04-30 12:34:06 +02:00
38ac13e87c chore: automatic commit 2025-04-30 12:32 2025-04-30 12:32:36 +02:00
0afb8cb6e2 Update neural network background configuration: reduce node count and connection distance, adjust glow and node colors, and modify shadow blur for improved visual clarity and performance. 2025-04-29 20:58:27 +01:00
5d282d2108 Refactor neural network background animation: streamline the code by consolidating node and connection logic, enhancing visual effects with improved glow and animation dynamics. Introduce responsive canvas resizing and optimize particle behavior for a smoother experience. 2025-04-29 20:54:24 +01:00
4aba72efa2 Merge branch 'main' of https://git.clickcandit.com/marwinm/website 2025-04-29 20:52:11 +01:00
89476d5353 w 2025-04-29 20:51:49 +01:00
0f7a33340a Update mindmap database: replace binary file with a new version to reflect recent changes in structure and data. 2025-04-27 08:56:56 +01:00
73501e7cda Add Flask server startup scripts: introduce start_server.bat for Windows and start-flask-server.py for enhanced server management. Update run.py to include logging and threaded request handling. Add test_server.py for server accessibility testing. 2025-04-25 17:09:09 +01:00
9f8eba6736 Refactor database initialization: streamline the process by removing the old init_database function, implementing a new structure for database setup, and ensuring the creation of a comprehensive mindmap hierarchy with an admin user. Update app.py to run on port 5000 instead of 6000. 2025-04-21 18:43:58 +01:00
b6bf9f387d Update mindmap database: replace binary file with a new version to incorporate recent structural and data changes. 2025-04-21 18:26:41 +01:00
d9fe1f8efc Update mindmap database: replace existing binary file with a new version, reflecting recent changes in mindmap structure and data. 2025-04-20 20:28:51 +01:00
fd7bc59851 Add user authentication routes: implement login, registration, and logout functionality, along with user profile and admin routes. Enhance mindmap API with error handling and default node creation. 2025-04-20 19:58:27 +01:00
55f1f87509 Refactor app initialization: encapsulate Flask app setup and database initialization within a create_app function, improving modularity and error handling during startup. 2025-04-20 19:54:07 +01:00
03f8761312 Update Docker configuration to change exposed port from 5000 to 6000 in Dockerfile and docker-compose.yml, ensuring consistency across the application. 2025-04-20 19:48:49 +01:00
506748fda7 Implement error handling and default node creation for mindmap routes; initialize database on first request to ensure root node exists. 2025-04-20 19:43:21 +01:00
6d069f68cd Update requirements.txt to include new dependencies for enhanced functionality and remove outdated packages for better compatibility. 2025-04-20 19:32:32 +01:00
4310239a7a Enhance Dockerfile: add system dependencies for building Python packages, update requirements.txt to remove specific version constraints, and verify installations with pip list. 2025-04-20 19:31:13 +01:00
e9fe907af0 Update requirements.txt to include email_validator==2.1.1 for improved email validation functionality. 2025-04-20 19:29:19 +01:00
0c69d9aba3 Remove unnecessary 'force' option from docker-compose.yml for cleaner configuration. 2025-04-20 19:26:44 +01:00
6da85cdece Refactor Docker setup: update docker-compose.yml to use a specific website directory for volumes, enable automatic restarts, and modify Dockerfile to clean up and copy application files more efficiently. 2025-04-20 19:25:08 +01:00
a073b09115 Update Docker configuration: change Docker Compose version to 3.8, enhance web service setup with context and dockerfile specifications, add volume and environment variables for Flask development, and modify Dockerfile to use Python 3.11 and improve file copying and command execution. 2025-04-20 19:22:08 +01:00
f1f4870989 Update dependencies in requirements.txt to specific versions for Flask, Flask-Login, Flask-SQLAlchemy, Werkzeug, and SQLAlchemy 2025-04-20 19:16:34 +01:00
24 changed files with 0 additions and 1765 deletions

View File

@@ -1,10 +0,0 @@
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY website .
CMD ["python", "app.py"]

View File

@@ -1 +0,0 @@

BIN
archiv_0.1.zip Normal file

Binary file not shown.

View File

@@ -1,7 +0,0 @@
version: "3.9"
services:
web:
build: .
ports:
- "5000:5000"
restart: always

View File

@@ -1,6 +0,0 @@
flask
flask-login
flask-wtf
email-validator
python-dotenv
flask-sqlalchemy

View File

@@ -1,264 +0,0 @@
import os
from datetime import datetime
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
import json
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mindmap.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
# Database Models
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
thoughts = db.relationship('Thought', backref='author', lazy=True)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class Thought(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
branch = db.Column(db.String(100), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan")
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
author = db.relationship('User', backref='comments')
class MindMapNode(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
parent_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
children = db.relationship('MindMapNode', backref=db.backref('parent', remote_side=[id]))
thoughts = db.relationship('Thought', secondary='node_thought_association', backref='nodes')
# Association table for many-to-many relationship between MindMapNode and Thought
node_thought_association = db.Table('node_thought_association',
db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
)
@login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
# Routes for authentication
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user)
next_page = request.args.get('next')
return redirect(next_page or url_for('index'))
flash('Ungültiger Benutzername oder Passwort')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
if User.query.filter_by(username=username).first():
flash('Benutzername existiert bereits')
return redirect(url_for('register'))
if User.query.filter_by(email=email).first():
flash('E-Mail ist bereits registriert')
return redirect(url_for('register'))
user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
login_user(user)
return redirect(url_for('index'))
return render_template('register.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
# Route for the homepage
@app.route('/')
def index():
return render_template('index.html')
# Route for the mindmap page
@app.route('/mindmap')
def mindmap():
return render_template('mindmap.html')
# Route for user profile
@app.route('/profile')
@login_required
def profile():
thoughts = Thought.query.filter_by(user_id=current_user.id).order_by(Thought.timestamp.desc()).all()
return render_template('profile.html', thoughts=thoughts)
# API routes for mindmap and thoughts
@app.route('/api/mindmap')
def get_mindmap():
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
def build_tree(node):
return {
'id': node.id,
'name': node.name,
'children': [build_tree(child) for child in node.children]
}
result = [build_tree(node) for node in root_nodes]
return jsonify(result)
@app.route('/api/thoughts/<int:node_id>', methods=['GET'])
def get_thoughts(node_id):
node = MindMapNode.query.get_or_404(node_id)
thoughts = []
for thought in node.thoughts:
thoughts.append({
'id': thought.id,
'content': thought.content,
'author': thought.author.username,
'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'),
'comments_count': len(thought.comments),
'branch': thought.branch
})
return jsonify(thoughts)
@app.route('/api/thought/<int:thought_id>', methods=['GET'])
def get_thought(thought_id):
thought = Thought.query.get_or_404(thought_id)
return jsonify({
'id': thought.id,
'content': thought.content,
'author': thought.author.username,
'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'),
'branch': thought.branch,
'comments_count': len(thought.comments)
})
@app.route('/api/thoughts', methods=['POST'])
@login_required
def add_thought():
data = request.json
node_id = data.get('node_id')
content = data.get('content')
if not node_id or not content:
return jsonify({'error': 'Fehlende Daten'}), 400
node = MindMapNode.query.get_or_404(node_id)
thought = Thought(
content=content,
branch=node.name,
user_id=current_user.id
)
db.session.add(thought)
node.thoughts.append(thought)
db.session.commit()
return jsonify({
'id': thought.id,
'content': thought.content,
'author': thought.author.username,
'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'),
'branch': thought.branch
})
@app.route('/api/comments/<int:thought_id>', methods=['GET'])
def get_comments(thought_id):
thought = Thought.query.get_or_404(thought_id)
comments = [
{
'id': comment.id,
'content': comment.content,
'author': comment.author.username,
'timestamp': comment.timestamp.strftime('%d.%m.%Y, %H:%M')
}
for comment in thought.comments
]
return jsonify(comments)
@app.route('/api/comments', methods=['POST'])
@login_required
def add_comment():
data = request.json
thought_id = data.get('thought_id')
content = data.get('content')
if not thought_id or not content:
return jsonify({'error': 'Fehlende Daten'}), 400
thought = Thought.query.get_or_404(thought_id)
comment = Comment(
content=content,
thought_id=thought_id,
user_id=current_user.id
)
db.session.add(comment)
db.session.commit()
return jsonify({
'id': comment.id,
'content': comment.content,
'author': comment.author.username,
'timestamp': comment.timestamp.strftime('%d.%m.%Y, %H:%M')
})
# Admin routes
@app.route('/admin')
@login_required
def admin():
if not current_user.is_admin:
flash('Zugriff verweigert')
return redirect(url_for('index'))
users = User.query.all()
nodes = MindMapNode.query.all()
thoughts = Thought.query.all()
return render_template('admin.html', users=users, nodes=nodes, thoughts=thoughts)
# Flask starten
if __name__ == '__main__':
with app.app_context():
# Make sure tables exist
db.create_all()
app.run(host="0.0.0.0", debug=True)

View File

@@ -1,88 +0,0 @@
from app import app, db, User, MindMapNode
def init_database():
"""Initialize the database with admin user and mindmap structure."""
with app.app_context():
# Create all tables
db.create_all()
# Check if we already have users
if User.query.first() is None:
print("Creating admin user...")
# Create admin user
admin = User(username='admin', email='admin@example.com', is_admin=True)
admin.set_password('admin123')
db.session.add(admin)
# Create regular test user
test_user = User(username='test', email='test@example.com', is_admin=False)
test_user.set_password('test123')
db.session.add(test_user)
db.session.commit()
print("Admin user created successfully!")
# Check if we already have mindmap nodes
if MindMapNode.query.first() is None:
print("Creating initial mindmap structure...")
# Create initial mindmap structure
root = MindMapNode(name="Wissenschaftliche Mindmap")
db.session.add(root)
# Level 1 nodes
node1 = MindMapNode(name="Naturwissenschaften", parent=root)
node2 = MindMapNode(name="Geisteswissenschaften", parent=root)
node3 = MindMapNode(name="Technologie", parent=root)
node4 = MindMapNode(name="Künste", parent=root)
db.session.add_all([node1, node2, node3, node4])
# Level 2 nodes - Naturwissenschaften
node1_1 = MindMapNode(name="Physik", parent=node1)
node1_2 = MindMapNode(name="Biologie", parent=node1)
node1_3 = MindMapNode(name="Chemie", parent=node1)
node1_4 = MindMapNode(name="Astronomie", parent=node1)
db.session.add_all([node1_1, node1_2, node1_3, node1_4])
# Level 2 nodes - Geisteswissenschaften
node2_1 = MindMapNode(name="Philosophie", parent=node2)
node2_2 = MindMapNode(name="Geschichte", parent=node2)
node2_3 = MindMapNode(name="Psychologie", parent=node2)
node2_4 = MindMapNode(name="Soziologie", parent=node2)
db.session.add_all([node2_1, node2_2, node2_3, node2_4])
# Level 2 nodes - Technologie
node3_1 = MindMapNode(name="Informatik", parent=node3)
node3_2 = MindMapNode(name="Biotechnologie", parent=node3)
node3_3 = MindMapNode(name="Künstliche Intelligenz", parent=node3)
node3_4 = MindMapNode(name="Energietechnik", parent=node3)
db.session.add_all([node3_1, node3_2, node3_3, node3_4])
# Level 2 nodes - Künste
node4_1 = MindMapNode(name="Bildende Kunst", parent=node4)
node4_2 = MindMapNode(name="Musik", parent=node4)
node4_3 = MindMapNode(name="Literatur", parent=node4)
node4_4 = MindMapNode(name="Film", parent=node4)
db.session.add_all([node4_1, node4_2, node4_3, node4_4])
# Level 3 nodes - a few examples
# Physik
MindMapNode(name="Quantenphysik", parent=node1_1)
MindMapNode(name="Relativitätstheorie", parent=node1_1)
# Informatik
MindMapNode(name="Maschinelles Lernen", parent=node3_1)
MindMapNode(name="Softwareentwicklung", parent=node3_1)
MindMapNode(name="Datenbanken", parent=node3_1)
# Commit changes
db.session.commit()
print("Mindmap structure created successfully!")
print("Database initialization complete.")
if __name__ == "__main__":
init_database()
print("You can now run the application with 'python app.py'")
print("Login with:")
print(" Admin: username=admin, password=admin123")
print(" User: username=test, password=test123")

Binary file not shown.

View File

@@ -1,6 +0,0 @@
flask
flask-login
flask-wtf
email-validator
python-dotenv
flask-sqlalchemy

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env python3
import os
from init_db import init_database
from app import app
if __name__ == "__main__":
# Initialize the database first
init_database()
# Run the Flask application
app.run(host="0.0.0.0", debug=True)

View File

@@ -1,108 +0,0 @@
// Background animation with Three.js
let scene, camera, renderer, stars = [];
function initBackground() {
// Setup scene
scene = new THREE.Scene();
// Setup camera
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 100;
// Setup renderer
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 0); // Transparent background
// Append renderer to DOM
const backgroundContainer = document.getElementById('background-container');
if (backgroundContainer) {
backgroundContainer.appendChild(renderer.domElement);
}
// Add stars
for (let i = 0; i < 1000; i++) {
const geometry = new THREE.SphereGeometry(0.2, 8, 8);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: Math.random() * 0.5 + 0.1 });
const star = new THREE.Mesh(geometry, material);
// Random position
star.position.x = Math.random() * 600 - 300;
star.position.y = Math.random() * 600 - 300;
star.position.z = Math.random() * 600 - 300;
// Store reference to move in animation
star.velocity = Math.random() * 0.02 + 0.005;
stars.push(star);
scene.add(star);
}
// Add large glowing particles
for (let i = 0; i < 15; i++) {
const size = Math.random() * 5 + 2;
const geometry = new THREE.SphereGeometry(size, 16, 16);
// Create a glowing material
const color = new THREE.Color();
color.setHSL(Math.random(), 0.7, 0.5); // Random hue
const material = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.2
});
const particle = new THREE.Mesh(geometry, material);
// Random position but further away
particle.position.x = Math.random() * 1000 - 500;
particle.position.y = Math.random() * 1000 - 500;
particle.position.z = Math.random() * 200 - 400;
// Store reference to move in animation
particle.velocity = Math.random() * 0.01 + 0.002;
stars.push(particle);
scene.add(particle);
}
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Start animation
animate();
}
function animate() {
requestAnimationFrame(animate);
// Move stars
stars.forEach(star => {
star.position.z += star.velocity;
// Reset position if star moves too close
if (star.position.z > 100) {
star.position.z = -300;
}
});
// Rotate the entire scene slightly for a dreamy effect
scene.rotation.y += 0.0003;
scene.rotation.x += 0.0001;
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Initialize background when the DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBackground);
} else {
initBackground();
}

View File

@@ -1 +0,0 @@
C:\Users\firem\Downloads\background.mp4

View File

@@ -1,49 +0,0 @@
// Erstelle eine einfache Mindmap-Struktur mit D3.js
const data = {
name: "Wissenschaftliche Mindmap",
children: [
{ name: "Forschung", children: [{ name: "Theorie" }, { name: "Experimente" }] },
{ name: "Technologie", children: [{ name: "Datenbanken" }, { name: "Cloud Computing" }] }
]
};
// D3.js-Setup für die Darstellung der Mindmap
const width = 800;
const height = 600;
const margin = 50;
const svg = d3.select("#mindmap")
.append("svg")
.attr("width", width)
.attr("height", height);
const root = d3.hierarchy(data);
const treeLayout = d3.tree().size([width - margin, height - margin]);
treeLayout(root);
const links = svg.selectAll(".link")
.data(root.links())
.enter()
.append("line")
.attr("class", "link")
.attr("x1", d => d.source.x + margin)
.attr("y1", d => d.source.y + margin)
.attr("x2", d => d.target.x + margin)
.attr("y2", d => d.target.y + margin)
.attr("stroke", "#2c3e50");
const nodes = svg.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x + margin},${d.y + margin})`);
nodes.append("circle")
.attr("r", 20)
.attr("fill", "#3498db");
nodes.append("text")
.attr("dx", 25)
.attr("dy", 5)
.text(d => d.data.name);

View File

@@ -1,27 +0,0 @@
/* Grundlegendes Styling für die Seite */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
/* Styling für den Header */
h1 {
text-align: center;
color: #2c3e50;
margin-top: 50px;
}
/* Button für die Navigation */
button {
padding: 10px 20px;
background-color: #3498db;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #2980b9;
}

View File

View File

@@ -1,109 +0,0 @@
{% extends "base.html" %}
{% block title %}Admin | Wissenschaftliche Mindmap{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="glass p-8 mb-8">
<h1 class="text-3xl font-bold text-white mb-4">Admin Bereich</h1>
<p class="text-white/70">Verwalte Benutzer, Gedanken und die Mindmap-Struktur.</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Users Section -->
<div class="dark-glass p-6" x-data="{ tab: 'users' }">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white">Benutzer</h2>
<span class="bg-indigo-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{{ users|length }}</span>
</div>
<div class="overflow-y-auto max-h-[500px]">
<table class="w-full text-white/90">
<thead class="text-white/60 text-sm uppercase">
<tr>
<th class="text-left py-3">ID</th>
<th class="text-left py-3">Benutzername</th>
<th class="text-left py-3">Email</th>
<th class="text-left py-3">Rolle</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr class="border-t border-white/10">
<td class="py-3">{{ user.id }}</td>
<td class="py-3">{{ user.username }}</td>
<td class="py-3">{{ user.email }}</td>
<td class="py-3">
{% if user.is_admin %}
<span class="bg-purple-600/70 text-white text-xs px-2 py-1 rounded">Admin</span>
{% else %}
<span class="bg-blue-600/70 text-white text-xs px-2 py-1 rounded">Benutzer</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Mindmap Nodes Section -->
<div class="dark-glass p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white">Mindmap Struktur</h2>
<span class="bg-indigo-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{{ nodes|length }}</span>
</div>
<div class="overflow-y-auto max-h-[500px]">
<div class="space-y-3">
{% for node in nodes %}
<div class="glass p-3">
<div class="flex justify-between items-center">
<span class="font-medium">{{ node.name }}</span>
<span class="text-xs text-white/60">ID: {{ node.id }}</span>
</div>
{% if node.parent %}
<p class="text-sm text-white/60 mt-1">Eltern: {{ node.parent.name }}</p>
{% else %}
<p class="text-sm text-white/60 mt-1">Hauptknoten</p>
{% endif %}
</div>
{% endfor %}
</div>
</div>
<div class="mt-6">
<button class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-4 py-2 rounded-lg transition-all duration-300 text-sm w-full">
Neuen Knoten hinzufügen
</button>
</div>
</div>
<!-- Thoughts Section -->
<div class="dark-glass p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white">Gedanken</h2>
<span class="bg-indigo-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{{ thoughts|length }}</span>
</div>
<div class="overflow-y-auto max-h-[500px]">
<div class="space-y-3">
{% for thought in thoughts %}
<div class="glass p-3">
<div class="flex justify-between items-start">
<span class="inline-block px-2 py-0.5 text-xs text-white/70 bg-white/10 rounded-full mb-1">{{ thought.branch }}</span>
<span class="text-xs text-white/50">{{ thought.timestamp.strftime('%d.%m.%Y') }}</span>
</div>
<p class="text-sm text-white mb-1 line-clamp-2">{{ thought.content }}</p>
<div class="flex justify-between items-center mt-2 text-xs">
<span class="text-white/60">Von: {{ thought.author.username }}</span>
<span class="text-white/60">{{ thought.comments|length }} Kommentar(e)</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,126 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Wissenschaftliche Mindmap{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#10b981',
accent: '#8b5cf6',
},
fontFamily: {
sans: ['Poppins', 'sans-serif'],
},
},
},
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
.dark-glass {
background: rgba(17, 24, 39, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
body {
min-height: 100vh;
background-color: #050b14;
font-family: 'Poppins', sans-serif;
position: relative;
overflow-x: hidden;
}
.gradient-text {
background-clip: text;
-webkit-background-clip: text;
color: transparent;
background-image: linear-gradient(to right, #4f46e5, #8b5cf6);
}
#background-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
#background-container canvas {
position: absolute;
top: 0;
left: 0;
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body class="antialiased text-gray-100">
<!-- Animated background container -->
<div id="background-container"></div>
<div class="min-h-screen">
<!-- Navigation -->
<nav class="glass px-4 py-3 mx-4 mt-4 flex justify-between items-center">
<a href="{{ url_for('index') }}" class="text-white text-xl font-bold">MindMap</a>
<div class="flex space-x-4">
<a href="{{ url_for('mindmap') }}" class="text-white hover:text-indigo-200 transition-colors">Mindmap</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('profile') }}" class="text-white hover:text-indigo-200 transition-colors">Profil</a>
{% if current_user.is_admin %}
<a href="{{ url_for('admin') }}" class="text-white hover:text-indigo-200 transition-colors">Admin</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="text-white hover:text-indigo-200 transition-colors">Abmelden</a>
{% else %}
<a href="{{ url_for('login') }}" class="text-white hover:text-indigo-200 transition-colors">Anmelden</a>
<a href="{{ url_for('register') }}" class="text-white hover:text-indigo-200 transition-colors">Registrieren</a>
{% endif %}
</div>
</nav>
<!-- Flash messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="container mx-auto mt-4 px-4">
{% for message in messages %}
<div class="glass p-4 mb-4 text-white">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Main content -->
<main class="container mx-auto p-4">
{% block content %}{% endblock %}
</main>
</div>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="{{ url_for('static', filename='background.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -1,41 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="flex flex-col items-center justify-center min-h-[80vh] text-center">
<div class="glass p-8 max-w-2xl w-full">
<h1 class="text-4xl font-bold text-white mb-4">Willkommen zur Wissenschafts-Mindmap</h1>
<p class="text-xl text-white/80 mb-8">Verknüpfe Wissen in neuronalen Strukturen und teile deine Gedanken mit der Community.</p>
<div class="flex flex-col space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4 justify-center">
<a href="{{ url_for('mindmap') }}" class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300 transform hover:scale-105">
Starte die Mindmap
</a>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="glass hover:bg-white/20 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300 transform hover:scale-105">
Registrieren
</a>
{% endif %}
</div>
</div>
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-4xl">
<div class="glass p-6 text-white">
<div class="text-3xl mb-2">🧠</div>
<h3 class="text-xl font-semibold mb-2">Visualisiere Wissen</h3>
<p class="text-white/80">Erkenne Zusammenhänge zwischen verschiedenen Wissensgebieten durch intuitive Mindmaps.</p>
</div>
<div class="glass p-6 text-white">
<div class="text-3xl mb-2">💡</div>
<h3 class="text-xl font-semibold mb-2">Teile Gedanken</h3>
<p class="text-white/80">Füge deine eigenen Gedanken zu bestehenden Themen hinzu und bereichere die Community.</p>
</div>
<div class="glass p-6 text-white">
<div class="text-3xl mb-2">🔄</div>
<h3 class="text-xl font-semibold mb-2">Interaktive Vernetzung</h3>
<p class="text-white/80">Beteilige dich an Diskussionen und sieh wie sich Ideen gemeinsam entwickeln.</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,41 +0,0 @@
{% extends "base.html" %}
{% block title %}Anmelden | Wissenschaftliche Mindmap{% endblock %}
{% block content %}
<div class="flex justify-center items-center min-h-[80vh]">
<div class="glass p-8 w-full max-w-md">
<h1 class="text-3xl font-bold text-white mb-6 text-center">Anmelden</h1>
<form method="POST" action="{{ url_for('login') }}" class="space-y-6">
<div>
<label for="username" class="block text-sm font-medium text-white mb-1">Benutzername</label>
<input type="text" id="username" name="username" required
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
</div>
<div>
<label for="password" class="block text-sm font-medium text-white mb-1">Passwort</label>
<input type="password" id="password" name="password" required
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
</div>
<div>
<button type="submit"
class="w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300">
Anmelden
</button>
</div>
</form>
<div class="mt-6 text-center">
<p class="text-white/70">
Noch kein Konto?
<a href="{{ url_for('register') }}" class="text-indigo-300 hover:text-white font-medium transition-colors">
Registrieren
</a>
</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,692 +0,0 @@
{% extends "base.html" %}
{% block title %}Mindmap | Wissenschaftliche Mindmap{% endblock %}
{% block extra_head %}
<style>
.node {
cursor: pointer;
}
.node circle {
fill: rgba(255, 255, 255, 0.2);
stroke: white;
stroke-width: 1.5px;
transition: all 0.3s ease;
}
.node text {
font-size: 12px;
fill: white;
}
.node--selected circle {
fill: rgba(139, 92, 246, 0.6);
r: 25;
stroke: rgba(255, 255, 255, 0.8);
stroke-width: 2px;
}
.link {
fill: none;
stroke: rgba(255, 255, 255, 0.3);
stroke-width: 1.5px;
}
.thoughts-panel {
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
}
.thoughts-panel.open {
transform: translateX(0);
}
.thoughts-container {
max-height: calc(100vh - 250px);
overflow-y: auto;
}
/* Custom scrollbar for the thoughts panel */
.thoughts-container::-webkit-scrollbar {
width: 5px;
}
.thoughts-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.thoughts-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 10px;
}
.thoughts-container::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* Tooltip */
.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 5px;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
}
/* Node color coding by category */
.node-science circle {
fill: rgba(79, 70, 229, 0.3);
}
.node-humanities circle {
fill: rgba(16, 185, 129, 0.3);
}
.node-technology circle {
fill: rgba(139, 92, 246, 0.3);
}
.node-arts circle {
fill: rgba(236, 72, 153, 0.3);
}
/* Add a glow effect on hover */
.node:hover circle {
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
}
</style>
{% endblock %}
{% block content %}
<div class="flex flex-col lg:flex-row h-[calc(100vh-100px)]">
<!-- Main mindmap visualization area -->
<div class="flex-grow relative" id="mindmap-container">
<div class="absolute top-4 left-4 z-10">
<h1 class="text-2xl font-bold text-white glass px-4 py-2 inline-block">Wissenschaftliche Mindmap</h1>
</div>
<!-- Zoom controls -->
<div class="absolute bottom-4 left-4 glass p-2 flex space-x-2 z-10">
<button id="zoom-in" class="w-8 h-8 flex items-center justify-center text-white hover:bg-white/20 rounded-full transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
</button>
<button id="zoom-out" class="w-8 h-8 flex items-center justify-center text-white hover:bg-white/20 rounded-full transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5 10a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1z" clip-rule="evenodd" />
</svg>
</button>
<button id="reset-view" class="w-8 h-8 flex items-center justify-center text-white hover:bg-white/20 rounded-full transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" />
</svg>
</button>
</div>
<!-- Legend -->
<div class="absolute top-4 right-4 glass p-3 z-10">
<h3 class="text-sm font-semibold text-white mb-2">Kategorien</h3>
<div class="space-y-1 text-xs">
<div class="flex items-center">
<span class="inline-block w-3 h-3 rounded-full bg-indigo-600/60 mr-2"></span>
<span class="text-white/90">Naturwissenschaften</span>
</div>
<div class="flex items-center">
<span class="inline-block w-3 h-3 rounded-full bg-green-500/60 mr-2"></span>
<span class="text-white/90">Geisteswissenschaften</span>
</div>
<div class="flex items-center">
<span class="inline-block w-3 h-3 rounded-full bg-purple-500/60 mr-2"></span>
<span class="text-white/90">Technologie</span>
</div>
<div class="flex items-center">
<span class="inline-block w-3 h-3 rounded-full bg-pink-500/60 mr-2"></span>
<span class="text-white/90">Künste</span>
</div>
</div>
</div>
<svg id="mindmap" class="w-full h-full"></svg>
<div id="tooltip" class="tooltip"></div>
</div>
<!-- Thoughts panel (hidden by default) -->
<div id="thoughts-panel" class="thoughts-panel fixed lg:relative right-0 top-0 lg:top-auto h-full lg:h-auto w-full sm:w-96 dark-glass z-20">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-white" id="selected-node-title">Keine Auswahl</h2>
<button id="close-panel" class="text-white/70 hover:text-white lg:hidden">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{% if current_user.is_authenticated %}
<div class="glass p-4 mb-6">
<h3 class="text-sm font-medium text-white/80 mb-2">Teile deinen Gedanken</h3>
<textarea id="thought-input" rows="3" placeholder="Was denkst du zu diesem Thema?"
class="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all resize-none"></textarea>
<button id="submit-thought"
class="mt-2 w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-medium px-4 py-2 rounded-lg transition-all">
Gedanken teilen
</button>
</div>
{% else %}
<div class="glass p-4 mb-6 text-center">
<p class="text-white/80 mb-2">Melde dich an, um deine Gedanken zu teilen</p>
<a href="{{ url_for('login') }}" class="text-indigo-300 hover:text-white font-medium">Anmelden</a>
</div>
{% endif %}
<h3 class="text-lg font-medium text-white mb-3">Community Gedanken</h3>
<div id="thoughts-container" class="thoughts-container space-y-4">
<div class="text-center py-8 text-white/50">
<p>Wähle einen Knoten aus, um Gedanken zu sehen</p>
</div>
</div>
</div>
</div>
</div>
<!-- Comment Modal -->
<div id="commentModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
<div class="dark-glass p-6 w-full max-w-lg">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-white" id="comment-modal-title">Kommentare</h3>
<button onclick="closeCommentModal()" class="text-white/70 hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="glass p-4 mb-4">
<p id="comment-thought-content" class="text-white mb-2"></p>
<div class="flex justify-between items-center text-xs text-white/60">
<span id="comment-thought-author"></span>
<span id="comment-thought-time"></span>
</div>
</div>
<div class="mb-4 max-h-60 overflow-y-auto" id="comments-list">
<!-- Comments will be loaded here -->
</div>
{% if current_user.is_authenticated %}
<div class="mt-4">
<textarea id="comment-input" rows="2" placeholder="Füge einen Kommentar hinzu..."
class="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all resize-none"></textarea>
<button id="submit-comment"
class="mt-2 w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-medium px-4 py-2 rounded-lg transition-all">
Kommentar hinzufügen
</button>
</div>
{% else %}
<div class="glass p-4 text-center">
<p class="text-white/80 mb-2">Melde dich an, um Kommentare zu hinterlassen</p>
<a href="{{ url_for('login') }}" class="text-indigo-300 hover:text-white font-medium">Anmelden</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// Global variables
let selectedNode = null;
let currentThoughtId = null;
const width = document.getElementById('mindmap-container').clientWidth;
const height = document.getElementById('mindmap-container').clientHeight;
const nodeCategories = {
'Naturwissenschaften': 'node-science',
'Geisteswissenschaften': 'node-humanities',
'Technologie': 'node-technology',
'Künste': 'node-arts'
};
// Initialize D3 visualization
const svg = d3.select('#mindmap')
.attr('width', width)
.attr('height', height);
const g = svg.append('g');
// Zoom behavior
const zoom = d3.zoom()
.scaleExtent([0.3, 3])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);
// Reset to initial view
function resetView() {
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity.translate(width / 2, height / 2).scale(0.8)
);
}
// Initialize tooltip
const tooltip = d3.select('#tooltip');
// Load mindmap data
function loadMindmap() {
fetch('/api/mindmap')
.then(response => response.json())
.then(data => {
renderMindmap(data[0]); // Assuming first item is the root node
resetView();
})
.catch(error => {
console.error('Error loading mindmap:', error);
alert('Fehler beim Laden der Mindmap. Bitte die Seite neu laden.');
});
}
// Get node category
function getNodeCategory(nodeName, rootCategories) {
// Check if the node is one of the root categories
if (nodeCategories[nodeName]) {
return nodeCategories[nodeName];
}
// Check parent categories for sub-nodes
for (const category in rootCategories) {
if (rootCategories[category].includes(nodeName)) {
return nodeCategories[category];
}
}
return '';
}
// Process data to track categories
function processData(node, rootCategories = {}) {
// Initialize categories for root node
if (node.name in nodeCategories) {
rootCategories[node.name] = [];
}
// Record all children of a category
if (node.children) {
node.children.forEach(child => {
// Add to parent category
for (const category in rootCategories) {
if (node.name === category || rootCategories[category].includes(node.name)) {
rootCategories[category].push(child.name);
}
}
// Process recursively
processData(child, rootCategories);
});
}
return rootCategories;
}
// Render the mindmap visualization
function renderMindmap(data) {
// Clear previous content
g.selectAll('*').remove();
// Process data to track categories
const rootCategories = processData(data);
// Create hierarchical layout
const root = d3.hierarchy(data);
// Create tree layout
const treeLayout = d3.tree()
.size([height - 100, width - 200])
.nodeSize([80, 200]);
treeLayout(root);
// Create links
const links = g.selectAll('.link')
.data(root.links())
.enter()
.append('path')
.attr('class', 'link')
.attr('d', d => {
return `M${d.source.y},${d.source.x}
C${(d.source.y + d.target.y) / 2},${d.source.x}
${(d.source.y + d.target.y) / 2},${d.target.x}
${d.target.y},${d.target.x}`;
});
// Create nodes
const nodes = g.selectAll('.node')
.data(root.descendants())
.enter()
.append('g')
.attr('class', d => {
const categoryClass = getNodeCategory(d.data.name, rootCategories);
return `node ${categoryClass} ${d.data.id === selectedNode ? 'node--selected' : ''}`;
})
.attr('transform', d => `translate(${d.y},${d.x})`)
.on('click', (event, d) => selectNode(d.data.id, d.data.name))
.on('mouseover', function(event, d) {
tooltip.transition()
.duration(200)
.style('opacity', 0.9);
tooltip.html(d.data.name)
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 28) + 'px');
})
.on('mouseout', function(d) {
tooltip.transition()
.duration(500)
.style('opacity', 0);
});
// Add circles to nodes
nodes.append('circle')
.attr('r', 20)
.attr('class', d => (d.depth === 0) ? 'root-node' : ''); // Special class for root node
// Add text labels
nodes.append('text')
.attr('dy', 30)
.attr('text-anchor', 'middle')
.text(d => d.data.name)
.each(function(d) {
// Wrap text for long labels
const text = d3.select(this);
const words = d.data.name.split(/\s+/);
if (words.length > 1) {
text.text('');
for (let i = 0; i < words.length; i++) {
const tspan = text.append('tspan')
.attr('x', 0)
.attr('dy', i === 0 ? 30 : 15)
.attr('text-anchor', 'middle')
.text(words[i]);
}
}
});
// Add node count indicator (how many thoughts are associated)
nodes.each(function(d) {
const node = d3.select(this);
// Get thought count for this node (an API call would be needed)
fetch(`/api/thoughts/${d.data.id}`)
.then(response => response.json())
.then(thoughts => {
if (thoughts.length > 0) {
// Add a small indicator
node.append('circle')
.attr('r', 8)
.attr('cx', 20)
.attr('cy', -20)
.attr('fill', 'rgba(139, 92, 246, 0.9)');
node.append('text')
.attr('x', 20)
.attr('y', -16)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr('font-size', '10px')
.text(thoughts.length);
}
})
.catch(error => console.error(`Error loading thoughts for node ${d.data.id}:`, error));
});
}
// Handle node selection
function selectNode(nodeId, nodeName) {
selectedNode = nodeId;
// Update selected node in visualization
g.selectAll('.node').classed('node--selected', d => d.data.id === nodeId);
// Update panel title
document.getElementById('selected-node-title').textContent = nodeName;
// Load thoughts for this node
loadThoughts(nodeId);
// Open the thoughts panel on mobile
document.getElementById('thoughts-panel').classList.add('open');
}
// Load thoughts for a node
function loadThoughts(nodeId) {
const thoughtsContainer = document.getElementById('thoughts-container');
thoughtsContainer.innerHTML = '<div class="text-center py-4"><div class="inline-block animate-spin rounded-full h-6 w-6 border-t-2 border-white"></div></div>';
fetch(`/api/thoughts/${nodeId}`)
.then(response => response.json())
.then(thoughts => {
thoughtsContainer.innerHTML = '';
if (thoughts.length === 0) {
thoughtsContainer.innerHTML = '<div class="text-center py-4 text-white/50"><p>Noch keine Gedanken zu diesem Thema</p></div>';
return;
}
thoughts.forEach(thought => {
const thoughtEl = document.createElement('div');
thoughtEl.className = 'glass p-4 hover:shadow-lg transition-all';
thoughtEl.innerHTML = `
<p class="text-white mb-2">${thought.content}</p>
<div class="flex justify-between items-center text-xs">
<span class="text-white/70">Von ${thought.author}</span>
<span class="text-white/50">${thought.timestamp}</span>
</div>
<div class="mt-2 text-right">
<button class="text-indigo-300 hover:text-white text-sm" onclick="openCommentModal(${thought.id})">
${thought.comments_count} Kommentar(e) anzeigen
</button>
</div>
`;
thoughtsContainer.appendChild(thoughtEl);
});
})
.catch(error => {
console.error('Error loading thoughts:', error);
thoughtsContainer.innerHTML = '<div class="text-center py-4 text-red-300"><p>Fehler beim Laden der Gedanken</p></div>';
});
}
// Submit a new thought
function submitThought() {
if (!selectedNode) {
alert('Bitte wähle zuerst einen Knoten aus.');
return;
}
const thoughtInput = document.getElementById('thought-input');
const content = thoughtInput.value.trim();
if (!content) {
alert('Bitte gib einen Gedanken ein.');
return;
}
fetch('/api/thoughts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
node_id: selectedNode,
content: content
}),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
thoughtInput.value = '';
loadThoughts(selectedNode); // Reload thoughts
// Refresh node counts in visualization
loadMindmap();
})
.catch(error => {
console.error('Error adding thought:', error);
alert('Fehler beim Hinzufügen des Gedankens.');
});
}
// Open comment modal
function openCommentModal(thoughtId) {
currentThoughtId = thoughtId;
// Get thought details
fetch(`/api/thought/${thoughtId}`)
.then(response => response.json())
.then(thought => {
document.getElementById('comment-thought-content').textContent = thought.content;
document.getElementById('comment-thought-author').textContent = `Von ${thought.author}`;
document.getElementById('comment-thought-time').textContent = thought.timestamp;
// Get comments
return fetch(`/api/comments/${thoughtId}`);
})
.then(response => response.json())
.then(comments => {
const commentsList = document.getElementById('comments-list');
commentsList.innerHTML = '';
if (comments.length === 0) {
commentsList.innerHTML = '<p class="text-center text-white/50 py-3">Keine Kommentare vorhanden</p>';
return;
}
comments.forEach(comment => {
const commentEl = document.createElement('div');
commentEl.className = 'glass p-3 mb-2';
commentEl.innerHTML = `
<p class="text-white text-sm">${comment.content}</p>
<div class="flex justify-between items-center mt-1 text-xs">
<span class="text-white/70">${comment.author}</span>
<span class="text-white/50">${comment.timestamp}</span>
</div>
`;
commentsList.appendChild(commentEl);
});
})
.catch(error => console.error('Error loading comments:', error));
document.getElementById('commentModal').classList.remove('hidden');
}
// Close comment modal
function closeCommentModal() {
document.getElementById('commentModal').classList.add('hidden');
currentThoughtId = null;
}
// Submit a new comment
function submitComment() {
if (!currentThoughtId) return;
const commentInput = document.getElementById('comment-input');
const content = commentInput.value.trim();
if (!content) {
alert('Bitte gib einen Kommentar ein.');
return;
}
fetch('/api/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
thought_id: currentThoughtId,
content: content
}),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
commentInput.value = '';
// Refresh comments
fetch(`/api/comments/${currentThoughtId}`)
.then(response => response.json())
.then(comments => {
const commentsList = document.getElementById('comments-list');
commentsList.innerHTML = '';
comments.forEach(comment => {
const commentEl = document.createElement('div');
commentEl.className = 'glass p-3 mb-2';
commentEl.innerHTML = `
<p class="text-white text-sm">${comment.content}</p>
<div class="flex justify-between items-center mt-1 text-xs">
<span class="text-white/70">${comment.author}</span>
<span class="text-white/50">${comment.timestamp}</span>
</div>
`;
commentsList.appendChild(commentEl);
});
})
.catch(error => console.error('Error refreshing comments:', error));
// Refresh thoughts since comment count changed
if (selectedNode) {
loadThoughts(selectedNode);
}
})
.catch(error => {
console.error('Error adding comment:', error);
alert('Fehler beim Hinzufügen des Kommentars.');
});
}
// Event listeners
document.addEventListener('DOMContentLoaded', function() {
// Load mindmap on page load
loadMindmap();
// Submit thought
document.getElementById('submit-thought')?.addEventListener('click', submitThought);
// Submit comment
document.getElementById('submit-comment')?.addEventListener('click', submitComment);
// Close panel on mobile
document.getElementById('close-panel')?.addEventListener('click', function() {
document.getElementById('thoughts-panel').classList.remove('open');
});
// Zoom controls
document.getElementById('zoom-in')?.addEventListener('click', function() {
svg.transition().duration(300).call(zoom.scaleBy, 1.3);
});
document.getElementById('zoom-out')?.addEventListener('click', function() {
svg.transition().duration(300).call(zoom.scaleBy, 0.7);
});
document.getElementById('reset-view')?.addEventListener('click', resetView);
});
</script>
{% endblock %}

View File

@@ -1,131 +0,0 @@
{% extends "base.html" %}
{% block title %}Profil | Wissenschaftliche Mindmap{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="glass p-8 mb-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
<div>
<h1 class="text-3xl font-bold text-white">Hallo, {{ current_user.username }}</h1>
<p class="text-white/70 mt-1">{{ current_user.email }}</p>
</div>
<div class="mt-4 md:mt-0">
<a href="{{ url_for('mindmap') }}"
class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300 inline-block">
Zur Mindmap
</a>
</div>
</div>
</div>
<div class="dark-glass p-8">
<h2 class="text-2xl font-bold text-white mb-6">Deine Gedanken</h2>
{% if thoughts %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for thought in thoughts %}
<div class="glass p-6 hover:shadow-lg transition-all" x-data="{ showActions: false }" @mouseenter="showActions = true" @mouseleave="showActions = false">
<div class="flex justify-between items-start">
<span class="inline-block px-3 py-1 text-xs text-white/70 bg-white/10 rounded-full mb-3">{{ thought.branch }}</span>
<span class="text-xs text-white/50">{{ thought.timestamp.strftime('%d.%m.%Y, %H:%M') }}</span>
</div>
<p class="text-white mb-4 leading-relaxed">{{ thought.content }}</p>
<div class="flex justify-between items-center" x-show="showActions" x-transition.opacity>
<div class="text-xs text-white/70">
<span>{{ thought.comments|length }} Kommentar(e)</span>
</div>
<a href="#" onclick="openThoughtDetails('{{ thought.id }}')" class="text-indigo-300 hover:text-white text-sm">Details anzeigen</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-12">
<p class="text-white/70 mb-4">Du hast noch keine Gedanken geteilt.</p>
<a href="{{ url_for('mindmap') }}" class="text-indigo-300 hover:text-white">Zur Mindmap gehen und mitmachen</a>
</div>
{% endif %}
</div>
</div>
<!-- Thought Detail Modal -->
<div id="thoughtModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
<div class="dark-glass p-8 w-full max-w-2xl max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-white" id="modalThoughtTitle">Gedanke Details</h3>
<button onclick="closeThoughtModal()" class="text-white/70 hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div id="modalContent" class="space-y-6">
<div class="glass p-4">
<div class="flex justify-between items-start mb-2">
<span class="inline-block px-3 py-1 text-xs text-white/70 bg-white/10 rounded-full" id="modalBranch"></span>
<span class="text-xs text-white/50" id="modalTimestamp"></span>
</div>
<p class="text-white" id="modalThoughtContent"></p>
</div>
<div>
<h4 class="text-lg font-medium text-white mb-3">Kommentare</h4>
<div id="commentsList" class="space-y-3"></div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function openThoughtDetails(thoughtId) {
fetch(`/api/thoughts/${thoughtId}`)
.then(response => response.json())
.then(thought => {
document.getElementById('modalThoughtTitle').textContent = `Gedanke von ${thought.author}`;
document.getElementById('modalBranch').textContent = thought.branch;
document.getElementById('modalTimestamp').textContent = thought.timestamp;
document.getElementById('modalThoughtContent').textContent = thought.content;
// Load comments
return fetch(`/api/comments/${thoughtId}`);
})
.then(response => response.json())
.then(comments => {
const commentsList = document.getElementById('commentsList');
commentsList.innerHTML = '';
if (comments.length === 0) {
commentsList.innerHTML = '<p class="text-white/50">Keine Kommentare vorhanden</p>';
return;
}
comments.forEach(comment => {
const commentEl = document.createElement('div');
commentEl.className = 'glass p-3';
commentEl.innerHTML = `
<div class="flex justify-between items-start mb-1">
<span class="text-sm font-medium text-white">${comment.author}</span>
<span class="text-xs text-white/50">${comment.timestamp}</span>
</div>
<p class="text-white/90 text-sm">${comment.content}</p>
`;
commentsList.appendChild(commentEl);
});
})
.catch(error => console.error('Error loading thought details:', error));
document.getElementById('thoughtModal').classList.remove('hidden');
}
function closeThoughtModal() {
document.getElementById('thoughtModal').classList.add('hidden');
}
</script>
{% endblock %}

View File

@@ -1,47 +0,0 @@
{% extends "base.html" %}
{% block title %}Registrieren | Wissenschaftliche Mindmap{% endblock %}
{% block content %}
<div class="flex justify-center items-center min-h-[80vh]">
<div class="glass p-8 w-full max-w-md">
<h1 class="text-3xl font-bold text-white mb-6 text-center">Registrieren</h1>
<form method="POST" action="{{ url_for('register') }}" class="space-y-6">
<div>
<label for="username" class="block text-sm font-medium text-white mb-1">Benutzername</label>
<input type="text" id="username" name="username" required
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
</div>
<div>
<label for="email" class="block text-sm font-medium text-white mb-1">E-Mail</label>
<input type="email" id="email" name="email" required
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
</div>
<div>
<label for="password" class="block text-sm font-medium text-white mb-1">Passwort</label>
<input type="password" id="password" name="password" required
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
</div>
<div>
<button type="submit"
class="w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300">
Registrieren
</button>
</div>
</form>
<div class="mt-6 text-center">
<p class="text-white/70">
Bereits registriert?
<a href="{{ url_for('login') }}" class="text-indigo-300 hover:text-white font-medium transition-colors">
Anmelden
</a>
</p>
</div>
</div>
</div>
{% endblock %}