自作のテキストエディタを改良してスプレッドシートと連携してみた
スプレッドシートと連携するので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; // タイトルとテキストのペアを返す
}