← Toda a documentação

Upload de arquivos

Com problemas? Siga o guia de solução de problemas de upload de imagens para requisições que não chegam, erros 413 e imagens perdidas depois de salvar.

Você escreve o handler de upload. Ele recebe o arquivo que o usuário inseriu, colou ou soltou, envia-o para o seu servidor e devolve ao editor a URL que deve ir para o documento. Os exemplos no download incluem um handler funcional para ASP.NET Web Forms, MVC, ASP.NET Core e PHP, além do script de cliente abaixo.

Configure um handler, ou os arquivos serão incorporados ao conteúdo

Sem um handler configurado, nada é enviado, e o arquivo ainda precisa ir para algum lugar, então ele vai para o documento:

  • Uma imagem é incorporada como uma URL data: em base64. A imagem passa a fazer parte do HTML que você salva, e o base64 acrescenta cerca de um terço: uma foto de 3 MB vira cerca de 4 MB de texto em um único campo. Essa é a causa habitual de um salvamento que falha, é truncado ou excede o tempo limite ao gravar no banco de dados.
  • Um documento é inserido como um link com o nome do arquivo, mas sem um endereço que funcione: o editor aponta o link para uma URL blob: temporária, e o sanitizador de conteúdo a remove, porque blob: não é um dos esquemas que ele permite. O leitor recebe um link que não abre. Defina fileUploadLocalObjectUrl: false para recusar a inserção e avisar o usuário.

Com um handler, o conteúdo guarda uma URL curta como /imageuploads/20260918-a1b2c3.png. Para verificar um documento existente, olhe o HTML que você armazenou: src="data:image/... significa que o handler não está sendo usado.

O botão Upload da caixa de diálogo Insert Gallery procura apenas a variável global window.rte_file_upload_handler. Se você definir só a opção file_upload_handler por editor, a galeria não mostra o botão Upload.

O jeito curto: passe uma URL para o editor

Se o seu servidor já aceita um arquivo e responde com a URL dele, você não precisa escrever nenhum JavaScript:

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);

O editor envia o arquivo por POST como multipart/form-data e lê a URL da resposta. Ele entende os formatos de resposta que os endpoints dos outros editores já usam, então um servidor escrito para o TinyMCE ou o CKEditor funciona aqui sem alterações:

Corpo da respostaEscrito para
{"location": "/img/x.png"}TinyMCE
{"url": "/img/x.png"}Adaptador de upload simples do CKEditor
{"urls": {"default": "..."}}CKEditor, vários tamanhos
READY:/img/x.pngos handlers de exemplo abaixo
{"error": {"message": "..."}} ou ERROR:messagequalquer um, informado como falha

Uma resposta que não esteja em nenhum desses formatos é tratada como upload com falha, em vez de ser inserida, então um endpoint que responde OK não tem como virar a origem de uma imagem.

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 };

Por padrão, cada arquivo enviado recebe um nome único, porque duas pessoas enviando image.png para o mesmo endpoint não podem sobrescrever o arquivo uma da outra. Novas tentativas, cancelamento e o evento de progresso funcionam igual a quando você usa o seu próprio handler. Escreva um handler quando precisar redimensionar antes de enviar, enviar diretamente para o S3 ou mostrar sua própria caixa de diálogo — ele tem prioridade sempre que os dois estiverem definidos.

Sintaxe

// 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 — o objeto File do navegador, com name, size e type. Envie-o com XMLHttpRequest ou fetch.

callback

Type: Function callback(url, errorMessage) — chame-o exatamente uma vez. Em caso de sucesso, passe a URL armazenada; em caso de falha, passe null e uma mensagem, que o editor mostra ao usuário. Um upload cancelado pelo usuário também é uma falha.

optionalIndex, optionalFiles

Quando vários arquivos são inseridos de uma vez, o handler é chamado para cada um: optionalFiles é o conjunto inteiro e optionalIndex é a posição deste arquivo, para que o progresso possa ser informado como “2 de 5”.

