Files
stiftung-management-system/app/templates/stiftung/vorlage_editor.html
SysAdmin Agent aed540fe4b
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Code Quality / quality (push) Has been cancelled
Add Vorlagen editor, upload portal, onboarding, and participant import command
- Dokument-Vorlagen-Editor: create/edit/reset document templates (admin)
- Upload-Portal: public portal for Nachweis uploads via token
- Onboarding: invite Destinatäre via email with multi-step wizard
- Bestätigungsschreiben: preview and send confirmation letters
- Email settings: SMTP configuration UI
- Management command: import_veranstaltung_teilnehmer for bulk participant import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 09:25:18 +00:00

312 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% load static %}
{% block title %}{{ vorlage.bezeichnung }} Vorlage bearbeiten{% endblock %}
{% block extra_css %}
<!-- Summernote WYSIWYG (lokal) -->
<link rel="stylesheet" href="{% static 'stiftung/vendor/summernote/summernote-bs5.min.css' %}">
<style>
.preview-frame {
width: 100%;
height: 580px;
border: 1px solid #dee2e6;
border-radius: 4px;
background: #fff;
}
.var-list {
max-height: 400px;
overflow-y: auto;
}
.var-item {
cursor: pointer;
font-family: monospace;
font-size: 12px;
}
.var-item:hover {
background-color: #e9ecef;
}
.note-editor.note-frame {
border-radius: 4px;
}
.code-editor-textarea {
width: 100%;
height: 520px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 13px;
line-height: 1.5;
padding: 12px;
border: 1px solid #dee2e6;
border-radius: 4px;
resize: vertical;
tab-size: 4;
white-space: pre;
overflow-wrap: normal;
overflow-x: auto;
background: #f8f9fa;
}
.code-editor-textarea:focus {
outline: none;
border-color: #86b7fe;
box-shadow: 0 0 0 .25rem rgba(13,110,253,.25);
}
</style>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between mb-3">
<div>
<h1 class="h3 mb-0 text-gray-800">
<i class="fas fa-file-code me-2"></i>{{ vorlage.bezeichnung }}
</h1>
<small class="text-muted"><code>{{ vorlage.schluessel }}</code> &nbsp;·&nbsp;
Kategorie: {{ vorlage.get_kategorie_display }}</small>
</div>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:vorlagen_liste' %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück zur Liste
</a>
{% if hat_original %}
<form method="post" action="{% url 'stiftung:vorlage_zuruecksetzen' pk=vorlage.pk %}"
onsubmit="return confirm('Alle Änderungen verwerfen und auf Original zurücksetzen?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-warning">
<i class="fas fa-undo me-1"></i>Original wiederherstellen
</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<!-- Editor (links) -->
<div class="col-lg-8">
<form method="post" id="editor-form">
{% csrf_token %}
<div class="d-flex gap-2 mb-2 justify-content-end">
<button type="button" class="btn btn-sm btn-outline-secondary" id="btn-preview">
<i class="fas fa-eye me-1"></i>Vorschau
</button>
<button type="submit" class="btn btn-sm btn-primary">
<i class="fas fa-save me-1"></i>Speichern
</button>
</div>
<textarea name="html_inhalt" id="code-editor"{% if use_code_editor %} class="code-editor-textarea"{% endif %}>{{ vorlage.html_inhalt }}</textarea>
<script type="application/json" id="vorlage-html-inhalt">{{ html_inhalt_json }}</script>
</form>
<!-- Vorschau-Bereich (initial versteckt) -->
<div id="preview-area" class="mt-3" style="display:none">
<div class="d-flex align-items-center justify-content-between mb-2">
<strong>Vorschau <span class="badge bg-secondary">Beispieldaten</span></strong>
<button type="button" class="btn btn-sm btn-outline-secondary" id="btn-close-preview">
<i class="fas fa-times me-1"></i>Vorschau schließen
</button>
</div>
<iframe id="preview-frame" class="preview-frame" title="Vorschau"></iframe>
</div>
</div>
<!-- Seitenleiste (rechts) -->
<div class="col-lg-4">
{% if variablen %}
<div class="card shadow mb-3">
<div class="card-header py-2">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-code me-1"></i>Verfügbare Variablen
</h6>
<small class="text-muted">Klick zum Einfügen</small>
</div>
<div class="card-body p-0">
<div class="var-list">
<table class="table table-sm mb-0">
<tbody>
{% for var, beschreibung in variablen.items %}
<tr class="var-item" data-var="{{ var }}">
<td class="ps-3 py-1">
<code>{% templatetag openvariable %} {{ var }} {% templatetag closevariable %}</code>
</td>
<td class="pe-3 py-1 text-muted small">{{ beschreibung }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<div class="card shadow">
<div class="card-header py-2">
<h6 class="m-0 font-weight-bold text-secondary">
<i class="fas fa-info-circle me-1"></i>Info
</h6>
</div>
<div class="card-body small">
<dl class="mb-0">
<dt>Zuletzt bearbeitet</dt>
<dd class="text-muted">{{ vorlage.zuletzt_bearbeitet_am|date:"d.m.Y H:i" }}</dd>
{% if vorlage.zuletzt_bearbeitet_von %}
<dt>Bearbeitet von</dt>
<dd class="text-muted">{{ vorlage.zuletzt_bearbeitet_von.get_full_name|default:vorlage.zuletzt_bearbeitet_von.username }}</dd>
{% endif %}
<dt>Erstellt am</dt>
<dd class="text-muted mb-0">{{ vorlage.erstellt_am|date:"d.m.Y" }}</dd>
</dl>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
<!-- jQuery (lokal) -->
<script src="{% static 'stiftung/vendor/jquery/jquery.min.js' %}"></script>
<!-- Summernote WYSIWYG (lokal) -->
<script src="{% static 'stiftung/vendor/summernote/summernote-bs5.min.js' %}"></script>
<script src="{% static 'stiftung/vendor/summernote/summernote-de-DE.min.js' %}"></script>
<script>
(function() {
var initialContent;
try {
initialContent = JSON.parse(document.getElementById('vorlage-html-inhalt').textContent);
} catch(e) {
initialContent = null;
}
var useCodeEditor = {{ use_code_editor|yesno:"true,false" }};
var editor = document.getElementById('code-editor');
// Code-Editor-Modus: Plain textarea fuer vollstaendige HTML-Dokumente
// (Serienbrief-Vorlagen mit DOCTYPE, Template-Tags usw.)
if (useCodeEditor) {
if (initialContent) {
editor.value = initialContent;
}
// Tab-Taste einfügen statt Fokus wechseln
editor.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
this.selectionStart = this.selectionEnd = start + 4;
}
});
// Variablen einfügen bei Klick
document.querySelectorAll('.var-item').forEach(function(row) {
row.addEventListener('click', function() {
var varName = this.getAttribute('data-var');
var placeholder = String.fromCharCode(123,123) + ' ' + varName + ' ' + String.fromCharCode(125,125);
var start = editor.selectionStart;
editor.value = editor.value.substring(0, start) + placeholder + editor.value.substring(editor.selectionEnd);
editor.selectionStart = editor.selectionEnd = start + placeholder.length;
editor.focus();
});
});
// Vorschau
var previewArea = document.getElementById('preview-area');
var previewFrame = document.getElementById('preview-frame');
document.getElementById('btn-preview').addEventListener('click', function() {
var formData = new FormData();
formData.append('html_inhalt', editor.value);
formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]').value);
fetch('{% url "stiftung:vorlage_vorschau" pk=vorlage.pk %}', { method: 'POST', body: formData })
.then(function(r) { return r.text(); })
.then(function(html) {
previewFrame.srcdoc = html;
previewArea.style.display = 'block';
previewArea.scrollIntoView({behavior: 'smooth'});
})
.catch(function(err) { alert('Vorschau fehlgeschlagen: ' + err); });
});
document.getElementById('btn-close-preview').addEventListener('click', function() {
previewArea.style.display = 'none';
});
return; // Skip Summernote initialization
}
if (typeof $ === 'undefined' || typeof $.fn.summernote === 'undefined') {
// Fallback: Summernote nicht geladen — Textarea sichtbar lassen
if (editor) { editor.style.height = '520px'; editor.style.fontFamily = 'monospace'; editor.style.fontSize = '13px'; }
return;
}
// Summernote initialisieren (für HTML-Fragment-Vorlagen: E-Mail, PDF-Fragmente)
$('#code-editor').summernote({
lang: 'de-DE',
height: 520,
toolbar: [
['style', ['bold', 'italic', 'underline', 'strikethrough', 'clear']],
['para', ['style', 'ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'hr']],
['view', ['fullscreen', 'codeview', 'undo', 'redo']],
],
callbacks: {
onInit: function() {
if (initialContent) {
$('#code-editor').summernote('code', initialContent);
}
}
}
});
// Variablen einfügen bei Klick
document.querySelectorAll('.var-item').forEach(function(row) {
row.addEventListener('click', function() {
const varName = this.getAttribute('data-var');
const placeholder = String.fromCharCode(123,123) + ' ' + varName + ' ' + String.fromCharCode(125,125);
$('#code-editor').summernote('focus');
$('#code-editor').summernote('insertText', placeholder);
});
});
// Vorschau
const previewArea = document.getElementById('preview-area');
const previewFrame = document.getElementById('preview-frame');
const btnPreview = document.getElementById('btn-preview');
const btnClosePreview = document.getElementById('btn-close-preview');
btnPreview.addEventListener('click', function() {
const content = $('#code-editor').summernote('code');
const formData = new FormData();
formData.append('html_inhalt', content);
formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]').value);
fetch('{% url "stiftung:vorlage_vorschau" pk=vorlage.pk %}', {
method: 'POST',
body: formData,
})
.then(r => r.text())
.then(html => {
previewFrame.srcdoc = html;
previewArea.style.display = 'block';
previewArea.scrollIntoView({behavior: 'smooth'});
})
.catch(err => alert('Vorschau fehlgeschlagen: ' + err));
});
btnClosePreview.addEventListener('click', function() {
previewArea.style.display = 'none';
});
// Formular-Submit: Summernote-Inhalt in Textarea schreiben
document.getElementById('editor-form').addEventListener('submit', function() {
// Summernote schreibt den Inhalt automatisch in die Textarea beim Submit
// Sicherheitshalber explizit synchronisieren:
const content = $('#code-editor').summernote('code');
document.querySelector('textarea[name=html_inhalt]').value = content;
});
})();
</script>
{% endblock %}