← Gesamte Dokumentation

Dateien hochladen

Probleme? Folgen Sie der Anleitung zur Fehlerbehebung beim Bild-Upload bei fehlenden Requests, Fehlern 413 und nach dem Speichern verlorenen Bildern.

Den Upload-Handler schreiben Sie selbst. Er erhält die Datei, die der Benutzer eingefügt, aus der Zwischenablage eingefügt oder hineingezogen hat, sendet sie an Ihren Server und gibt dem Editor die URL zurück, die in das Dokument kommt. Die Beispiele im Download enthalten einen funktionierenden Handler für ASP.NET Web Forms, MVC, ASP.NET Core und PHP sowie das Client-Skript unten.

Konfigurieren Sie einen Handler, sonst werden Dateien in den Inhalt eingebettet

Ist kein Handler konfiguriert, wird nichts hochgeladen, die Datei muss aber trotzdem irgendwohin, also landet sie im Dokument:

  • Ein Bild wird als Base64-data:-URL eingebettet. Das Bild wird Teil des gespeicherten HTML, und Base64 fügt etwa ein Drittel hinzu: Aus einem Foto mit 3 MB werden rund 4 MB Text in einem Feld. Das ist die übliche Ursache dafür, dass ein Speichern in die Datenbank fehlschlägt, abgeschnitten wird oder in ein Timeout läuft.
  • Ein Dokument wird als Link mit dem Dateinamen eingefügt, aber ohne funktionierende Adresse: Der Editor verweist auf eine temporäre blob:-URL, und der Sanitizer für Inhalte entfernt sie, weil blob: nicht zu den erlaubten Schemata gehört. Der Leser erhält einen Link, der sich nicht öffnen lässt. Setzen Sie fileUploadLocalObjectUrl: false, um das Einfügen stattdessen abzulehnen und den Benutzer zu informieren.

Mit einem Handler enthält der Inhalt eine kurze URL wie /imageuploads/20260918-a1b2c3.png. Um ein vorhandenes Dokument zu prüfen, sehen Sie sich das gespeicherte HTML an: src="data:image/... bedeutet, dass der Handler nicht verwendet wird.

Die Upload-Schaltfläche des Dialogs Insert Gallery sucht nur nach der globalen Funktion window.rte_file_upload_handler. Setzen Sie nur die Option file_upload_handler pro Editor, zeigt die Galerie keine Upload-Schaltfläche.

Der kurze Weg: dem Editor eine URL geben

Nimmt Ihr Server bereits eine Datei an und antwortet mit ihrer URL, müssen Sie überhaupt kein JavaScript schreiben:

var config = {};

// The whole setup: the editor POSTs the file here and uses the URL you answer with.
config.file_upload_url = "/imageupload.ashx";

var editor = new RichTextEditor("#div_editor", config);

Der Editor sendet die Datei per POST als multipart/form-data und liest die URL aus der Antwort. Er versteht die Formate, mit denen die Endpoints anderer Editoren bereits antworten, daher funktioniert ein für TinyMCE oder CKEditor geschriebener Server hier unverändert:

Body der AntwortGeschrieben für
{"location": "/img/x.png"}TinyMCE
{"url": "/img/x.png"}CKEditor Simple Upload Adapter
{"urls": {"default": "..."}}CKEditor, mehrere Größen
READY:/img/x.pngdie Beispiel-Handler unten
{"error": {"message": "..."}} oder ERROR:messagebeide, als Fehler gemeldet

Eine Antwort in keinem dieser Formate wird als fehlgeschlagener Upload behandelt statt eingefügt, sodass ein Endpoint, der mit OK antwortet, nicht zur Bildquelle werden kann.

config.file_upload_field = "file";          // form field name; CKEditor's endpoints use "upload"
config.file_upload_base_path = "https://cdn.example.com";  // prepended to a relative URL
config.file_upload_credentials = true;      // send cookies on a cross-origin upload
config.file_upload_reuse_filename = true;   // keep the original name (default: a unique one)
config.file_upload_headers = { "X-CSRF-Token": token };

