Files
stiftung-management-system/app/templates/stiftung/vorlage_editor.html
SysAdmin Agent fe2c657586
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
Fix Vorlagen editor: drop Summernote, use code editor for all templates (STI-82)
Summernote WYSIWYG was mangling Django template syntax ({{ }}, {% %})
on save, causing content to revert to corrupted state. Switched all
template types to the plain code editor textarea which preserves
content exactly as-is.

Also removed jQuery/Summernote JS dependencies from the editor page,
and fixed getEditorContent reference in preview code.

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

257 lines
10 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' %}
{% block title %}{{ vorlage.bezeichnung }} Vorlage bearbeiten{% endblock %}
{% block extra_css %}
<style>
.preview-frame {
width: 100%;
height: 75vh;
border: 1px solid #dee2e6;
border-radius: 4px;
background: #fff;
}
.var-list {
max-height: 55vh;
overflow-y: auto;
}
.var-item {
cursor: pointer;
font-family: monospace;
font-size: 12px;
}
.var-item:hover {
background-color: #e9ecef;
}
.code-editor-textarea {
width: 100%;
height: 70vh;
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);
}
#tab-vorschau .preview-loading {
text-align: center;
padding: 80px 0;
color: #6c757d;
}
</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
</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>
<!-- Tabs: Editor | Vorschau -->
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-editor" type="button" role="tab">
<i class="fas fa-code me-1"></i>Editor
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-vorschau" type="button" role="tab" id="btn-tab-vorschau">
<i class="fas fa-eye me-1"></i>Vorschau
</button>
</li>
<li class="nav-item ms-auto">
<button type="submit" form="editor-form" class="btn btn-sm btn-primary my-1">
<i class="fas fa-save me-1"></i>Speichern
</button>
</li>
</ul>
<div class="tab-content">
<!-- Editor Tab -->
<div class="tab-pane fade show active" id="tab-editor" role="tabpanel">
<div class="row">
<div class="{% if variablen %}col-lg-9{% else %}col-12{% endif %}">
<form method="post" id="editor-form">
{% csrf_token %}
<textarea name="html_inhalt" id="code-editor" class="code-editor-textarea">{{ vorlage.html_inhalt }}</textarea>
<script type="application/json" id="vorlage-html-inhalt">{{ html_inhalt_json }}</script>
</form>
</div>
{% if variablen %}
<div class="col-lg-3">
<div class="card shadow mb-3">
<div class="card-header py-2">
<h6 class="m-0 fw-bold text-primary small">
<i class="fas fa-code me-1"></i>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>
<div class="card shadow">
<div class="card-header py-2">
<h6 class="m-0 fw-bold text-secondary small">
<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>
{% endif %}
</div>
</div>
<!-- Vorschau Tab -->
<div class="tab-pane fade" id="tab-vorschau" role="tabpanel">
<div class="preview-loading" id="preview-loading">
<i class="fas fa-spinner fa-spin fa-2x mb-2 d-block"></i>
Vorschau wird geladen...
</div>
<iframe id="preview-frame" class="preview-frame" title="Vorschau" style="display:none"></iframe>
</div>
</div>
{% endblock %}
{% block javascript %}
<script>
(function() {
var initialContent;
try {
initialContent = JSON.parse(document.getElementById('vorlage-html-inhalt').textContent);
} catch(e) {
initialContent = null;
}
var editor = document.getElementById('code-editor');
// ---- Editor setup: plain code editor for all templates ----
// Always load content from JSON (the textarea's Django-rendered value may be HTML-escaped)
if (initialContent !== null) {
editor.value = initialContent;
}
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;
}
});
// Variable insertion
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();
});
});
// ---- Preview (always set up, independent of editor mode) ----
var previewFrame = document.getElementById('preview-frame');
var previewLoading = document.getElementById('preview-loading');
function loadPreview() {
var content = editor.value;
var csrfEl = document.querySelector('[name=csrfmiddlewaretoken]');
if (!csrfEl) {
previewLoading.innerHTML = '<i class="fas fa-exclamation-triangle text-danger fa-2x mb-2 d-block"></i>CSRF-Token nicht gefunden.';
return;
}
var formData = new FormData();
formData.append('html_inhalt', content);
formData.append('csrfmiddlewaretoken', csrfEl.value);
previewLoading.style.display = 'block';
previewFrame.style.display = 'none';
fetch('{% url "stiftung:vorlage_vorschau" pk=vorlage.pk %}', {
method: 'POST',
body: formData,
credentials: 'same-origin',
})
.then(function(r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.text();
})
.then(function(html) {
previewFrame.srcdoc = html;
previewFrame.style.display = 'block';
previewLoading.style.display = 'none';
})
.catch(function(err) {
previewLoading.innerHTML = '<i class="fas fa-exclamation-triangle text-danger fa-2x mb-2 d-block"></i>Vorschau fehlgeschlagen: ' + err;
});
}
// Load preview when clicking the Vorschau tab (direct click — more reliable than Bootstrap tab events)
document.getElementById('btn-tab-vorschau').addEventListener('click', function() {
// Small delay so the tab pane is visible before we load
setTimeout(loadPreview, 100);
});
})();
</script>
{% endblock %}