見出し画像

自作のテキストエディタを改良してスプレッドシートと連携してみた

スプレッドシートと連携するのでGASを利用
スプレッドシートのシート名はテキスト
1行目にタイムスタンプ、タイトル、テキストの順

スプレッドシート

GASに下記の4つのコードを入力したら、自己責任でデプロイして実行。
デプロイしてURLをゲットすればどのデバイスからでも入力できる。

3つ目と4つ目はスプレッドシートIDが必要
スプレッドシートIDはスプレッドシートのURLの ■の長い文字列の部分
projects/■■■■■■■■■■■■■■■■■■■■■/edit
1つ目は、code.gs
2つ目は、index.html
3つ目は、saveToSpreadsheet.gs
4つ目は、loadFromSpreadsheet.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');  // HTMLファイルを呼び出す
}

function saveToSpreadsheet(title, content) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.appendRow([title, content]);  // タイトルとコンテンツを新しい行に追加
}
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>テキストエディタ</title>
  <style>
    body {
      font-family: Arial, sans-serif;
    }

    .editor-wrapper {
      position: relative;
      width: 800px;
      height: 500px;
      margin: 0 auto;
      border: 1px solid #ccc;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    .line-numbers {
      position: absolute;
      top: 10px;
      left: 0;
      bottom: 0;
      width: 30px;
      text-align: right;
      padding-right: 5px;
      line-height: 1.5;
      font-size: 14px;
      background-color: #f0f0f0;
      color: blue; /* 行番号の色を青に設定 */
      user-select: none;
      pointer-events: none;
      overflow: hidden;
    }

    textarea {
      width: 100%;
      height: 100%;
      padding-left: 40px;
      padding-top: 10px;
      line-height: 1.5;
      font-size: 14px;
      border: none;
      resize: none;
      outline: none;
      white-space: pre;
      font-family: inherit;
      box-sizing: border-box;
    }

    .controls {
      display: grid;
      grid-template-columns: 1fr;
      gap: 10px;
      margin: 10px auto;
      justify-content: center;
    }

    .row {
      display: flex;
      gap: 10px;
      justify-content: center;
      align-items: center; /* 垂直位置を中央に揃える */
    }

    select, input, button {
      padding: 5px;
    }

    .search-replace {
      display: flex;
      gap: 10px;
    }

    /* タイトル入力エリアのスタイル */
    #titleInput {
      font-size: 20px;
      font-weight: bold;
      width: 60%; /* テキストエリアと同じ幅 */
    }

    /* タイトルの表示エリアのスタイル */
    #titleDisplay {
      font-size: 20px;
      font-weight: bold;
      width: 100%; /* テキストエリアと同じ幅 */
      border: none;
      background-color: transparent;
      text-align: left; /* 左寄せ */
    }
  </style>
