定型文を入力できる

画面

音声入力

プログラム

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="Shift_JIS">
<title>音声入力</title>
<script>
  function startSpeechRecognition() {
    var recognition = new webkitSpeechRecognition(); // Chrome用のプレフィックス
    var textBox = document.getElementById("myTextBox");

    recognition.lang = 'ja-JP'; // 言語を日本語に設定

    recognition.onresult = function(event) {
      var transcript = event.results[0][0].transcript;
      textBox.value += transcript; // 続けて入力するために += 演算子を使用
    }

    recognition.start();
  }
    
  function copyTextToClipboard() {
    var textBox = document.getElementById("myTextBox");
    textBox.select();
    document.execCommand("copy");
//      alert("テキストがクリップボードにコピーされました");
    }
    
  function clearTextBox() {
    var textBox = document.getElementById("myTextBox");
    textBox.value = "";
  }

  // ==================== 追加機能: 助詞・接続詞ボタン ====================
  // items を持つグループはそのまま表示、subs を持つグループは1行にまとめて表示する。
  var PARTICLE_GROUPS = [
    // 助詞はすべて1つのグループにまとめる。分類はコメント順に残し、重複は1つに統合。
    { name: "助詞", items: [
      // 格助詞
      "が", "の", "を", "に", "へ", "と", "より", "から", "にて", "して",
      // 接続助詞
      "ば", "とも", "ど", "ども", "て", "で", "つつ", "ながら",
      "ものの", "ものを", "ものから",
      // 副助詞
      "だに", "すら", "さへ", "し", "い", "のみ", "ばかり", "など", "まで",
      // 係助詞
      "は", "も", "ぞ", "なむ", "や", "か", "こそ",
      // 終助詞
      "な", "そ", "ばや", "しか", "てしか", "てもがな", "かな", "かし",
      // 間投助詞
      "よ"
    ]},
    // よく使う定型文。必要な語はこの行に足してください。
    { name: "定型文", items: [
      "お願い", "お願いします", "よろしく", "よろしくお願いします",
      "ありがとう", "ありがとうございます", "続けてください。", "してください。",
      "改行", "削除", "ご確認ください", "拝見しました"
    ]},
    { name: "接続詞", subs: [
      { name: "添加", items: [
        "そして", "また", "さらに", "しかも", "ほかに", "もしくは"
      ]},
      { name: "逆接", items: [
        "しかし", "でも", "けれど", "けれども", "ところが", "それでも", "ただし"
      ]},
      { name: "原因・理由", items: [
        "だから", "ので", "から", "ため", "ゆえに"
      ]},
      { name: "条件", items: [
        "もし", "なら"
      ]},
      { name: "目的", items: [
        "ように", "ために"
      ]},
      { name: "順序・結論", items: [
        "すると", "そこで", "したがって", "よって", "つまり"
      ]},
      { name: "例示", items: [
        "たとえば", "例えば"
      ]}
    ]},
    { name: "句読点", items: [
      "。", "、"
    ]},
    { name: "数字(半角)", items: [
      "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
    ]},
    { name: "数字(漢数字)", items: [
      "零", "一", "二", "三", "四", "五", "六", "七", "八", "九"
    ]}
  ];

  // カーソル位置に語句を挿入して、フォーカスをテキストエリアに戻す
  function insertParticle(particle) {
    var textBox = document.getElementById("myTextBox");
    var start = textBox.selectionStart;
    var end = textBox.selectionEnd;
    var value = textBox.value;

    textBox.value = value.substring(0, start) + particle + value.substring(end);

    var pos = start + particle.length;
    textBox.focus();
    textBox.setSelectionRange(pos, pos);
  }

  // 直前の1文字を削除する
  function deletePrevChar() {
    var textBox = document.getElementById("myTextBox");
    var start = textBox.selectionStart;
    var end = textBox.selectionEnd;
    var value = textBox.value;

    if (start !== end) {
      textBox.value = value.substring(0, start) + value.substring(end);
    } else if (start > 0) {
      textBox.value = value.substring(0, start - 1) + value.substring(start);
      start = start - 1;
    }

    textBox.focus();
    textBox.setSelectionRange(start, start);
  }

  // 改行をカーソル位置に挿入する
  function insertNewLine() {
    insertParticle("\n");
  }

  // 入力欄の文字列をカーソル位置に挿入する
  function insertByInput() {
    var field = document.getElementById("insertTarget");
    var text = field.value;
    if (!text) { field.focus(); return; }
    insertParticle(text);
    setFootMessage("「" + text + "」を入力しました");
  }

  // 指定した文字列をテキストから取り除く
  // カーソルより手前にある出現を最優先し、無ければ後ろから最初の出現を消す。
  function deleteByTarget() {
    var field = document.getElementById("deleteTarget");
    var target = field.value;
    if (!target) { field.focus(); return; }

    var textBox = document.getElementById("myTextBox");
    var value = textBox.value;
    var cursor = textBox.selectionStart;
    var idx = -1;
    var from = 0;

    while (true) {
      var found = value.indexOf(target, from);
      if (found < 0 || found + target.length > cursor) break;
      idx = found;
      from = found + 1;
    }

    if (idx < 0) {
      var ahead = value.indexOf(target, cursor);
      if (ahead >= 0) idx = ahead;
    }

    if (idx < 0) {
      setFootMessage("「" + target + "」が見つかりません");
      field.focus();
      field.select();
      return;
    }

    textBox.value = value.substring(0, idx) + value.substring(idx + target.length);
    textBox.focus();
    textBox.setSelectionRange(idx, idx);
    setFootMessage("「" + target + "」を削除しました");
  }

  // フッターの一時メッセージを表示する
  function setFootMessage(msg) {
    var el = document.getElementById("footMsg");
    if (!el) return;
    el.textContent = msg;
    if (msg) {
      clearTimeout(setFootMessage.timer);
      setFootMessage.timer = setTimeout(function() { el.textContent = ""; }, 2500);
    }
  }

  // ボタンを右側パネルに生成する
  function buildParticleButtons() {
    var container = document.getElementById("particleGroups");
    if (!container) return;
    container.innerHTML = "";

    for (var g = 0; g < PARTICLE_GROUPS.length; g++) {
      container.appendChild(createGroup(PARTICLE_GROUPS[g]));
    }
  }

  // グループ1つを生成する
  function createGroup(group) {
    var details = document.createElement("details");
    details.open = true;

    var summary = document.createElement("summary");
    summary.className = "groupTitle";
    summary.appendChild(document.createTextNode(group.name + " (" + countItems(group) + ")"));
    details.appendChild(summary);

    if (group.subs) {
      for (var s = 0; s < group.subs.length; s++) {
        details.appendChild(createSubRow(group.subs[s]));
      }
    } else {
      details.appendChild(createGrid(group.items));
    }

    return details;
  }

  // グループ内の語数を数える
  function countItems(group) {
    if (!group.subs) return group.items.length;
    var n = 0;
    for (var s = 0; s < group.subs.length; s++) {
      n += group.subs[s].items.length;
    }
    return n;
  }

  // 通常の語ボタンを行内に並べる
  function createGrid(items) {
    var row = document.createElement("div");
    row.className = "row";

    for (var i = 0; i < items.length; i++) {
      row.appendChild(createButton(items[i]));
    }

    return row;
  }

  // 見出しとボタンを同じ行に並べる
  function createSubRow(group) {
    var row = document.createElement("div");
    row.className = "row subRow";

    var label = document.createElement("span");
    label.className = "subLabel";
    label.appendChild(document.createTextNode(group.name));
    row.appendChild(label);

    for (var i = 0; i < group.items.length; i++) {
      row.appendChild(createButton(group.items[i]));
    }

    return row;
  }

  // ボタン1個を生成する
  function createButton(text) {
    var btn = document.createElement("button");
    btn.className = "pbtn";
    btn.type = "button";
    btn.appendChild(document.createTextNode(text));
    btn.onclick = (function(word) {
      return function() { insertParticle(word); };
    })(text);
    return btn;
  }

  window.addEventListener("load", buildParticleButtons);