Os handlers de exemplo respondem em texto simples: READY:<url> com status 200 ou ERROR:<message> com status 500. O script de cliente abaixo converte essas respostas nas duas formas de callback.

Cancelando um upload

Retorne do handler um objeto com um método abort() (ou apenas uma função de abort), e o editor poderá interromper uma requisição que ainda está em andamento:

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() e destroy() abortam as requisições em andamento quando o handler fornece uma função de abort. Se a imagem de destino tiver sido removida, um resultado que chega atrasado é descartado; remover uma imagem, por si só, não garante que a requisição de rede seja abortada imediatamente. O callback atrasado de um upload cancelado é ignorado, então uma resposta lenta não tem como trazer de volta uma imagem excluída. Um evento uploadcancel é disparado com o arquivo e o motivo. Não retornar nada continua funcionando: o upload só não pode ser abortado.

Imagens já incorporadas ao conteúdo

O handler é chamado para os arquivos que o usuário insere, cola ou solta. Conteúdo que chega de outra forma — definido com setHTMLCode, colado de uma página web ou de outro editor, ou restaurado de um rascunho — mantém sua URL data: em base64, então essas imagens são salvas dentro do HTML e o seu handler nunca as vê. Chame uploadEmbeddedImages antes de salvar:

Em um formulário nativo, use os IDs abaixo e substitua a action pelo endpoint que salva o seu documento. Configure o handler de upload antes de criar o editor. Este exemplo aguarda os uploads, recusa imagens locais não resolvidas e preserva a validação nativa e o botão de envio.

<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.";
    }
});

Ele percorre o documento atual, converte cada origem data:image/ em um File, envia uma por vez pelo handler e substitui cada origem pela URL que você retorna. Imagens que já estão em http(s) não são alteradas. Sem um handler configurado, nada muda e todas as imagens são contadas como ignoradas.

Uma contagem de pendentes igual a zero não prova que deu certo: verifique as imagens que faltam depois de um upload com falha ou cancelado. Não chame form.submit() diretamente; isso ignora a validação nativa e os listeners de submit. Enviar uma imagem é algo separado de salvar o documento; o seu servidor precisa autenticar, autorizar e sanitizar o HTML recebido antes de armazená-lo ou renderizá-lo.

Upload automático e nova tentativa após falha

automaticUploads agenda a varredura acima para você. Isso não garante que o salvamento será concluído: mantenha as verificações de pendências e de erros e inspecione as imagens incorporadas não resolvidas antes de capturar o conteúdo para salvar:

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

Ela é executada depois de setHTMLCode e depois de HTML inserido ou colado, com debounce, e não faz nada quando não há handler configurado. uploadRetryCount cobre a outra falha comum: uma única conexão perdida deixava a imagem incorporada para sempre. A nova tentativa é silenciosa quando dá certo, cada tentativa dispara um evento uploadretry e, quando as tentativas se esgotam, o erro do seu callback é informado como antes.

Limitando o tamanho dos uploads e do conteúdo

Defina maxUploadFileSize e o próprio editor recusa um arquivo maior, antes de lê-lo. Nada é incorporado e nada é enviado, e o usuário é informado de qual tamanho foi rejeitado e qual é o limite:

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;
};

Isso cobre todos os caminhos pelos quais um arquivo chega: a caixa de diálogo de imagem, a caixa de diálogo de documento, colar e arrastar e soltar. Imagens já incorporadas no HTML colado (do Word, por exemplo) não são cobertas, porque recusar uma no meio da colagem deixaria o documento convertido pela metade; maxHTMLLength, abaixo, é a proteção para esse caso.

Mantenha a verificação também no seu handler, já que o navegador não é um lugar confiável para impor um limite. O handler de exemplo rejeita qualquer coisa acima de 4,000,000 bytes e responde ERROR:file too big.

