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>
This commit is contained in:
@@ -57,10 +57,9 @@ def vorlage_editor(request, pk):
|
||||
html_json = json.dumps(vorlage.html_inhalt)
|
||||
html_json = html_json.replace("<", "\\u003c").replace(">", "\\u003e")
|
||||
|
||||
# Serienbrief templates are full HTML documents with Django template tags
|
||||
# ({% for %}, {% if %}) — Summernote WYSIWYG mangles these.
|
||||
# Use a plain code editor textarea instead.
|
||||
use_code_editor = vorlage.kategorie == "serienbrief"
|
||||
# All templates contain Django template tags ({{ }}, {% %}) that
|
||||
# Summernote WYSIWYG mangles on save. Use plain code editor for all.
|
||||
use_code_editor = True
|
||||
|
||||
return render(request, "stiftung/vorlage_editor.html", {
|
||||
"vorlage": vorlage,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% 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%;
|
||||
@@ -26,9 +22,6 @@
|
||||
.var-item:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.note-editor.note-frame {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.code-editor-textarea {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
@@ -113,7 +106,7 @@
|
||||
<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"{% if use_code_editor %} class="code-editor-textarea"{% endif %}>{{ vorlage.html_inhalt }}</textarea>
|
||||
<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>
|
||||
@@ -179,11 +172,6 @@
|
||||
{% 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;
|
||||
@@ -193,111 +181,40 @@
|
||||
initialContent = null;
|
||||
}
|
||||
|
||||
var useCodeEditor = {{ use_code_editor|yesno:"true,false" }};
|
||||
var editor = document.getElementById('code-editor');
|
||||
var summernoteActive = false;
|
||||
|
||||
// Returns current HTML content regardless of editor mode
|
||||
function getEditorContent() {
|
||||
if (summernoteActive) {
|
||||
return $('#code-editor').summernote('code');
|
||||
}
|
||||
return editor.value;
|
||||
// ---- 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 setup ----
|
||||
if (useCodeEditor) {
|
||||
// Plain textarea for full HTML documents (Serienbrief)
|
||||
if (initialContent) {
|
||||
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;
|
||||
}
|
||||
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();
|
||||
});
|
||||
// Variable insertion for code editor
|
||||
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();
|
||||
});
|
||||
});
|
||||
} else if (typeof $ !== 'undefined' && typeof $.fn.summernote !== 'undefined') {
|
||||
// Summernote WYSIWYG for HTML fragment templates
|
||||
$('#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
summernoteActive = true;
|
||||
|
||||
// Variable insertion for Summernote
|
||||
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);
|
||||
$('#code-editor').summernote('focus');
|
||||
$('#code-editor').summernote('insertText', placeholder);
|
||||
});
|
||||
});
|
||||
|
||||
// Sync Summernote to textarea on form submit
|
||||
document.getElementById('editor-form').addEventListener('submit', function() {
|
||||
document.querySelector('textarea[name=html_inhalt]').value = getEditorContent();
|
||||
});
|
||||
} else {
|
||||
// Fallback: Summernote not loaded — style textarea as code editor
|
||||
if (editor) {
|
||||
editor.style.height = '70vh';
|
||||
editor.style.fontFamily = "'SFMono-Regular', Consolas, monospace";
|
||||
editor.style.fontSize = '13px';
|
||||
editor.style.padding = '12px';
|
||||
editor.style.background = '#f8f9fa';
|
||||
}
|
||||
if (initialContent) {
|
||||
editor.value = initialContent;
|
||||
}
|
||||
// Variable insertion for plain textarea fallback
|
||||
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 = getEditorContent();
|
||||
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.';
|
||||
|
||||
Reference in New Issue
Block a user