Contar palabras y caracteres: emoji, CJK y límites de envío

RichTextEditor con recuento de palabras, caracteres y límite de envío

Añade un contador específico de tu aplicación sin sustituir el documento con cada pulsación. Esta guía incluye un módulo probado y un ejemplo completo que valida un límite de 200 caracteres y mantiene intactos los borradores que lo superan.

Definir qué significa un carácter

La longitud de una cadena JavaScript cuenta unidades UTF-16. Un emoji familiar o una letra con acento combinado puede contener varias unidades aunque parezca un carácter. Este ejemplo cuenta grafemas con Intl.Segmenter, incluye espacios y saltos de línea y normaliza CRLF o CR a LF.

Las palabras son los segmentos reconocidos como tales para el idioma explícito del documento. No se promete coincidir con Microsoft Word, el contador integrado del editor, la facturación SMS o las reglas de una red social. Separa esas políticas y explica a los autores qué unidad utiliza el límite.

Descargar el módulo de recuento

Descarga el módulo de métricas de texto. Acepta texto sin formato, no HTML. Obtén el documento con getPlainText() en lugar de contar el HTML de getHTMLCode(). Las etiquetas no son palabras; prueba los límites de párrafo y los marcadores que produzca tu política de extracción.

El módulo necesita segmentación en el navegador o servidor. Si no está disponible, muestra un error en vez de pasar silenciosamente a separar por espacios, algo inadecuado para chino y japonés.

Conectar el contador y la comprobación de envío

Sirve los archivos del editor y el módulo desde tu aplicación. El ejemplo solo comprueba las condiciones del formulario; no envía ni guarda contenido. El recuento se agrupa durante la escritura y se recalcula inmediatamente al enviar. Un rechazo deja intacto el borrador.

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

Aplicar la misma política en el servidor

Repite la validación en el servidor usando el documento enviado y una política de extracción acordada. No confíes en un número proporcionado por el cliente ni en otro campo de texto sin formato. Comprueba por separado permisos, saneamiento HTML y tamaño en bytes: 200 grafemas no implican una carga HTML pequeña.

Puedes reutilizar el módulo en una aplicación Node.js moderna tras extraer el texto. Alinea normalización e idioma y ejecuta pruebas compartidas: los datos Unicode y de segmentación pueden variar entre versiones del navegador y del servidor. Si el límite tiene valor contractual, elige y versiona una política autorizada del servidor.

Probar idiomas y métodos de entrada

  1. Comprueba texto vacío, espacios, saltos de párrafo y CRLF.
  2. Prueba un emoji familiar, una bandera y un acento combinado. No los cortes para ajustar el límite.
  3. Usa documentos chinos y japoneses reales en vez de asumir que los espacios separan las palabras.
  4. Pega más del límite y envía inmediatamente, antes de que se actualice el contador. Debe rechazarse el envío y conservarse el borrador.
  5. Borra suficiente texto y vuelve a enviar. Prueba también composición IME y deshacer en tu integración.

El contador no aplica reglas de contenido obligatorio ni decide si un documento de solo imágenes es válido. Reescribir el HTML o cancelar cada pulsación puede dañar selección, historial y entrada IME.

Consulta la extracción de texto sin formato, la validación de formularios, el guardado automático en el servidor y las políticas de recuento.

Para seguir leyendo