Community erstellt
This commit is contained in:
344
templates/community/edit_post.html
Normal file
344
templates/community/edit_post.html
Normal file
@@ -0,0 +1,344 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Beitrag bearbeiten{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.markdown-preview {
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.markdown-preview p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.markdown-preview h1, .markdown-preview h2, .markdown-preview h3,
|
||||
.markdown-preview h4, .markdown-preview h5, .markdown-preview h6 {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.markdown-preview h1 { font-size: 1.8rem; }
|
||||
.markdown-preview h2 { font-size: 1.5rem; }
|
||||
.markdown-preview h3 { font-size: 1.3rem; }
|
||||
.markdown-preview h4 { font-size: 1.1rem; }
|
||||
.markdown-preview ul, .markdown-preview ol {
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.markdown-preview ul { list-style-type: disc; }
|
||||
.markdown-preview ol { list-style-type: decimal; }
|
||||
.markdown-preview pre {
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.markdown-preview code {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 0.3em;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
.markdown-preview pre code {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
.markdown-preview blockquote {
|
||||
border-left: 4px solid;
|
||||
padding-left: 1rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.dark .markdown-preview code {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
.dark .markdown-preview blockquote {
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
.node-mention {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: rgba(109, 40, 217, 0.1);
|
||||
color: #6d28d9;
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
font-size: 0.9em;
|
||||
margin: 0 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.dark .node-mention {
|
||||
background-color: rgba(167, 139, 250, 0.2);
|
||||
color: #a78bfa;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<div class="mb-6 flex items-center text-sm">
|
||||
<a href="{{ url_for('community') }}" class="opacity-75 hover:opacity-100 transition-opacity">
|
||||
<i class="fas fa-home mr-1"></i> Forum
|
||||
</a>
|
||||
<span class="mx-2 opacity-50">/</span>
|
||||
<a href="{{ url_for('forum_category', category_id=post.category_id) }}" class="opacity-75 hover:opacity-100 transition-opacity">
|
||||
{{ post.category.title }}
|
||||
</a>
|
||||
<span class="mx-2 opacity-50">/</span>
|
||||
{% if post.parent_id %}
|
||||
<a href="{{ url_for('forum_post', post_id=post.parent_id) }}" class="opacity-75 hover:opacity-100 transition-opacity truncate max-w-[200px]">
|
||||
{{ post.parent.title }}
|
||||
</a>
|
||||
<span class="mx-2 opacity-50">/</span>
|
||||
{% endif %}
|
||||
<span class="font-medium">Beitrag bearbeiten</span>
|
||||
</div>
|
||||
|
||||
<!-- Formular-Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold mb-2">Beitrag bearbeiten</h1>
|
||||
<p class="opacity-75">
|
||||
{% if post.parent_id %}
|
||||
Antwort auf <span class="font-medium">{{ post.parent.title }}</span>
|
||||
{% else %}
|
||||
in der Kategorie <span class="font-medium">{{ post.category.title }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Formular -->
|
||||
<div class="mb-8 rounded-xl overflow-hidden"
|
||||
x-bind:class="darkMode ? 'bg-gray-800/60 border border-white/10' : 'bg-white border border-gray-200'">
|
||||
<div class="p-4 border-b font-medium" x-bind:class="darkMode ? 'border-white/10' : 'border-gray-200'">
|
||||
<i class="fas fa-edit mr-2"></i>
|
||||
Beitrag bearbeiten
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<form action="{{ url_for('edit_post', post_id=post.id) }}" method="POST" x-data="{
|
||||
title: '{{ post.title|safe }}',
|
||||
content: '{{ post.content|replace('\n', '\\n')|replace('\'', '\\\'')|safe }}',
|
||||
showPreview: false,
|
||||
previewHtml: '',
|
||||
|
||||
updatePreview() {
|
||||
// Verarbeite den Inhalt
|
||||
if (this.content.trim() === '') {
|
||||
this.previewHtml = '<div class=\'opacity-50 italic\'>Die Vorschau wird hier angezeigt...</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Verarbeite Markdown
|
||||
let html = marked.parse(this.content);
|
||||
|
||||
// Ersetze @Knotenname mit entsprechenden Links
|
||||
html = html.replace(/@([a-zA-Z0-9äöüÄÖÜß_-]+)/g, '<span class=\'node-mention\'><i class=\'fas fa-diagram-project fa-xs mr-1\'></i>$1</span>');
|
||||
|
||||
this.previewHtml = html;
|
||||
}
|
||||
}">
|
||||
<div class="mb-6">
|
||||
<label for="title" class="block mb-2 font-medium">Titel</label>
|
||||
<div class="rounded-lg overflow-hidden"
|
||||
x-bind:class="darkMode ? 'border border-white/20' : 'border border-gray-300'">
|
||||
<input type="text" id="title" name="title"
|
||||
class="w-full px-4 py-3"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gray-700/50 text-white placeholder-gray-400 focus:border-indigo-500'
|
||||
: 'bg-white text-gray-700 placeholder-gray-400 focus:border-indigo-500'"
|
||||
x-model="title"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label for="content" class="font-medium">Inhalt</label>
|
||||
<div class="flex space-x-2">
|
||||
<button type="button"
|
||||
class="px-3 py-1 rounded text-sm flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gray-700 hover:bg-gray-600 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300 text-gray-700'"
|
||||
@click="showPreview = false"
|
||||
x-bind:disabled="!showPreview"
|
||||
x-bind:class="{'opacity-50': !showPreview}">
|
||||
<i class="fas fa-edit mr-1"></i> Bearbeiten
|
||||
</button>
|
||||
<button type="button"
|
||||
class="px-3 py-1 rounded text-sm flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gray-700 hover:bg-gray-600 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300 text-gray-700'"
|
||||
@click="updatePreview(); showPreview = true"
|
||||
x-bind:disabled="showPreview"
|
||||
x-bind:class="{'opacity-50': showPreview}">
|
||||
<i class="fas fa-eye mr-1"></i> Vorschau
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor -->
|
||||
<div class="rounded-lg overflow-hidden mb-2"
|
||||
x-bind:class="darkMode ? 'border border-white/20' : 'border border-gray-300'"
|
||||
x-show="!showPreview">
|
||||
<textarea id="content" name="content" rows="12"
|
||||
class="w-full p-3 resize-y"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gray-700/50 text-white placeholder-gray-400 focus:border-indigo-500'
|
||||
: 'bg-white text-gray-700 placeholder-gray-400 focus:border-indigo-500'"
|
||||
x-model="content"
|
||||
required></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Preview -->
|
||||
<div class="rounded-lg overflow-hidden mb-2 p-4 markdown-preview"
|
||||
x-bind:class="darkMode
|
||||
? 'border border-white/20 bg-gray-700/30'
|
||||
: 'border border-gray-300 bg-gray-50'"
|
||||
x-show="showPreview"
|
||||
x-html="previewHtml">
|
||||
</div>
|
||||
|
||||
<!-- Markdown-Hilfsmittel -->
|
||||
<div class="mb-4" x-show="!showPreview">
|
||||
<div class="text-xs opacity-70">
|
||||
<p>Du kannst Knotenpunkte der Mindmap durch <code>@Knotenname</code> verlinken.</p>
|
||||
<p>Dieser Editor unterstützt Markdown-Formatierung:</p>
|
||||
<div class="flex flex-wrap gap-2 mt-1">
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="**" data-before="" data-after="" title="Fett">
|
||||
<i class="fas fa-bold"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="*" data-before="" data-after="" title="Kursiv">
|
||||
<i class="fas fa-italic"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="`" data-before="" data-after="" title="Code">
|
||||
<i class="fas fa-code"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="[Link-Text](URL)" data-before="" data-after="" title="Link">
|
||||
<i class="fas fa-link"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="\n```\nCode-Block\n```" data-before="" data-after="" title="Code-Block">
|
||||
<i class="fas fa-file-code"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format=">" data-before="" data-after="" title="Zitat">
|
||||
<i class="fas fa-quote-right"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="- " data-before="" data-after="" title="Liste">
|
||||
<i class="fas fa-list-ul"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="1. " data-before="" data-after="" title="Nummerierte Liste">
|
||||
<i class="fas fa-list-ol"></i>
|
||||
</button>
|
||||
<button type="button" class="markdown-button px-2 py-1 rounded text-xs" data-format="# " data-before="" data-after="" title="Überschrift">
|
||||
<i class="fas fa-heading"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="{{ url_for('forum_post', post_id=post.parent_id or post.id) }}"
|
||||
class="px-5 py-2.5 rounded-lg transition-all duration-300 flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gray-700 hover:bg-gray-600 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300 text-gray-700'">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="px-5 py-2.5 rounded-lg transition-all duration-300 flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-indigo-700 hover:bg-indigo-600 text-white'
|
||||
: 'bg-indigo-500 hover:bg-indigo-600 text-white'">
|
||||
<i class="fas fa-save mr-2"></i>
|
||||
Änderungen speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Markdown-Buttons für den Beitragseditor
|
||||
document.querySelectorAll('.markdown-button').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const textarea = document.getElementById('content');
|
||||
const format = this.dataset.format;
|
||||
const before = this.dataset.before || '';
|
||||
const after = this.dataset.after || '';
|
||||
|
||||
// Hole die aktuelle Auswahl
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const selection = textarea.value.substring(start, end);
|
||||
|
||||
// Wende die Formatierung an
|
||||
let formattedText;
|
||||
if (format.includes('\n')) {
|
||||
// Für Formate mit Zeilenumbrüchen (z.B. Code-Blöcke)
|
||||
formattedText = format.replace('Code-Block', selection || 'Code-Block');
|
||||
} else if (format.includes('[Link-Text](URL)')) {
|
||||
formattedText = format.replace('Link-Text', selection || 'Link-Text');
|
||||
} else if (format === '- ' || format === '1. ' || format === '# ' || format === '> ') {
|
||||
// Für Listen und Überschriften: am Anfang der Zeile einfügen
|
||||
const beforeSelection = textarea.value.substring(0, start);
|
||||
const afterSelection = textarea.value.substring(end);
|
||||
|
||||
// Finde den Anfang der aktuellen Zeile
|
||||
const lastNewline = beforeSelection.lastIndexOf('\n');
|
||||
const lineStart = lastNewline === -1 ? 0 : lastNewline + 1;
|
||||
|
||||
// Füge das Format am Zeilenanfang ein
|
||||
formattedText = beforeSelection.substring(0, lineStart) +
|
||||
format +
|
||||
beforeSelection.substring(lineStart) +
|
||||
selection +
|
||||
afterSelection;
|
||||
|
||||
// Setze die neue Cursor-Position
|
||||
const newCursorPos = end + format.length;
|
||||
textarea.value = formattedText;
|
||||
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
||||
|
||||
// Alpine.js Model aktualisieren
|
||||
textarea.dispatchEvent(new Event('input'));
|
||||
return; // Früher zurückkehren, da wir die Formatierung bereits angewendet haben
|
||||
} else {
|
||||
// Für einfache Formatierungen wie fett, kursiv, Code
|
||||
formattedText = before + format + selection + format + after;
|
||||
}
|
||||
|
||||
// Ersetze den Text
|
||||
textarea.value = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end);
|
||||
|
||||
// Setze den Fokus zurück auf das Textarea
|
||||
textarea.focus();
|
||||
|
||||
// Alpine.js Model aktualisieren
|
||||
textarea.dispatchEvent(new Event('input'));
|
||||
|
||||
// Setze die Auswahl neu, wenn es eine Auswahl gab
|
||||
if (selection) {
|
||||
const newStart = start + before.length + format.length;
|
||||
const newEnd = newStart + selection.length;
|
||||
textarea.setSelectionRange(newStart, newEnd);
|
||||
} else {
|
||||
// Setze den Cursor in die Mitte von **|** oder `|`
|
||||
const newCursorPos = start + before.length + format.length;
|
||||
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user