Adicionar uma galeria de imagens ao RichTextEditor no React

A caixa de diálogo Insert Gallery do RichTextEditor funciona em um app React da mesma forma que em uma página comum. O que muda é a integração: o editor é um script de navegador que o React não gerencia, o componente precisa fazer a limpeza dele e, durante o desenvolvimento, as requisições da caixa de diálogo precisam chegar ao seu servidor de API passando pelo servidor de desenvolvimento. Esta página mostra uma configuração com Vite + React que faz as três coisas, testada com o exemplo em Express.
Carregar os scripts do editor antes do app
O editor é distribuído como scripts comuns que definem window.RichTextEditor. Em um app Vite, o lugar correto mais simples para eles é o index.html, antes do script de módulo que inicia o React:
<!-- Plain browser scripts from public/, loaded before the React bundle. -->
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<script src="/rte-files.js"></script>Os arquivos ficam em public/richtexteditor/, que o Vite serve sem alterações. O rte-files.js conecta os hooks de upload do editor a /rte/upload. Sem ele, a galeria consegue navegar, mas o botão Upload não tem o que chamar. Scripts clássicos no <head> são executados antes do bundle de módulos, então window.RichTextEditor já existe quando qualquer componente é montado.
O componente
// window.RichTextEditor comes from the scripts in index.html.
export default function RichTextEditor({ options, initialHtml = "", editorRef }) {
const container = useRef(null);
useEffect(() => {
const editor = new window.RichTextEditor(container.current, options);
editor.setHTMLCode(initialHtml);
if (editorRef) editorRef.current = editor;
// Runs on unmount, and once in development: Strict Mode mounts, unmounts, mounts again.
return () => {
if (editorRef) editorRef.current = null;
editor.destroy();
};
}, []); // create once; options are read at creation
return <div ref={container} />;
}O editor é criado uma única vez, no useEffect, na div para a qual a ref aponta. A limpeza chama destroy(). Isso importa no desenvolvimento, porque o Strict Mode do React monta cada componente, desmonta e monta de novo. Com a limpeza, o primeiro editor é destruído antes de o segundo ser criado. A nossa verificação no navegador, com <StrictMode> ativado, encontrou exatamente um editor na página, e o aviso da versão de avaliação do editor apareceu duas vezes no console: um por criação. Esquecer a limpeza é um erro que passa despercebido. Quando criamos um segundo editor na mesma div sem destroy(), a página continuou mostrando um editor, mas a primeira instância nunca foi limpa.
A lista de dependências vazia é proposital. As opções são lidas quando o editor é criado, então recriar o editor a cada renderização perderia o que o usuário digitou. Para ler o conteúdo, guarde a instância em uma ref e chame getHTMLCode():
const options = {
toolbar: "full",
galleryEndpoint: "/rte/gallery", // forwarded to Express by vite.config.js
maxUploadFileSize: 4000000,
};No exemplo, um botão Show HTML chama editor.current.getHTMLCode() e imprimiu <p>Hello from React.</p>. Depois que uma imagem é inserida pela galeria, o HTML contém <img src="/uploads/...">.
Acessar o servidor de API no desenvolvimento
galleryEndpoint: "/rte/gallery" é relativo à página, e a página vem do Vite. O proxy de desenvolvimento do Vite encaminha /rte/ (as rotas de galeria e upload) e /uploads/ (as imagens) para o exemplo em Express na porta 5101:
// /rte/ and /uploads/ go to the Express sample (node-express, port 5101).
// Keep the trailing slashes: "/rte" alone would also catch /rte-files.js.
proxy: {
"/rte/": "http://localhost:5101",
"/uploads/": "http://localhost:5101",
},O navegador vê uma única origem, então não há cabeçalhos CORS para definir nem URLs absolutas nas opções do editor. Mantenha as barras finais. O Vite compara uma chave simples como prefixo da URL, então uma chave "/rte" também corresponde a /rte-files.js. Nós testamos: com "/rte", /rte-files.js voltou com X-Powered-By: Express, servido pelo servidor de API em vez do app. Com "/rte/", o Vite serviu a própria cópia.
O proxy é um recurso de desenvolvimento. Em produção, sirva o app compilado e a API a partir de uma única origem, por exemplo com o Express servindo dist/ ao lado das rotas, ou atrás de um único proxy reverso.
O que a caixa de diálogo mostra
A imagem no topo é a caixa de diálogo neste app React, com as pastas e imagens do servidor Express. O lado do servidor é o mesmo do guia da galeria em Node.js. Duas limitações vêm da galeria na versão lançada, não do React. A dica de formatos lista JFIF, BMP e AVIF, que o servidor de exemplo recusa. E um arquivo recusado mostra "Upload failed for <name>." em vez da mensagem do servidor.
Teste
Baixe o projeto de exemplo deste guia e o exemplo em Express. Copie a pasta richtexteditor do download do editor para o exemplo em Express, ou defina RTE_DIR, e inicie-o com npm install e npm start. Depois, no projeto React:
npm install
npm run dev
VITE v8.3.5 ready in 440 ms
➜ Local: http://localhost:5111/
curl "http://localhost:5111/rte/gallery?folder="
{"currentFolder":"","currentFolderDisplay":"/","parentFolder":null,"folders":[...],"images":[...]}O npm run dev primeiro copia os arquivos do editor para public/. Defina RTE_DIR com a pasta do seu editor ou coloque a pasta em public/richtexteditor você mesmo. Abra http://localhost:5111, clique em Insert Gallery, escolha uma imagem e clique em Insert.