</head>
<body>

  <div class="controls">
    <!-- 一列目: タイトル表示 -->
    <div class="row">
      <input type="text" id="titleInput" placeholder="タイトルを入力してください">
    </div>

    <!-- 二列目: フォントサイズ、文字色、背景色 -->
    <div class="row">
      <select id="fontSizeSelect" onchange="changeFontSize()">
        <option value="12">12pt</option>
        <option value="14" selected>14pt</option>
        <option value="16">16pt</option>
        <option value="18">18pt</option>
        <option value="20">20pt</option>
      </select>

      <select id="textColorSelect" onchange="changeTextColor()">
        <option value="black" selected>文字色: 黒</option>
        <option value="red">赤</option>
        <option value="blue">青</option>
        <option value="green">緑</option>
        <option value="orange">オレンジ</option>
      </select>

      <select id="backgroundColorSelect" onchange="changeBackgroundColor()">
        <option value="white" selected>背景色: 白</option>
        <option value="black">黒</option>
        <option value="lightyellow">薄黄</option>
        <option value="lightblue">薄青</option>
        <option value="lightgreen">薄緑</option>
      </select>

      <button onclick="showTitleSelection()">読込</button>
      <button onclick="saveContent()">保存</button>
    </div>

    <!-- 三列目: 検索、置換 -->
    <div class="row">
      <input type="text" id="searchBox" placeholder="検索">
      <button onclick="searchText()">検索</button>
      <button onclick="searchNext()">次を検索</button>
      <input type="text" id="replaceBox" placeholder="置換">
      <button onclick="replaceText()">置換</button>
    </div>
  </div>

  <div class="editor-wrapper">
    <div class="line-numbers" id="line-numbers"></div>
    <textarea id="editor" oninput="updateLineNumbers()" onscroll="syncScroll()"></textarea>
  </div>

  <script>
    let lastMatchIndex = -1; // 最後に選択されたマッチのインデックス
    let titleData = [];

    function updateLineNumbers() {
      const editor = document.getElementById("editor");
      const lineNumbers = document.getElementById("line-numbers");
      const lines = editor.value.split("\n").length;
      let lineNumberHtml = '';

      for (let i = 1; i <= lines; i++) {
        lineNumberHtml += i + '<br>';
      }

      lineNumbers.innerHTML = lineNumberHtml;
    }

    function syncScroll() {
      const editor = document.getElementById("editor");
      const lineNumbers = document.getElementById("line-numbers");
      lineNumbers.scrollTop = editor.scrollTop;
    }

    function changeFontSize() {
      const editor = document.getElementById("editor");
      const lineNumbers = document.getElementById("line-numbers");
      const fontSizeSelect = document.getElementById("fontSizeSelect");
      const fontSize = fontSizeSelect.value;

      editor.style.fontSize = fontSize + 'pt';
      lineNumbers.style.fontSize = fontSize + 'pt';
    }

    function changeTextColor() {
      const editor = document.getElementById("editor");
      const textColorSelect = document.getElementById("textColorSelect");
      const textColor = textColorSelect.value;

      editor.style.color = textColor; // テキストの色を変更
    }

    function changeBackgroundColor() {
      const editor = document.getElementById("editor");
      const backgroundColorSelect = document.getElementById("backgroundColorSelect");
      const backgroundColor = backgroundColorSelect.value;

      editor.style.backgroundColor = backgroundColor; // 背景色を変更
    }

    function searchText() {
      const editor = document.getElementById("editor");
      const searchTerm = document.getElementById("searchBox").value;

      const editorContent = editor.value;
      const regex = new RegExp(searchTerm, 'gi');  // 'i' フラグで大文字小文字を無視
      const matches = editorContent.match(regex);

      if (matches) {
        lastMatchIndex = editorContent.toLowerCase().indexOf(matches[0].toLowerCase()); // 最初のマッチのインデックスを保存
        editor.setSelectionRange(lastMatchIndex, lastMatchIndex + matches[0].length);
        editor.focus();

        // 検索結果を中央に表示するためのスクロール位置を計算
        const editorHeight = editor.clientHeight;
        const lineHeight = parseInt(window.getComputedStyle(editor).lineHeight, 10);
        const linesFromTop = editor.value.substring(0, lastMatchIndex).split("\n").length - 1; // 上から何行目か
        const scrollPosition = (linesFromTop * lineHeight) - (editorHeight / 2) + (lineHeight / 2);

        // スクロールを中央に移動
        editor.scrollTop = Math.max(scrollPosition, 0);
      }
    }

    function searchNext() {
      const editor = document.getElementById("editor");
      const searchTerm = document.getElementById("searchBox").value;
      const editorContent = editor.value;

      const selectedText = editor.value.substring(editor.selectionStart, editor.selectionEnd);
      let matchIndex;

      if (selectedText) {
        const regex = new RegExp(selectedText, 'gi');  // 'i' フラグで大文字小文字を無視

        while ((matchIndex = regex.exec(editorContent)) !== null) {
          if (matchIndex.index > lastMatchIndex) {
            lastMatchIndex = matchIndex.index;
            editor.setSelectionRange(lastMatchIndex, lastMatchIndex + matchIndex[0].length);
            editor.focus();

            // 検索結果を中央に表示するためのスクロール位置を計算
            const editorHeight = editor.clientHeight;
            const lineHeight = parseInt(window.getComputedStyle(editor).lineHeight, 10);
            const linesFromTop = editor.value.substring(0, lastMatchIndex).split("\n").length - 1; // 上から何行目か
            const scrollPosition = (linesFromTop * lineHeight) - (editorHeight / 2) + (lineHeight / 2);

            // スクロールを中央に移動
            editor.scrollTop = Math.max(scrollPosition, 0);
            return;
          }
        }
      }

      lastMatchIndex = -1; // もしマッチが無ければリセット
    }

    function replaceText() {
      const editor = document.getElementById("editor");
      const searchTerm = document.getElementById("searchBox").value;
      const replaceTerm = document.getElementById("replaceBox").value;
      const editorContent = editor.value;

      const regex = new RegExp(searchTerm, 'gi');
      editor.value = editorContent.replace(regex, replaceTerm);
      updateLineNumbers();
      editor.focus(); // エディターにフォーカス
    }

    function showTitleSelection() {
      // スプレッドシートからタイトルを読み込む
      google.script.run.withSuccessHandler(displayTitleSelection).loadFromSpreadsheet();
    }

    function displayTitleSelection(data) {
  const selectionWindow = window.open("", "Title Selection", "width=400,height=300");
  selectionWindow.document.write("<html><head><title>タイトル選択</title></head><body>");
  selectionWindow.document.write("<h2>タイトルを選択してください</h2>");

  data.forEach((item, index) => {
    selectionWindow.document.write(
      `<input type="radio" name="title" id="title${index}" value="${item.title}" data-text="${item.text}">`
    );
    selectionWindow.document.write(`<label for="title${index}">${item.title}</label><br>`);
  });

  selectionWindow.document.write(
    `<button onclick="selectTitle()">実行</button>
    <script>
      function selectTitle() {
        const radios = document.querySelectorAll('input[name="title"]');
        let selectedTitle;
        let selectedText;

        radios.forEach(radio => {
          if (radio.checked) {
            selectedTitle = radio.value;
            selectedText = radio.getAttribute('data-text');
          }
        });

        if (selectedTitle) {
          // メインウィンドウにタイトルとテキストを送信
          window.opener.selectTitle(selectedTitle, selectedText);
          window.close(); // ウィンドウを閉じる
        } else {
          alert('タイトルを選択してください。');
        }
      }
    <\/script>` // エスケープ処理
  );
  selectionWindow.document.write("</body></html>");
}


    function loadContent() {
      // スプレッドシートからの内容読み込み処理を実装
      google.script.run.withSuccessHandler(function(data) {
        // 取得したデータをタイトル入力フィールドに設定
        titleData = data; // データを保存
        const titleInput = document.getElementById("titleInput");
        titleInput.value = titleData[0] || ""; // 最初のタイトルを表示
        document.getElementById("editor").value = titleData[1] || ""; // コンテンツを表示
        updateLineNumbers(); // 行番号を更新
      }).loadFromSpreadsheet(); // Google Apps Scriptの関数を呼び出す
    }

    function saveContent() {
      const editor = document.getElementById("editor");
      const title = document.getElementById("titleInput").value;
      const content = editor.value;

      // Google Apps Scriptの関数を呼び出してスプレッドシートに保存
      google.script.run.saveToSpreadsheet(title, content);

      // ユーザーに保存完了を通知
      alert('データがスプレッドシートに保存されました!');
    }

    function selectTitle(title, text) {
      const titleInput = document.getElementById("titleInput");
      const editor = document.getElementById("editor");

      titleInput.value = title; // タイトルを入力
      editor.value = text; // テキストをエディターに表示
      updateLineNumbers(); // 行番号を更新
    }

  </script>