Jede hochgeladene Datei erhält standardmäßig einen eindeutigen Namen, weil sich zwei Personen, die image.png an denselben Endpoint hochladen, nicht gegenseitig überschreiben dürfen. Wiederholungen, Abbruch und das Fortschrittsereignis funktionieren genauso wie mit Ihrem eigenen Handler. Schreiben Sie stattdessen einen Handler, wenn Sie vor dem Senden verkleinern, direkt zu S3 hochladen oder einen eigenen Dialog zeigen müssen – er hat Vorrang, wenn beides gesetzt ist.

Syntax

// One global handler for every editor on the page.
// The Insert Gallery dialog's Upload button uses this one.
window.rte_file_upload_handler = function (file, callback, optionalIndex, optionalFiles) { ... };

// Or per editor, for the image dialog, paste and drag-and-drop.
var config = {};
config.file_upload_handler = function (file, callback, optionalIndex, optionalFiles) { ... };
var editor = new RichTextEditor("#div_editor", config);
file

Type: File – das File-Objekt des Browsers mit name, size und type. Senden Sie es mit XMLHttpRequest oder fetch.

callback

Type: Function callback(url, errorMessage) – rufen Sie ihn genau einmal auf. Bei Erfolg übergeben Sie die gespeicherte URL; bei einem Fehler übergeben Sie null und eine Meldung, die der Editor dem Benutzer zeigt. Auch ein vom Benutzer abgebrochener Upload ist ein Fehler.

optionalIndex, optionalFiles

Werden mehrere Dateien auf einmal eingefügt, wird der Handler für jede aufgerufen: optionalFiles ist die ganze Menge und optionalIndex die Position dieser Datei, sodass sich der Fortschritt als „2 von 5“ melden lässt.

Die Beispiel-Handler antworten im Klartext: READY:<url> mit Status 200 oder ERROR:<message> mit Status 500. Das Client-Skript unten macht daraus die beiden Formen des Callbacks.

Einen Upload abbrechen

Geben Sie aus dem Handler ein Objekt mit einer Methode abort() (oder eine einfache Abbruchfunktion) zurück, dann kann der Editor einen noch laufenden Request stoppen:

config.file_upload_handler = function (file, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/imageupload.ashx");
    // ... send the file, then callback(url) or callback(null, message)
    xhr.send(formdata);

    // Returning this is what lets the editor stop the request.
    return { abort: function () { xhr.abort(); } };
};

editor.cancelUploads();          // abort everything in flight; returns the count
editor.getPendingUploadCount();  // how many are still running

cancelUploads() und destroy() brechen laufende Requests ab, wenn der Handler eine Abbruchfunktion bereitstellt. Wurde das Zielbild entfernt, wird ein verspätetes Ergebnis verworfen; das Entfernen eines Bildes garantiert für sich genommen keinen sofortigen Abbruch im Netzwerk. Der verspätete Callback eines abgebrochenen Uploads wird ignoriert, sodass eine langsame Antwort ein gelöschtes Bild nicht zurückbringen kann. Ein Ereignis uploadcancel wird mit der Datei und dem Grund ausgelöst. Nichts zurückzugeben funktioniert weiterhin: Der Upload lässt sich dann einfach nicht abbrechen.

Bereits im Inhalt eingebettete Bilder

Der Handler wird für Dateien aufgerufen, die der Benutzer einfügt, aus der Zwischenablage einfügt oder hineinzieht. Inhalt, der auf anderem Weg ankommt – mit setHTMLCode gesetzt, aus einer Webseite oder einem anderen Editor eingefügt oder aus einem Entwurf wiederhergestellt –, behält seine Base64-data:-URL, sodass diese Bilder im HTML gespeichert werden und Ihr Handler sie nie sieht. Rufen Sie vor dem Speichern uploadEmbeddedImages auf:

Für ein natives Formular verwenden Sie die IDs unten und ersetzen die Action durch Ihren Endpoint zum Speichern des Dokuments. Konfigurieren Sie den Upload-Handler, bevor Sie den Editor erstellen. Dieses Beispiel wartet auf Uploads, lehnt nicht aufgelöste lokale Bilder ab und erhält die native Validierung und die Absende-Schaltfläche.

