Contagem de palavras e caracteres: emoji, CJK e limites de envio

RichTextEditor com contagem de palavras, caracteres e limite de envio

Adicione um contador próprio da aplicação sem substituir o documento a cada tecla. Este guia inclui um módulo testado e um exemplo completo que valida um limite de 200 caracteres, mantendo intactos os rascunhos que ultrapassarem o limite.

Defina o que significa um caractere

O comprimento de uma string JavaScript conta unidades UTF-16. Um emoji de família ou uma letra com acento combinado pode conter várias unidades e parecer um caractere. Este exemplo conta grafemas com Intl.Segmenter, inclui espaços e quebras de linha e normaliza CRLF ou CR para LF.

Palavras são os segmentos identificados como palavras para o idioma explícito do documento. Isso não promete igualdade com Microsoft Word, a estatística interna do editor, cobrança de SMS ou regras de redes sociais. Separe essas políticas e explique aos autores qual unidade seu limite usa.

Baixe o módulo de contagem

Baixe o módulo de métricas de texto. Ele recebe texto simples, não HTML. Leia o documento com getPlainText() em vez de contar o HTML de getHTMLCode(). Tags não são palavras; limites de parágrafo e marcadores gerados pela extração precisam de testes explícitos.

O módulo exige suporte à segmentação no navegador ou servidor. Se não houver suporte, informa um erro em vez de mudar silenciosamente para divisão por espaços, inadequada para chinês e japonês.

Conecte o contador à verificação de envio

Disponibilize os arquivos do editor e o módulo pela sua aplicação. O exemplo apenas verifica as condições do formulário; não envia nem salva conteúdo. A contagem é agrupada durante a digitação e refeita imediatamente no envio. Uma rejeição mantém o rascunho intacto.

<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<style>body{font:16px/1.6 system-ui,sans-serif;max-width:960px;margin:24px auto;padding:0 16px}button{min-height:44px;padding:8px 16px}</style>
<h1>Word and character counter</h1>
<form id="message-form">
  <div style="max-width:100%;overflow-x:auto"><div id="editor"></div></div>
  <p id="counter"></p>
  <p>Limit: 200 graphemes, including spaces and line breaks. This demo does not save.</p>
  <button type="submit">Check submission</button>
  <p id="result" role="status" aria-live="polite"></p>
</form>
<script type="module">
import { countText } from "/js/text-metrics.mjs";
const editor = new RichTextEditor("#editor", { toolbar: "full", height: "320px", showStatistics: false });
editor.setHTMLCode("<p>Write a short note.</p>");
const LOCALE = "en"; // Choose the document language, not necessarily the UI language.
const LIMIT = 200;
const counter = document.getElementById("counter");
const result = document.getElementById("result");
function updateCount() {
  try {
    const metrics = countText(editor.getPlainText(), LOCALE);
    counter.textContent = metrics.words + " words · " + metrics.characters +
      " / " + LIMIT + " characters (graphemes)";
    return metrics;
  } catch {
    counter.textContent = "Counter unavailable. Check locale and Intl.Segmenter support.";
    return null;
  }
}
let timer, checkedText = null;
editor.attachEvent("change", () => {
  if (editor.getPlainText() !== checkedText) result.textContent = "";
  clearTimeout(timer);
  timer = setTimeout(updateCount, 200);
});
document.getElementById("message-form").addEventListener("submit", event => {
  event.preventDefault();
  clearTimeout(timer);
  const metrics = updateCount(); // Do not trust a delayed displayed count.
  checkedText = editor.getPlainText();
  if (!metrics) { result.textContent = "Cannot validate this count."; return; }
  if (metrics.characters > LIMIT) {
    result.textContent = "Too long. Shorten the draft; your content is still here.";
    editor.focus();
    return;
  }
  result.textContent = "Within limit. Ready for your server-side validation.";
  // Add your authenticated save here. This demo sends no request.
});
updateCount();
// Exposed only so you can inspect this standalone demo in developer tools:
window.counterDemo = { editor, updateCount };
</script>

Aplique a mesma política no servidor

Repita a validação no servidor usando o documento recebido e uma política de extração acordada. Não confie na contagem enviada pelo cliente nem em outro campo de texto simples. Verifique separadamente autorização, sanitização HTML e tamanho da requisição em bytes: 200 grafemas não garantem um HTML pequeno.

Você pode reutilizar o módulo em uma aplicação Node.js moderna após extrair texto simples. Alinhe normalização e idioma e use testes compartilhados: dados Unicode e de segmentação podem variar entre versões do navegador e servidor. Para limites contratuais, escolha e versione uma política oficial do servidor.

Teste idiomas e métodos de entrada

  1. Verifique texto vazio, espaços, quebras de parágrafo e CRLF.
  2. Experimente um emoji de família, uma bandeira e um acento combinado. Não os corte para respeitar o limite.
  3. Use documentos chineses e japoneses reais, sem presumir que espaços separam palavras.
  4. Cole conteúdo acima do limite e envie imediatamente, antes de o contador atualizar. A verificação deve rejeitar e preservar o rascunho.
  5. Apague texto suficiente e envie novamente. Teste também composição IME e desfazer na sua integração.

O contador não aplica regras de conteúdo obrigatório nem decide se um documento só com imagens é válido. Reescrever o HTML ou cancelar cada tecla pode prejudicar seleção, histórico e entrada IME.

Veja extração de texto simples, validação de formulários, salvamento automático no servidor e políticas de contagem de palavras.

Leia a seguir