</script>
<style>
    /* 画面からはみ出さずに1画面に収める */
    html {
      height: 100%;
      overflow: hidden;
    }
    body {
      height: 100%;
      margin: 0;
      padding: 6px;
      box-sizing: border-box;
      font-family: "Meiryo", "MS PGothic", sans-serif;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }
    .button {
      display: inline-block;
      margin-bottom: 10px;
      margin-right: 10px;
    }
    .toolbar {
      flex: 0 0 auto;
      margin-bottom: 2px;
    }
    .main {
      flex: 1 1 auto;
      min-height: 0;
      display: flex;
      gap: 8px;
    }
    #myTextBox {
      flex: 1 1 220px;
      min-width: 200px;
      height: 100%;
      font-size: 16px;
      box-sizing: border-box;
      resize: none;
    }
    /* 助詞パネルは右側に置き、語ボタンを横に並べる */
    #particlePanel {
      flex: 1 1 640px;
      min-width: 560px;
      box-sizing: border-box;
      border: 1px solid #999;
      padding: 4px;
      background: #fafafa;
      display: flex;
      flex-direction: column;
    }
    /* 画面が足りない場合のみスクロールする */
    #particleGroups {
      flex: 1 1 auto;
      min-height: 0;
      overflow-y: auto;
    }
    .groupTitle {
      font-weight: bold;
      font-size: 13px;
      padding: 1px 4px;
      background: #e8eef5;
      border: 1px solid #c4d2e0;
      cursor: pointer;
      user-select: none;
    }
    .row {
      display: flex;
      flex-wrap: wrap;
      gap: 3px;
      padding: 1px 0;
    }
    .subLabel {
      font-size: 12px;
      font-weight: bold;
      color: #444;
      align-self: center;
      min-width: 62px;
    }
    .pbtn {
      font-family: inherit;
      font-size: 14px;
      line-height: 1.2;
      padding: 2px 6px;
      background: #fff;
      border: 1px solid #888;
      border-radius: 3px;
      cursor: pointer;
      min-width: 30px;
    }
    .pbtn:hover {
      background: #fff3b0;
      border-color: #b58900;
    }
    .pbtn:active {
      background: #f6c445;
    }
    .panelFoot {
      flex: 0 0 auto;
      border-top: 1px solid #ddd;
      padding-top: 2px;
    }
    .panelFoot .button {
      margin-bottom: 2px;
      margin-right: 0;
    }
    .footRow {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 4px;
    }
    .footLabel {
      font-size: 12px;
      color: #444;
      margin-left: 6px;
    }
    .footInput {
      font-family: inherit;
      font-size: 13px;
      padding: 2px 4px;
      width: 130px;
      box-sizing: border-box;
    }
    .footMsg {
      font-size: 11px;
      color: #a33;
      margin-left: 6px;
    }