<form id="article-form" action="/your-document-save-endpoint" method="post">
  <!-- Include your framework's CSRF token field here. -->
  <div id="div_editor"></div>
  <input id="content" name="content" type="hidden">
  <p id="upload-save-status" role="status" aria-live="polite"></p>
  <button type="submit">Save document</button>
</form>
var form = document.getElementById("article-form");
var content = document.getElementById("content");
var saveStatus = document.getElementById("upload-save-status");
var scanning = false, approvedHTML = null;

form.addEventListener("submit", function (event) {
    var current = editor.getHTMLCode();
    if (approvedHTML !== null) {
        var approved = approvedHTML;
        approvedHTML = null;
        if (current !== approved || editor.getPendingUploadCount() > 0) {
            event.preventDefault();
            saveStatus.textContent = "Content changed. Wait for uploads, then save again.";
            return;
        }
        content.value = current; // Fresh snapshot in the native submit event.
        return;
    }
    event.preventDefault();
    if (scanning || editor.getPendingUploadCount() > 0) {
        saveStatus.textContent = "Images are still uploading. Wait, then save again.";
        return;
    }
    scanning = true;
    var submitter = event.submitter;
    saveStatus.textContent = "Checking embedded images...";
    try {
        editor.uploadEmbeddedImages(function (summary) {
            scanning = false;
            var localImage = Array.prototype.some.call(
                editor.getDocument().querySelectorAll("img"), function (image) {
                    return /^(data:|blob:|file:)/i.test((image.getAttribute("src") || "").trim());
                });
            if (summary.failed || summary.skipped || localImage || editor.getPendingUploadCount() > 0) {
                saveStatus.textContent = "Some images are not ready. Review and retry or remove them, then save again.";
                return;
            }
            approvedHTML = editor.getHTMLCode();
            saveStatus.textContent = "Images ready. Complete any required fields before saving.";
            // Re-run native validation; preserve the initiating submit button.
            if (submitter && submitter.form === form) form.requestSubmit(submitter);
            else form.requestSubmit();
            approvedHTML = null; // Validation may have stopped the submit event.
        });
    } catch (error) {
        scanning = false;
        approvedHTML = null;
        saveStatus.textContent = "Could not prepare images. Check your upload handler and try again.";
    }
});

Die Methode durchläuft das aktuelle Dokument, wandelt jede Quelle data:image/ in ein File um, sendet sie einzeln durch den Handler und ersetzt jede Quelle durch die URL, die Sie zurückgeben. Bilder, die bereits auf http(s) liegen, bleiben unverändert. Ohne konfigurierten Handler ändert sich nichts, und jedes Bild wird als übersprungen gezählt.

Null ausstehende Uploads sind kein Erfolgsnachweis: Prüfen Sie nach einem fehlgeschlagenen oder abgebrochenen Upload auf fehlende Bilder. Rufen Sie nicht direkt form.submit() auf; das umgeht die native Validierung und Submit-Listener. Das Hochladen eines Bildes ist getrennt vom Speichern des Dokuments; Ihr Server muss das gesendete HTML authentifizieren, autorisieren und bereinigen, bevor er es speichert oder darstellt.

Automatisch hochladen und Fehlschläge wiederholen

automaticUploads plant den Durchlauf von oben für Sie ein. Das garantiert nicht, dass beim Speichern alles abgeschlossen ist: Behalten Sie die Prüfungen auf ausstehende Uploads und Fehler bei und untersuchen Sie nicht aufgelöste eingebettete Bilder, bevor Sie den Snapshot zum Speichern erstellen:

var config = {};
config.file_upload_handler = rte_file_upload_handler;

// Upload embedded base64 images by themselves, after setHTMLCode and after a paste.
config.automaticUploads = true;
config.automaticUploadsDelay = 300;   // ms debounce; a paste of several images uploads once

// Retry a failed upload before telling the user about it.
config.uploadRetryCount = 2;          // extra attempts after the first (default 0)
config.uploadRetryDelay = 1000;       // ms before the first retry; doubles after that