Dois limites do servidor também precisam ficar acima do seu maior arquivo:

  • ASP.NET: maxRequestLength em <httpRuntime> (em kilobytes, padrão 4096) e maxAllowedContentLength em <requestLimits> (em bytes, padrão de cerca de 30 MB).
  • PHP: upload_max_filesize (padrão 2 MB) e post_max_size. Acima desses valores, o PHP descarta o arquivo antes de o seu handler ser executado, e o editor informa que nenhum arquivo foi recebido.

Para limitar o próprio documento, defina um limite de caracteres. As duas opções vêm desativadas por padrão (0) e, quando uma edição ultrapassa o limite, o editor volta ao último estado válido e mostra uma mensagem, em vez de salvar algo que o seu banco de dados vai rejeitar:

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

Defina maxHTMLLength um pouco abaixo do tamanho da coluna em que você armazena o HTML. Trate o hook reachmaxlength para substituir o alerta padrão pela sua própria caixa de diálogo.

Reduzindo fotos antes do upload

A câmera de um celular gera arquivos muito maiores do que qualquer página web precisa, e recusá-los com maxUploadFileSize só faz o autor ir procurar outra imagem. O editor não tem uma configuração de redimensionamento própria; o lugar disso é o handler, porque só o seu aplicativo sabe qual deve ser o tamanho da cópia armazenada. Redimensione a imagem em um canvas e envie o resultado em vez do original:

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;
};

Medido em uma foto de 2400 × 1600: o original de 3.9 MB é enviado como um JPEG de 1600 × 1067 com 94 KB, e o documento guarda a URL que o seu servidor retornou.

Duas coisas para ter em mente. A conversão para JPEG descarta a transparência, então crie um desvio com base em file.type se os seus usuários colam logotipos além de fotos. E a recodificação remove os metadados do arquivo — o que costuma ser bem-vindo, já que é ali que ficam as coordenadas de GPS de uma foto, mas não se você depende deles.

Quando um upload falha

Chame o callback com null e uma mensagem: callback(null, "http-error-413"). O editor mostra The file could not be uploaded. (“não foi possível enviar o arquivo”) seguido da sua mensagem entre colchetes, e o hook uploadfailed é disparado antes, para que você possa mostrar sua própria caixa de diálogo. Esse hook recebe a sua mensagem exatamente como você a passou — o código bruto, não a frase em volta dele —, então você pode criar desvios com base nela.

Quando um handler de upload configurado informa uma falha, o core distribuído atualmente remove a imagem em vez de manter sua pré-visualização em base64. Defina maxUploadFileSize com o limite que o seu endpoint impõe, para que o arquivo seja recusado antes de ser lido.

Código JavaScript de modelo

Adaptado do handler funcional do download, este script de cliente mostra uma caixa de diálogo de progresso com um botão Cancel, envia o arquivo por POST e chama o callback com a URL ou com o erro. Ele é concluído uma única vez, expira depois de 60 segundos, permite cancelar pelo teclado e restaura o foco. O contador rte_upload_state de página única pertence a este exemplo, e não à API do editor; isole-o por editor em um aplicativo com vários editores.

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);

Como uma imagem enviada é alinhada

O editor não aplica nenhum alinhamento próprio. Uma imagem inserida é um <img> inline sem atributo align e sem estilo, então ela fica no texto e segue o alinhamento do parágrafo que a contém — à esquerda, a menos que esse parágrafo esteja centralizado ou alinhado à direita:

<!-- 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>

Por isso, não existe uma configuração de “alinhamento padrão de imagem” para mudar. Se as imagens aparecem centralizadas em todo lugar, a causa é uma regra na sua própria folha de estilo ou parágrafos centralizados. Verifique o HTML que você armazenou: se o <img> não tem alinhamento, a causa é o seu CSS.

Os usuários definem o alinhamento de cada imagem selecionando-a e usando o menu de alinhamento na barra de ferramentas de imagem. Para mudar o comportamento de todas as imagens, escreva a regra no seu próprio CSS e passe a mesma regra para o editor, para que a visualização de edição corresponda à página publicada:

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";

Leitura complementar