</style>
</head>
<body>
  <div class="toolbar">
    <button class="button" onclick="clearTextBox()">クリア</button>
    <button class="button" onclick="startSpeechRecognition()">音声入力を開始</button>
    <button class="button" onclick="copyTextToClipboard()">テキストをコピー</button>
  </div>
  <div class="main">
    <textarea id="myTextBox"></textarea>
    <div id="particlePanel">
      <div id="particleGroups"></div>
      <div class="panelFoot">
        <div class="footRow">
          <button class="button" onclick="insertNewLine()">改行</button>
          <button class="button" onclick="deletePrevChar()">1文字削除</button>
          <label class="footLabel" for="insertTarget">入力する文字列</label>
          <input type="text" id="insertTarget" class="footInput" placeholder="入れる語句を入力" onkeydown="if (event.key === 'Enter') insertByInput();">
          <button class="button" onclick="insertByInput()">挿入</button>
          <label class="footLabel" for="deleteTarget">削除する文字列</label>
          <input type="text" id="deleteTarget" class="footInput" placeholder="消す語句を入力" onkeydown="if (event.key === 'Enter') deleteByTarget();">
          <button class="button" onclick="deleteByTarget()">削除</button>
          <span id="footMsg" class="footMsg"></span>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

JavaScript

Posted by eightban