</body>
</html>
function saveToSpreadsheet(title, content) {
  var spreadsheetId = "ここにスプレッドシートのIDを入れる";  // スプレッドシートのIDを入力
  var sheetName = "テキスト";  // シート名を入力
  
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName(sheetName);
  
  if (!sheet) {
    Logger.log("指定されたシートが見つかりません");
    return;
  }

  // A列にタイムスタンプ、B列にタイトル、C列にテキスト内容を書き込む
  sheet.appendRow([new Date(), title, content]);  // 現在の日時をタイムスタンプとしてA列に追加
}
function loadFromSpreadsheet() {
  var spreadsheetId = "ここにスプレッドシートのIDを入力する";  // スプレッドシートのIDを入力
  var sheetName = "テキスト";  // シート名を入力
  
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var sheet = spreadsheet.getSheetByName(sheetName);
  
  if (!sheet) {
    Logger.log("指定されたシートが見つかりません");
    return [];
  }

  // B2から最終行までのタイトルと対応するC列のテキストを取得
  var lastRow = sheet.getLastRow();
  var titlesRange = sheet.getRange(2, 2, lastRow - 1, 1);  // B2から最終行まで
  var textsRange = sheet.getRange(2, 3, lastRow - 1, 1);   // C2から最終行まで
  
  var titles = titlesRange.getValues().flat();
  var texts = textsRange.getValues().flat();
  
  // タイトルと対応するテキストを一緒に返す [{title: "タイトル1", text: "テキスト1"}, ...]
  var data = titles.map(function(title, index) {
    return {
      title: title,
      text: texts[index]
    };
  });
  
  return data;  // タイトルとテキストのペアを返す
}

いいなと思ったら応援しよう!