Files
deskhub/DeskHub-Webseite/assets/js/richtext.js
T
2026-10-16 19:12:15 +00:00

149 lines
5.6 KiB
JavaScript
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.
/**
* Verwandelt ein <textarea data-richtext> in einen einfachen Rich-Text-Editor
* (Fett/Kursiv/Unterstrichen/Listen/Link) ohne externe Bibliotheken. Der HTML-Inhalt
* wird laufend in das ursprüngliche (versteckte) Textfeld zurückgeschrieben, damit
* normale Formular-Submits unverändert funktionieren. Serverseitig wird der Wert
* zusätzlich über sanitize_staff_html() bereinigt – dieses Skript ist nur für Komfort,
* keine Sicherheitsgrenze.
*/
(function () {
function initRichText(textarea) {
if (!textarea || textarea.dataset.richtextReady) return;
textarea.dataset.richtextReady = '1';
const wrap = document.createElement('div');
wrap.className = 'richtext-wrap';
const toolbar = document.createElement('div');
toolbar.className = 'richtext-toolbar';
const editable = document.createElement('div');
editable.className = 'richtext-editable';
editable.contentEditable = 'true';
editable.innerHTML = textarea.value;
const buttons = [
['Fett', 'bold', 'B'],
['Kursiv', 'italic', 'I'],
['Unterstrichen', 'underline', 'U'],
['Aufzählung', 'insertUnorderedList', '•'],
['Nummerierte Liste', 'insertOrderedList', '1.'],
];
buttons.forEach(([title, cmd, label]) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.title = title;
btn.textContent = label;
btn.addEventListener('click', () => {
editable.focus();
document.execCommand(cmd, false, null);
textarea.value = editable.innerHTML;
});
toolbar.appendChild(btn);
});
const linkBtn = document.createElement('button');
linkBtn.type = 'button';
linkBtn.title = 'Link einfügen';
linkBtn.textContent = '🔗';
linkBtn.addEventListener('click', () => {
const url = window.prompt('Link-Adresse (https://...)');
if (url) {
editable.focus();
document.execCommand('createLink', false, url);
textarea.value = editable.innerHTML;
}
});
toolbar.appendChild(linkBtn);
// Bild einfügen: nur sichtbar, wenn ein Upload-Endpunkt angegeben ist
// (per data-image-upload-url am textarea, aktuell für Signaturen genutzt).
const uploadUrl = textarea.dataset.imageUploadUrl;
if (uploadUrl) {
const imgBtn = document.createElement('button');
imgBtn.type = 'button';
imgBtn.title = 'Bild einfügen';
imgBtn.textContent = '🖼️';
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/png,image/jpeg,image/gif';
fileInput.style.display = 'none';
fileInput.addEventListener('change', () => {
const file = fileInput.files && fileInput.files[0];
fileInput.value = '';
if (!file) return;
const metaToken = document.querySelector('meta[name="csrf"]');
const csrfToken = metaToken ? metaToken.content : '';
const formData = new FormData();
formData.append('image', file);
formData.append('csrf_token', csrfToken);
imgBtn.disabled = true;
imgBtn.textContent = '⏳';
fetch(uploadUrl, { method: 'POST', body: formData })
.then((r) => r.json())
.then((data) => {
if (data.error) {
window.alert(data.error);
return;
}
// Rotierten CSRF-Token überall auf der Seite aktualisieren, sonst schlägt
// das nachfolgende Absenden des umgebenden Formulars fehl.
if (data.csrfToken) {
document.querySelectorAll('input[name="csrf_token"]').forEach((el) => { el.value = data.csrfToken; });
if (metaToken) metaToken.content = data.csrfToken;
}
editable.focus();
document.execCommand('insertHTML', false, '<img src="' + data.url + '" alt="" style="max-width:100%">');
textarea.value = editable.innerHTML;
})
.catch(() => window.alert('Der Bild-Upload ist fehlgeschlagen.'))
.finally(() => {
imgBtn.disabled = false;
imgBtn.textContent = '🖼️';
});
});
imgBtn.addEventListener('click', () => fileInput.click());
toolbar.appendChild(imgBtn);
toolbar.appendChild(fileInput);
}
editable.addEventListener('input', () => { textarea.value = editable.innerHTML; });
editable.addEventListener('blur', () => { textarea.value = editable.innerHTML; });
const form = textarea.closest('form');
if (form) {
form.addEventListener('submit', () => { textarea.value = editable.innerHTML; });
}
textarea.style.display = 'none';
textarea.parentNode.insertBefore(wrap, textarea);
wrap.appendChild(toolbar);
wrap.appendChild(editable);
wrap.appendChild(textarea);
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('textarea[data-richtext]').forEach(initRichText);
});
// Hilfsfunktion für andere Skripte (z.B. Textbaustein-Auswahl), um Inhalt in ein
// ggf. durch initRichText() ersetztes Feld einzufügen – funktioniert mit und ohne Rich-Text-Editor.
window.insertIntoRichField = function (textareaId, html) {
const textarea = document.getElementById(textareaId);
if (!textarea) return;
const editable = textarea.previousElementSibling;
if (editable && editable.classList && editable.classList.contains('richtext-editable')) {
editable.innerHTML += (editable.innerHTML && editable.innerHTML !== '<br>' ? '<br><br>' : '') + html;
textarea.value = editable.innerHTML;
} else {
textarea.value += (textarea.value ? '\n\n' : '') + html;
}
};
})();