Der Durchlauf läuft per Debounce verzögert nach setHTMLCode und nach eingefügtem HTML und tut nichts, wenn kein Handler konfiguriert ist. uploadRetryCount deckt den anderen häufigen Fehler ab: Früher ließ eine einzige abgebrochene Verbindung das Bild dauerhaft eingebettet. Eine erfolgreiche Wiederholung geschieht ohne Meldung, jeder Versuch löst ein Ereignis uploadretry aus, und sind die Versuche aufgebraucht, wird der Fehler Ihres Callbacks wie bisher gemeldet.

Die Größe von Uploads und Inhalt begrenzen

Setzen Sie maxUploadFileSize, dann lehnt der Editor eine größere Datei selbst ab, bevor er sie liest. Nichts wird eingebettet, nichts wird gesendet, und der Benutzer erfährt, welche Größe abgelehnt wurde und wo das Limit liegt:

var config = {};

// Largest file the editor will insert or upload, in bytes. 0 (the default) = no limit.
config.maxUploadFileSize = 5 * 1024 * 1024;   // 5 MB

// Optional: replace the default alert with your own dialog.
// The handler receives (state, name, message); set state.returnValue = true to say
// you have handled it, and the editor shows nothing itself.
config.oncustomdialog = function (state, name, message) {
  if (name !== "uploadtoobig") return;   // leave the others to the editor
  showMyDialog(message);
  state.returnValue = true;
};

Das gilt für jeden Weg, auf dem eine Datei ankommt: den Bilddialog, den Dokumentdialog, Einfügen aus der Zwischenablage und Drag & Drop. Bilder, die bereits in eingefügtem HTML eingebettet sind (etwa aus Word), sind nicht erfasst, weil die Ablehnung eines Bildes mitten im Einfügen das Dokument halb umgewandelt zurückließe; dafür ist maxHTMLLength unten die Absicherung.

Behalten Sie die Prüfung auch in Ihrem Handler bei, denn ein Browser ist kein vertrauenswürdiger Ort, um ein Limit durchzusetzen. Der Beispiel-Handler lehnt alles über 4,000,000 Bytes ab und antwortet mit ERROR:file too big.

Außerdem müssen zwei Limits auf dem Server über Ihrer größten Datei liegen:

  • ASP.NET: maxRequestLength in <httpRuntime> (Kilobytes, Standard 4096) und maxAllowedContentLength in <requestLimits> (Bytes, Standard etwa 30 MB).
  • PHP: upload_max_filesize (Standard 2 MB) und post_max_size. Liegen sie darunter, verwirft PHP die Datei, bevor Ihr Handler läuft, und der Editor meldet, dass keine Datei empfangen wurde.

Um das Dokument selbst zu begrenzen, setzen Sie ein Zeichenlimit. Beide Optionen sind standardmäßig aus (0), und überschreitet eine Änderung das Limit, kehrt der Editor zum letzten gültigen Zustand zurück und zeigt eine Meldung an, statt etwas zu speichern, das Ihre Datenbank ablehnen wird:

var config = {};
config.maxHTMLLength = 60000;  // characters INCLUDING the HTML tags; 0 = no limit
config.maxTextLength = 0;      // characters EXCLUDING the tags; 0 = no limit

Setzen Sie maxHTMLLength etwas unter die Größe der Spalte, in der Sie das HTML speichern. Behandeln Sie den Hook reachmaxlength, um den Standardhinweis durch einen eigenen Dialog zu ersetzen.

Fotos vor dem Hochladen verkleinern

Eine Smartphone-Kamera erzeugt Dateien, die weit größer sind, als irgendeine Webseite braucht, und sie mit maxUploadFileSize abzulehnen, sagt dem Autor nur, er solle sich ein anderes Bild suchen. Der Editor hat keine eigene Einstellung zum Verkleinern; das gehört in den Handler, weil nur Ihre Anwendung weiß, wie groß die gespeicherte Kopie sein soll. Skalieren Sie das Bild auf einem Canvas und laden Sie das Ergebnis statt des Originals hoch:

