仕事で決められたURLの遷移先を1つずつ確認する作業があり、毎回コピペして確認するのが手間でいい方法がないか考えてみました。
Chromeのアドオンでないか確認してみましたが、複数のURLを一度に開く拡張機能はあったのですが、今回は確認するURLが100以上毎回あるので現実的ではないのかなと判断しました。
そこでhtml、css、javascriptで作ってしまえばいいのでは?ということでChatGPTに聞きながら作成しました。
結論から言うとログインが必要なサイトだと上手く機能しなかったのでボツにはなりましたが、もったいないので記事にして公開することにしました 笑
左上のEnter URLsに確認したいURLのリストを入力します。
次にLoad URLsボタンを押下してサイトを確認していきます。
右上のNextボタンとPreviousボタンでリストに記載したURLをそれぞれ遷移することが出来ます。
最終的なコード
必要最低限の機能を実装したスクリプトが完成しました。自分で調べながらやったら1週間くらいかかると思います 笑
使い方はindex.html、styles.css、script.jsという3つのファイルを作成して、同じフォルダの中に配置します。
その後index.htmlをクリックしてください。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL Viewer</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="urlInputPanel">
<label for="urlInput">Enter URL(s)</label></br>
<textarea id="urlInput" rows="10"></textarea></br>
<button onclick="loadURLs()">Load URLs</button></br>
<label for="memoInput">Memo</label></br>
<textarea id="memoInput"></textarea></br>
<button id="copyAllMemosBtn" onclick="copyAllMemos()">Copy All Memos</button></br>
<label for="jumpToIndex" id="jumpToIndexLabel">Jump to Index:</label></br>
<input type="number" id="jumpToIndex" min="1" step="1"></br>
<button onclick="jumpToIndex()">Jump</button></br>
<label for="clearLocalStorageBtn">clear all</label>
<button id="clearLocalStorageBtn" onclick="confirmClearLocalStorage()">Clear Local Storage</button>
</div>
<div id="urlDisplayPanel">
<div id="urlInfoLabel">Current URL:</div>
<iframe id="urlFrame" src="" frameborder="0"></iframe>
<div id="navigationButtons">
<button onclick="showPrevious()" id="previousBtn">Previous</button>
<button onclick="showNext()" id="nextBtn">Next</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
body {
margin: 0;
display: flex;
height: 100vh;
}
#urlInputPanel {
flex: 1;
width: 10%;
padding: 10px;
box-sizing: border-box;
position: relative;
}
#urlInputPanel textarea {
width: 100%;
margin-bottom: 10px;
}
#urlInputPanel button {
margin-bottom: 10px;
}
#urlInputPanel #memoInput {
width: 100%;
height: 100px;
margin-bottom: 10px;
}
#urlDisplayPanel {
flex: 9;
width: 90%;
padding: 10px;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
#urlDisplayPanel iframe {
width: 100%;
height: 100%;
border: none;
}
#urlInfoLabel {
position: absolute;
top: 10px;
left: 10px;
font-size: 14px;
color: #333;
}
#navigationButtons {
position: absolute;
top: 10px;
right: 10px;
display: flex;
justify-content: space-between;
}
#jumpToIndexPanel {
position: absolute;
bottom: 50px;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-end;
}
#jumpToIndexLabel {
margin-bottom: 5px;
}
#jumpToIndex {
margin-bottom: 5px;
}
#copyAllMemosBtn {
margin-top: 10px;
}
let urls = [];
let currentIndex = -1;
let memoData = {}; // URLごとのメモデータを格納するオブジェクト
document.addEventListener('DOMContentLoaded', function () {
const storedData = localStorage.getItem('urlViewerData');
if (storedData) {
const parsedData = JSON.parse(storedData);
urls = parsedData.urls || [];
currentIndex = parsedData.currentIndex !== undefined ? parsedData.currentIndex : -1;
memoData = parsedData.memoData || {};
restoreData();
}
document.addEventListener('keydown', function (event) {
// Ctrl + S (Windows) または Command + S (MacOS) で保存
if ((event.ctrlKey && event.key === 's') || (event.metaKey && event.key === 's')) {
event.preventDefault();
saveData();
alert('Data saved!');
}
});
});
function saveData() {
const urlInput = document.getElementById('urlInput');
const memoInput = document.getElementById('memoInput');
// Save memo data for the current URL
memoData[urls[currentIndex]] = memoInput.value;
const dataToSave = {
urls: urls,
currentIndex: currentIndex,
memoData: memoData
};
localStorage.setItem('urlViewerData', JSON.stringify(dataToSave));
}
function restoreData() {
const urlInput = document.getElementById('urlInput');
const memoInput = document.getElementById('memoInput');
const urlFrame = document.getElementById('urlFrame');
const urlInfoLabel = document.getElementById('urlInfoLabel');
const nextBtn = document.getElementById('nextBtn');
const jumpToIndexInput = document.getElementById('jumpToIndex');
if (urls.length > 0 && currentIndex >= 0 && currentIndex < urls.length) {
urlInput.value = urls.join('\n');
memoInput.value = memoData[urls[currentIndex]] || '';
urlFrame.src = urls[currentIndex];
urlInfoLabel.textContent = `Current URL: ${urls[currentIndex]}`;
jumpToIndexInput.max = urls.length;
jumpToIndexInput.value = currentIndex + 1;
nextBtn.disabled = urls.length <= 1;
}
}
function loadURLs() {
const urlInput = document.getElementById('urlInput');
const memoInput = document.getElementById('memoInput');
const urlFrame = document.getElementById('urlFrame');
const urlInfoLabel = document.getElementById('urlInfoLabel');
const nextBtn = document.getElementById('nextBtn');
const jumpToIndexInput = document.getElementById('jumpToIndex');
if (urlInput.value.trim() !== '') {
urls = urlInput.value.split('\n').map(url => url.trim());
currentIndex = 0;
urlFrame.src = urls[currentIndex];
urlInfoLabel.textContent = `Current URL: ${urls[currentIndex]}`;
jumpToIndexInput.max = urls.length;
jumpToIndexInput.value = currentIndex + 1;
nextBtn.disabled = urls.length <= 1;
memoData[urls[currentIndex]] = memoInput.value;
console.log('Memo for URL', urls[currentIndex], ':', memoInput.value);
saveData();
}
}
function showPrevious() {
const urlFrame = document.getElementById('urlFrame');
const urlInfoLabel = document.getElementById('urlInfoLabel');
const nextBtn = document.getElementById('nextBtn');
const jumpToIndexInput = document.getElementById('jumpToIndex');
const memoInput = document.getElementById('memoInput');
if (currentIndex > 0) {
memoData[urls[currentIndex]] = memoInput.value;
currentIndex--;
urlFrame.src = urls[currentIndex];
urlInfoLabel.textContent = `Current URL: ${urls[currentIndex]}`;
jumpToIndexInput.value = currentIndex + 1;
nextBtn.disabled = false;
memoInput.value = memoData[urls[currentIndex]] || '';
saveData();
}
}
function showNext() {
const urlFrame = document.getElementById('urlFrame');
const urlInfoLabel = document.getElementById('urlInfoLabel');
const nextBtn = document.getElementById('nextBtn');
const jumpToIndexInput = document.getElementById('jumpToIndex');
const memoInput = document.getElementById('memoInput');
if (currentIndex < urls.length - 1) {
memoData[urls[currentIndex]] = memoInput.value;
currentIndex++;
urlFrame.src = urls[currentIndex];
urlInfoLabel.textContent = `Current URL: ${urls[currentIndex]}`;
jumpToIndexInput.value = currentIndex + 1;
nextBtn.disabled = currentIndex === urls.length - 1;
memoInput.value = memoData[urls[currentIndex]] || '';
saveData();
}
}
function jumpToIndex() {
const urlFrame = document.getElementById('urlFrame');
const urlInfoLabel = document.getElementById('urlInfoLabel');
const jumpToIndexInput = document.getElementById('jumpToIndex');
const memoInput = document.getElementById('memoInput');
memoData[urls[currentIndex]] = memoInput.value;
const newIndex = parseInt(jumpToIndexInput.value, 10) - 1;
if (newIndex >= 0 && newIndex < urls.length) {
currentIndex = newIndex;
urlFrame.src = urls[currentIndex];
urlInfoLabel.textContent = `Current URL: ${urls[currentIndex]}`;
memoInput.value = memoData[urls[currentIndex]] || '';
saveData();
}
}
function copyAllMemos() {
const allMemos = urls.map((url, index) => `${url}, ${memoData[url] || ''}`).join('\n');
const dummyTextArea = document.createElement('textarea');
dummyTextArea.value = allMemos;
document.body.appendChild(dummyTextArea);
dummyTextArea.select();
document.execCommand('copy');
document.body.removeChild(dummyTextArea);
alert('All Memos copied to clipboard!');
}
function confirmClearLocalStorage() {
const confirmation = window.confirm("Are you sure you want to clear all data? This action cannot be undone.");
if (confirmation) {
// ユーザーが確認した場合はローカルストレージをクリア
localStorage.removeItem('urlViewerData');
// 初期化したことを通知
alert('Local Storage cleared. The page will be reloaded.');
// ページを再読み込み
location.reload();
}
}
document.addEventListener('keydown', function (event) {
if (event.key === 'ArrowRight') {
showNext();
} else if (event.key === 'ArrowLeft') {
showPrevious();
}
});
ChatGPTとの作成方法 (プロンプトエンジニアリング)
ChatGPTと対話しながら作成しました。
おかげさまで1日で作成することが出来ました。
作成の流れは下記になります。
頑張って最初は英語でやりとりをしてみようとしています。(後で日本語になります 笑)
I would like to create a webpage that displays a list of urls like slideshare.
動作確認をして、何が問題が伝えます。
User
this is not working. Only displays URL List
User
I want to use this webpage locally.
I would like to create a HTML file that satisfies below.
1. should have two panels, one is "url input panel" and one is "url display panel".
2. "url input panel" is textarea that input urls you want to open
3. "url display panel" reads url one by one from url input panel
4. this is kind of working as a slideshow, need next button and previous button, and label that displays current url opens
User
can you add linenumber to textarea?
User
Next and Previous button should be located on right top corner.
User
I would like to put text area to left side, and iframe to right side
User
Please use both panels in full screen
User
Can you change the width and height of url-display-panel dynamically like url-input-panel?
User
Please make url-display-panel takes up rest of screen area
あなたは優秀なウェブサイトのフロントエンドエンジニアです。
下記のような要件を満たすHTMLファイルを作成してください。
ステップバイステップで作成してください。
1. 左側にURLを入力するパネル(url input panel)、右側にURLをiframeで開いて表示するパネル(url display panel)を作成
2. "url input panel" は urlの入力を受け付けます
3. "url display panel" は url input panelのurlを一つずつ読み込みます。
4. スライドショーのような動きを期待しています。nextボタン、previousボタンも必要です。
5. "url display panel" は画面の90%、"url input panel"は画面の10%ほどを利用するような初期表示にしてください。
User
urlInputPanelではURLを複数受け付けるようにしたいです。
User
<input type="text" id="urlInput">はtextareaでも代用可能ですか?
User
下記二つのボタンは右上に配置してもらえますか?
<button onclick="showPrevious()">Previous</button>
<button onclick="showNext()">Next</button>
User
Load Urlsボタンを押下した後、textareaの内容が消えてしまうようです
(上記内容が上手く伝わらなかったので下記で言い直した)
User
テキストエリアの内容はクリアしたくないです。
User
現在読み込み中のURL情報のラベルをurlDisplayPanelの左上に表示したいです。
User
一番最後のURLの後にもNextボタンが押せてしまうようです。
User
現在のインデックスをLoad Urlsボタンの下に作成したいです。
User
指定したインデックスを入力し、そのURLにジャンプする機能が欲しいです
User
jumpToIndexPanelのheightはもう少し小さくても良いです
あなたは優秀なウェブサイトのフロントエンドエンジニアです。
下記のコードがあります。追加要件を満たすHTMLファイルを作成してください。
ステップバイステップで作成してください。
◆ 追加要件
jumpToIndexPanelの内容をurlInputPanelの中に実装してください。
---
コードをここに記載する
User
jumpToIndexPanelのポジションもurlInputPanelの<button onclick="loadURLs()">Load URLs</button>以下に配置されるようにしたいです。
User
currentIndexLabelは一旦除外してください。
User
urlFrameはurlDisplayPanelのサイズと同じにしてください。
User
キーボードの右矢印を押すとNextボタンを押下、左矢印を押すとPreviousボタンを押下するようにしたいです。
User
<label for="jumpToIndex" id="jumpToIndexLabel">Jump to Index:</label>
<input type="number" id="jumpToIndex" min="1" step="1">
<button onclick="jumpToIndex()">Jump</button>
で、
<input type="number" id="jumpToIndex" min="1" step="1">のレイアウトを変更したいです。
JumpToIndexとnumberの真下にボタンを配置したいです。
CSSの#jumpToIndexPanelで変更可能だと思うので修正してください。
User
メモ欄を設けたいです
User
それぞれのindexごとにメモ欄を設けたいが可能ですか?
User
メモ欄の内容をnextボタンやpreviousボタンを押下するときに保存しておきたいです。
User
メモ欄の情報をcsvにアウトプットしたいのですが、可能でしょうか?
User
Export memoボタンを押下しても何もおきません
(ブラウザのセキュリティポリシーにより、JavaScriptからの自動ダウンロードがブロックされている可能性があります とChatGPTから回答がある)
〜色々やりとりしてもダメそう〜
User
Export memoの代わりにCopy memoボタンを実装出来ますか?
memoArrayの情報を全てコピーして欲しいです
User
最初のindexのメモ欄しかクリップボードにコピーされないようです。全部のメモ欄に記載してある情報をクリップボードにコピーできますか?
User
全てのURLに対するメモ欄の内容をクリップボードにコピーしたいです。
Copy Memoボタンの代わりに、Copy All Memosボタンを実装してください
User
1つのURLに対するメモ欄しかコピーされていないようです。
User
直っていません。原因はわかりますか?
(ここで自分自身でコードレビュー。疑問点を投げかけてみる)
User
function copyAllMemos() {
const memoInput = document.getElementById('memoInput');
memoInput.select();
document.execCommand('copy');
alert('All Memos copied to clipboard!');
}
ここで全てのメモ欄をconcatした情報を作成しなくてもいいのですか?
User
index.htmlを開き直すと情報が全て消されてしまいます。
何かいい方法はありますか?
(ローカルストレージにデータを保存するように修正してくれた)
User
localStorageをクリアする機能も必要ですね。
Memoの下に初期化ボタンが欲しいです。
ボタンを押したときに念のため本当に初期化してよいか確認アラートを実装してください。
User
あなたは優秀なウェブサイトのフロントエンドエンジニアです。
下記のようなコード:index.html、styles.css、script.jsが存在します。追加要件を満たすため修正してください。
まずは様々な方法論を考えてください。
次にステップバイステップで修正したコードを作成してください。
◆ 追加要件
・Cntl+Sで現在の情報をsaveData()で保存したいです。
コードは下記です。
User
Cntl+Sで保存した後、copyAllMemosを押下しても情報がコピーされません。
まとめ
ChatGPTを使うことによりコーディングによる手間や時間はかなり低減することが可能になりました。
ただしAIにユーザーが希望した通りの行動をしてもらうためのノウハウはもっと蓄積していく必要があるなと感じました。