リッチテキストの単語数・文字数:絵文字、CJK、送信上限

単語数、文字数、送信上限を表示する RichTextEditor

キー入力のたびに文書を置き換えずに、アプリケーション独自のカウンターを RichTextEditor に追加します。検証済みモジュールと完全なブラウザーサンプルで、200文字の上限を確認し、超過した下書きもそのまま残します。

文字の数え方を決める

JavaScript の文字列長は UTF-16 コード単位の数です。家族の絵文字や結合アクセント付き文字は、見た目が1文字でも複数の単位を含むことがあります。この例では Intl.Segmenter で書記素クラスタを数え、空白と改行を含め、CRLF と CR を LF にそろえます。

単語数は、指定した文書ロケールで単語として識別される区間の数です。Microsoft Word、エディター標準のステータス表示、SMS の課金、SNS の規則との一致は保証しません。方針を分け、上限の単位を利用者に説明してください。

カウント用モジュールを取得する

テキスト集計モジュールを取得します。入力は HTML ではなくプレーンテキストです。getHTMLCode() の HTML を数える代わりに、getPlainText() で文書を取得してください。タグは単語ではなく、段落の区切りや抽出方針によるマーカーも意図的にテストする必要があります。

このモジュールにはブラウザーまたはサーバーの分節処理への対応が必要です。対応がなければエラーにし、中国語や日本語に向かない空白区切りへ黙って切り替えません。

カウンターと送信時の確認を組み込む

エディターのアセットとモジュールをアプリケーションから配信します。サンプルは送信条件を確認するだけで、内容を送信・保存しません。入力中は集計をまとめて遅延実行し、フォーム送信時にはすぐに再計算します。送信を拒否しても下書きは変更しません。

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

サーバー側でも方針をそろえる

サーバー側でも、送信された文書と合意したテキスト抽出方針から再検証します。クライアントの計数値や別のテキストフィールドを信用しないでください。権限、HTML のサニタイズ、要求のバイト数は別に検証します。200書記素でも HTML 本体が小さいとは限りません。

新しい Node.js アプリケーションでも、プレーンテキストを抽出した後でモジュールを再利用できます。正規化とロケールをそろえ、共通のテストデータを使ってください。ブラウザーとサーバーのバージョンで Unicode や分節データが異なる場合があります。上限が契約に関わるなら、サーバー側の権威ある方針を選び、バージョンを固定します。

言語と入力方法をテストする

  1. 空のテキスト、空白、段落の改行、CRLF を確認します。
  2. 家族の絵文字、国旗、結合アクセントを試します。上限に合わせるために途中で分割・切り詰めないでください。
  3. 中国語と日本語の実文書を使い、単語が空白で区切られると仮定しないでください。
  4. 上限を超える内容を貼り付け、表示が更新される前に送信します。拒否されても下書きが残ることを確認します。
  5. 十分に削除して再送信し、確認を通過することを検証します。自分の実装では IME の変換中入力と元に戻す操作も試してください。

このカウンターは必須入力の規則や、画像だけの文書が有効かどうかを判定しません。エディターの HTML を毎回書き換えたり、すべてのキー入力を取り消したりすると、選択範囲、履歴、IME を壊す恐れがあります。

テキスト抽出、フォーム検証、サーバー自動保存、単語数の設計方針を参照してください。

次に読む