config.file_upload_handler = function (file, callback, optionalIndex, optionalFiles) {
    var MAXWIDTH = 1600, MAXHEIGHT = 1600, QUALITY = 0.82;

    function send(blob, name) {
        // your own upload code: post "blob" under the name "name",
        // then callback(theUrl) or callback(null, theError).
    }

    // Leave anything that is not a still photograph alone. GIFs would lose
    // their animation, and a PDF is not something a canvas can redraw.
    if (file.type.indexOf("image/") !== 0 || file.type === "image/gif") {
        send(file, file.name);
        return;
    }

    var url = URL.createObjectURL(file);
    var img = new Image();
    img.onload = function () {
        var scale = Math.min(1, MAXWIDTH / img.width, MAXHEIGHT / img.height);
        if (scale === 1) { URL.revokeObjectURL(url); send(file, file.name); return; }  // already small enough

        var canvas = document.createElement("canvas");
        canvas.width = Math.round(img.width * scale);
        canvas.height = Math.round(img.height * scale);
        canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
        URL.revokeObjectURL(url);

        canvas.toBlob(function (blob) {
            if (!blob) { send(file, file.name); return; }   // the browser refused: send the original
            send(blob, file.name.replace(/\.[^.]+$/, "") + ".jpg");
        }, "image/jpeg", QUALITY);
    };
    img.onerror = function () { URL.revokeObjectURL(url); callback(null, "not-an-image"); };
    img.src = url;
};

Gemessen an einem Foto mit 2400 × 1600: Das Original mit 3.9 MB wird als JPEG mit 1600 × 1067 und 94 KB hochgeladen, und das Dokument enthält die URL, die Ihr Server zurückgegeben hat.

Zwei Dinge sollten Sie beachten. Die Umwandlung in JPEG verwirft die Transparenz, unterscheiden Sie also anhand von file.type, wenn Ihre Benutzer neben Fotos auch Logos einfügen. Und das Neukodieren entfernt die Metadaten der Datei – meist willkommen, weil dort die GPS-Koordinaten eines Fotos stehen, aber nicht, wenn Sie darauf angewiesen sind.

Wenn ein Upload fehlschlägt

Rufen Sie den Callback mit null und einer Meldung auf: callback(null, "http-error-413"). Der Editor zeigt The file could not be uploaded., gefolgt von Ihrer Meldung in Klammern, und vorher wird der Hook uploadfailed ausgelöst, sodass Sie stattdessen Ihren eigenen Dialog zeigen können. Dieser Hook erhält Ihre Meldung genau so, wie Sie sie übergeben haben – den rohen Code, nicht den Satz drumherum –, sodass Sie danach verzweigen können.

Wenn ein konfigurierter Upload-Handler einen Fehler meldet, entfernt der aktuell ausgelieferte Kern das Bild, statt seine Base64-Vorschau beizubehalten. Setzen Sie maxUploadFileSize auf das Limit, das Ihr Endpoint durchsetzt, damit die Datei abgelehnt wird, bevor sie gelesen wird.

JavaScript-Vorlage

Dieses Client-Skript, abgeleitet vom funktionierenden Handler aus dem Download, zeigt einen Fortschrittsdialog mit Abbrechen-Schaltfläche, sendet die Datei per POST und ruft den Callback mit der URL oder dem Fehler auf. Es schließt genau einmal ab, bricht nach 60 Sekunden mit einem Timeout ab, lässt sich per Tastatur abbrechen und stellt den Fokus wieder her. Der seitenweite Zähler rte_upload_state gehört zu diesem Beispiel, nicht zur Editor-API; trennen Sie ihn in einer Anwendung mit mehreren Editoren pro Editor.

var uploadhandlerpath = "/imageupload.ashx";

function rte_file_upload_handler(file, callback, optionalIndex, optionalFiles) {
	var originalCallback = callback;
	var finished = false;
	rte_upload_state.pending++;
	callback = function (url, error) {
		if (finished) return;
		finished = true;
		if (error) rte_upload_state.error = String(error);
		try { originalCallback(url, error); }
		finally { rte_upload_state.pending--; }
	};
	try {

	function append(parent, tagname, csstext) {
		var tag = parent.ownerDocument.createElement(tagname);
		if (csstext) tag.style.cssText = csstext;
		parent.appendChild(tag);
		return tag;
	}

	var uploadcancelled = false;

	var dialogouter = append(document.body, "div", "display:flex;align-items:center;justify-content:center;z-index:2147483646;position:fixed;inset:0;padding:16px;box-sizing:border-box;background-color:rgba(15,23,42,0.28)");
	var dialoginner = append(dialogouter, "div", "box-sizing:border-box;min-width:260px;max-width:calc(100vw - 32px);padding:16px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;box-shadow:0 16px 40px rgba(15,23,42,.2)");

	var line1 = append(dialoginner, "div", "margin:0 0 10px;color:#172033;font:700 15px/1.3 Aptos,Segoe UI,sans-serif;");
	var previousFocus = document.activeElement;
	window.__rteDemoUploadDialogId = (window.__rteDemoUploadDialogId || 0) + 1;
	line1.id = "rte-upload-label-" + window.__rteDemoUploadDialogId;
	dialogouter.setAttribute("role", "dialog");
	dialogouter.setAttribute("aria-modal", "true");
	dialogouter.setAttribute("aria-labelledby", line1.id);
	line1.innerText = "Uploading...";

	var totalsize = file.size;
	var sentsize = 0;

	if (optionalFiles && optionalFiles.length > 1) {
		totalsize = 0;
		for (var i = 0; i < optionalFiles.length; i++) {
			totalsize += optionalFiles[i].size;
			if (i < optionalIndex) sentsize = totalsize;
		}
		line1.innerText = "Uploading..." + (optionalIndex + 1) + "/" + optionalFiles.length;
	}

	var line2 = append(dialoginner, "div", "margin:0 0 8px;color:#52657e;font:600 12px/1.3 Aptos,Segoe UI,sans-serif;");
	line2.innerText = "0%";

	var progressbar = append(dialoginner, "div", "height:8px;overflow:hidden;border-radius:4px;background:#e7eef7;");
	progressbar.setAttribute("role", "progressbar");
	progressbar.setAttribute("aria-label", "File upload progress");
	progressbar.setAttribute("aria-valuemin", "0");
	progressbar.setAttribute("aria-valuemax", "100");
	progressbar.setAttribute("aria-valuenow", "0");
	var progressbg = append(progressbar, "div", "width:0;height:100%;border-radius:inherit;background:#2487e8;transition:width 120ms ease;");

	var line3 = append(dialoginner, "div", "display:flex;justify-content:flex-end;margin-top:14px;");
	var btn = append(line3, "button");
	btn.style.cssText = "min-height:32px;padding:7px 11px;border:1px solid #bdd0e6;border-radius:7px;background:#fff;color:#315277;font:700 12px/1 Aptos,Segoe UI,sans-serif;cursor:pointer;";
	btn.type = "button";
	btn.setAttribute("aria-label", "Cancel file upload");
	dialoginner.addEventListener("keydown", function (event) {
		if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); btn.click(); }
		else if (event.key === "Tab" && !event.ctrlKey && !event.metaKey && !event.altKey) { event.preventDefault(); btn.focus(); }
	});
	btn.focus();
	btn.innerText = "Cancel";
	btn.onclick = function () {
		uploadcancelled = true;
		xh.abort();
	}

	var xh = new XMLHttpRequest();
	xh.timeout = 60000;
	xh.open("POST", uploadhandlerpath + "?name=" + encodeURIComponent(file.name || "edited-image.png") + "&type=" + encodeURIComponent(file.type || "image/png") + "&size=" + file.size, true);
	xh.onload = xh.onabort = xh.onerror = xh.ontimeout = function (pe) {
		if (finished) return;
		var restoreFocus = dialoginner.contains(document.activeElement);
		dialogouter.remove();
		try { if (restoreFocus && previousFocus && previousFocus.isConnected && typeof previousFocus.focus === "function") previousFocus.focus(); } catch (error) { /* Never prevent upload settlement. */ }
		if (pe.type == "load") {
			if (xh.status != 200) {
				if (xh.responseText.startsWith("ERROR:")) {
					callback(null, "http-error-" + xh.responseText.substring(6));
				}
				else {
					callback(null, "http-error-" + xh.status);
				}
			}
			else if (xh.responseText.startsWith("READY:")) {
				var resolved;
				try {
					var value = xh.responseText.substring(6).trim();
					if (!value) throw new Error('Missing URL');
					resolved = new URL(value, new URL(uploadhandlerpath, document.baseURI));
					if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') throw new Error('Invalid URL');
				} catch (error) { callback(null, 'Server returned an invalid image URL'); return; }
				callback(resolved.href);
			}
			else {
				callback(null, "http-error-" + xh.responseText);
			}
		}
		else if (uploadcancelled) {
			callback(null, "cancelled");
		}
		else {
			callback(null, pe.type);
		}
	}
	xh.upload.onprogress = function (pe) {
		if (finished) return;
		//pe.total
		var percent = totalsize > 0 ? Math.max(0, Math.min(100, Math.floor(100 * (sentsize + pe.loaded) / totalsize))) : 0;
		line2.innerText = percent + "%";
		progressbar.setAttribute("aria-valuenow", String(percent));

		progressbg.style.width = percent + "%";
	}
	xh.send(file);
	return function () { if (!finished) { uploadcancelled = true; xh.abort(); } };
	} catch (error) {
		if (finished) throw error;
		var restoreFailedFocus = dialoginner && dialoginner.contains(document.activeElement);
		if (dialogouter && dialogouter.parentNode) dialogouter.parentNode.removeChild(dialogouter);
		try { if (restoreFailedFocus && previousFocus && previousFocus.isConnected && typeof previousFocus.focus === "function") previousFocus.focus(); } catch (focusError) { /* Preserve upload error. */ }
		callback(null, 'Could not start upload');
	}
}

// Single-page demo state, not an editor API.
var rte_upload_state = { pending: 0, error: '' };

var config = {
    file_upload_handler: rte_file_upload_handler,
    maxUploadFileSize: 4000000, // Match your server byte limit.
    allowBase64Images: false
};
var editor = new RichTextEditor("#div_editor", config);

Wie ein hochgeladenes Bild ausgerichtet wird

Der Editor wendet keine eigene Ausrichtung an. Ein eingefügtes Bild ist ein Inline-<img> ohne Attribut align und ohne Style, es steht also im Text und folgt der Ausrichtung des Absatzes, der es enthält – links, sofern dieser Absatz nicht zentriert oder rechtsbündig ist:

<!-- what the editor inserts: no align attribute, no style -->
<p>Some text <img src="/imageuploads/20260918-a1b2c3.png" loading="lazy" decoding="async"></p>

<!-- the same image, centred only because its paragraph is centred -->
<p style="text-align:center"><img src="/imageuploads/20260918-a1b2c3.png" loading="lazy" decoding="async"></p>

Es gibt daher keine Einstellung „Standardausrichtung von Bildern“, die man ändern könnte. Wirken Bilder überall zentriert, liegt das an einer Regel in Ihrem eigenen Stylesheet oder an zentrierten Absätzen. Prüfen Sie das gespeicherte HTML: Trägt das <img> keine Ausrichtung, liegt es an Ihrem CSS.

Benutzer legen die Ausrichtung pro Bild fest, indem sie es auswählen und das Ausrichtungsmenü in der Bild-Symbolleiste verwenden. Um das Verhalten aller Bilder zu ändern, schreiben Sie die Regel in Ihr eigenes CSS und geben dem Editor dieselbe Regel, damit die Bearbeitungsansicht der veröffentlichten Seite entspricht:

var config = {};

// Applies inside the editing area. Use the same rule on the pages that display
// the saved content, or the two will not look alike.
config.contentCssText = "p { text-align: left; } p img { float: none; }";

// Or load one stylesheet in both places:
config.contentCssUrl = "/css/editor-content.css";

Weiterführende Literatur