見出し画像

うわ!仕事で使うツール、バラバラすぎ・・・・・・

毎日の作業。エディタでメモを取り、ブラウザで検索し、カレンダーで日程を確認する。アプリを行ったり来たりの無駄な動き。

「全部一つの画面にまとめられないかな」

そんな思いつきを、AIの力を借りて形にしてみました。
まえに簡単に作ったエディタを応用すればいいかな、という程度の安易な考えでした。

開発スタート! でも、すぐに壁にぶつかる

最初の構想は単純でした。

  • 左半分に黒背景のシンプルなエディタ

  • 右上にブラウザ

  • 右下にカレンダー

しかし、開発を始めて15分。早速、技術的な壁にぶつかります。

「ブラウザ部分が表示できない!」

調べてみると、これはセキュリティ上の制限だったのです。
GoogleやYouTubeなどの主要サイトは、iframeでの埋め込みを制限しています。
これは「クリックジャッキング」という攻撃からユーザーを守るための対策なのだとか。

(なるほど、セキュリティって大事...)

そんなことに感心しつつも、できないことにちょっと苦戦しました。

失敗が生んだ、より良いアイデア

ここでAIが面白い提案をしてきました。
「ブラウザの代わりに、カスタマイズ可能なリンク集はどうでしょう?」
これが予想以上の効果を生みました。

  • よく使うサイトへのクイックアクセス

  • Google検索機能

  • リンクの追加・編集が自由自在

実は、フルブラウザよりも使いやすいかもしれません。
はい、そんなことはないですね、さすがに。

驚きの開発スピード

AIとの共同開発は、想像以上にスピーディでした。

  • 基本設計:15分

  • エディタ部分:20分

  • リンク集&検索機能:45分

  • カレンダー実装:25分

  • リンク管理機能:15分

合計たったの2時間で、実用的なツールが完成しました。

完成したエディタの特徴

  1. シンプルで使いやすい

    • 目に優しい黒背景のエディタ

    • 自動保存機能付き

    • 直感的な操作性

  2. カスタマイズ自由自在

    • リンクの追加・編集・削除

    • よく使うサイトへのクイックアクセス

    • Google検索機能

  3. 3ヶ月分のカレンダー

    • コンパクトな表示

    • 今日の日付を強調

    • 土日の色分け

そして、最終的にできたエディタがこれです!

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>高機能エディタ</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            display: flex;
            height: 100vh;
            background: #1a1a1a;
            color: #e0e0e0;
            font-family: 'Segoe UI', sans-serif;
        }
        .editor-section {
            width: 50%;
            padding: 20px;
            background: #1e1e1e;
        }
        #editor {
            width: 100%;
            height: 100%;
            background: #1e1e1e;
            color: #d4d4d4;
            border: none;
            resize: none;
            font-family: 'Consolas', monospace;
            font-size: 16px;
            line-height: 1.6;
            outline: none;
            padding: 10px;
        }
        .tools-section {
            width: 50%;
            display: flex;
            flex-direction: column;
            padding: 20px;
            background: #252526;
        }
        .browser-area {
            flex-grow: 1;
            background: #1e1e1e;
            margin-bottom: 20px;
            border-radius: 8px;
            padding: 15px;
            display: flex;
            flex-direction: column;
            gap: 15px;
            min-height: 0;
        }
        .search-box {
            display: flex;
            gap: 10px;
        }
        .search-input {
            flex-grow: 1;
            padding: 8px 12px;
            background: #2d2d2d;
            border: 1px solid #454545;
            color: #d4d4d4;
            border-radius: 4px;
        }
        .search-button {
            padding: 8px 16px;
            background: #0078d4;
            border: none;
            color: white;
            border-radius: 4px;
            cursor: pointer;
        }
        .quick-links {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
            padding: 15px;
            background: #2d2d2d;
            border-radius: 4px;
            overflow-y: auto;
        }
        .link-card {
            position: relative;
            padding: 10px;
            background: #1e1e1e;
            border-radius: 4px;
            cursor: pointer;
            transition: background 0.3s;
        }
        .link-card:hover {
            background: #333;
        }
        .link-card h3 {
            margin: 0 0 5px 0;
            color: #4ec9b0;
            font-size: 14px;
        }
        .link-card p {
            margin: 0;
            color: #d4d4d4;
            font-size: 12px;
        }
        .link-controls {
            margin-top: 10px;
            text-align: right;
        }
        .control-button {
            padding: 8px 16px;
            background: #0078d4;
            border: none;
            color: white;
            border-radius: 4px;
            cursor: pointer;
        }
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 1000;
        }
        .modal-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #2d2d2d;
            padding: 20px;
            border-radius: 8px;
            width: 300px;
        }
        .modal-input {
            width: 100%;
            padding: 8px;
            margin: 10px 0;
            background: #1e1e1e;
            border: 1px solid #454545;
            color: #d4d4d4;
            border-radius: 4px;
        }
        .modal-buttons {
            display: flex;
            gap: 10px;
            justify-content: flex-end;
            margin-top: 15px;
        }
        .modal-buttons button {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .modal-buttons button:first-child {
            background: #0078d4;
            color: white;
        }
        .modal-buttons button:last-child {
            background: #454545;
            color: #d4d4d4;
        }
        .link-card .edit-delete {
            display: none;
            position: absolute;
            top: 5px;
            right: 5px;
            gap: 5px;
        }
        .link-card:hover .edit-delete {
            display: flex;
        }
        .edit-delete button {
            padding: 3px 6px;
            background: rgba(0,0,0,0.5);
            border: none;
            color: #d4d4d4;
            border-radius: 3px;
            cursor: pointer;
            font-size: 12px;
        }
        #calendars {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
            height: 200px;
        }
        .calendar {
            background: #1e1e1e;
            padding: 8px;
            border-radius: 4px;
        }
        .calendar-title {
            text-align: center;
            color: #4ec9b0;
            margin-bottom: 5px;
            font-size: 0.9em;
        }
        .calendar-table {
            width: 100%;
            border-collapse: collapse;
        }
        .calendar-table th, 
        .calendar-table td {
            padding: 2px;
            text-align: center;
            border: 1px solid #333;
            font-size: 0.85em;
        }
        .sunday { color: #ff4444; }
        .saturday { color: #4444ff; }
        .today {
            background: #264f78;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div class="editor-section">
        <textarea id="editor" spellcheck="false"></textarea>
    </div>
    <div class="tools-section">
        <div class="browser-area">
            <div class="search-box">
                <input type="text" class="search-input" id="searchInput" 
                       placeholder="検索キーワードを入力" 
                       onkeydown="if(event.key==='Enter')performSearch()">
                <button class="search-button" onclick="performSearch()">検索</button>
            </div>
            <div class="quick-links" id="quickLinks"></div>
            <div class="link-controls">
                <button class="control-button" onclick="showAddLinkForm()">+ リンクを追加</button>
            </div>
        </div>
        <div id="calendars"></div>
    </div>

    <!-- リンク追加/編集用モーダル -->
    <div id="linkModal" class="modal">
        <div class="modal-content">
            <h2>リンクの追加/編集</h2>
            <input type="text" id="linkTitle" placeholder="タイトル" class="modal-input">
            <input type="text" id="linkUrl" placeholder="URL" class="modal-input">
            <input type="text" id="linkDescription" placeholder="説明" class="modal-input">
            <div class="modal-buttons">
                <button onclick="saveLinkCard()">保存</button>
                <button onclick="closeLinkModal()">キャンセル</button>
            </div>
        </div>
    </div>

    <script>
        // デフォルトのリンク設定
        const defaultLinks = [
            { title: 'Google', url: 'https://www.google.co.jp', description: '検索エンジン' },
            { title: 'YouTube', url: 'https://www.youtube.com', description: '動画プラットフォーム' },
            { title: 'GitHub', url: 'https://www.github.com', description: 'ソースコード管理' },
            { title: 'ChatGPT', url: 'https://chat.openai.com', description: 'AI チャット' },
            { title: 'DeepL', url: 'https://www.deepl.com/translator', description: '翻訳サービス' },
            { title: 'Notion', url: 'https://www.notion.so', description: 'ノート・タスク管理' }
        ];

        // リンク管理機能
        let editingIndex = -1;

        function saveLinks(links) {
            localStorage.setItem('quickLinks', JSON.stringify(links));
        }

        function loadLinks() {
            const saved = localStorage.getItem('quickLinks');
            return saved ? JSON.parse(saved) : defaultLinks;
        }

        function renderLinkCards() {
            const links = loadLinks();
            const container = document.getElementById('quickLinks');
            container.innerHTML = links.map((link, index) => `
                <div class="link-card" onclick="openLink('${link.url}')">
                    <div class="edit-delete">
                        <button onclick="event.stopPropagation(); editLink(${index})">編集</button>
                        <button onclick="event.stopPropagation(); deleteLink(${index})">削除</button>
                    </div>
                    <h3>${link.title}</h3>
                    <p>${link.description}</p>
                </div>
            `).join('');
        }

        // モーダル操作
        function showAddLinkForm() {
            editingIndex = -1;
            document.getElementById('linkTitle').value = '';
            document.getElementById('linkUrl').value = '';
            document.getElementById('linkDescription').value = '';
            document.getElementById('linkModal').style.display = 'block';
        }

        function editLink(index) {
            const links = loadLinks();
            const link = links[index];
            editingIndex = index;
            document.getElementById('linkTitle').value = link.title;
            document.getElementById('linkUrl').value = link.url;
            document.getElementById('linkDescription').value = link.description;
            document.getElementById('linkModal').style.display = 'block';
        }

        function closeLinkModal() {
            document.getElementById('linkModal').style.display = 'none';
        }

        function saveLinkCard() {
            const title = document.getElementById('linkTitle').value;
            const url = document.getElementById('linkUrl').value;
            const description = document.getElementById('linkDescription').value;
            
            if (!title || !url) {
                alert('タイトルとURLは必須です');
                return;
            }

            const links = loadLinks();
            const newLink = { title, url, description };
            
            if (editingIndex === -1) {
                links.push(newLink);
            } else {
                links[editingIndex] = newLink;
            }

            saveLinks(links);
            renderLinkCards();
            closeLinkModal();
        }

        function deleteLink(index) {
            if (confirm('このリンクを削除してもよろしいですか?')) {
                const links = loadLinks();
                links.splice(index, 1);
                saveLinks(links);
                renderLinkCards();
            }
        }

        // 検索機能
        function performSearch() {
            const searchTerm = document.getElementById('searchInput').value;
            if (searchTerm.trim()) {
                const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(searchTerm)}`;
                window.open(searchUrl, '_blank');
            }
        }

        // リンクを開く
        function openLink(url) {
            window.open(url, '_blank');
        }

        // カレンダー機能
        function createCalendar(monthOffset = 0) {
            const date = new Date();
            date.setMonth(date.getMonth() + monthOffset);
            const year = date.getFullYear();
            const month = date.getMonth();
            const today = new Date();
            const firstDay = new Date(year, month, 1);
            const lastDay = new Date(year, month + 1, 0);
            
            let html = `
                <div class="calendar">
                    <div class="calendar-title">${year}年${month + 1}月</div>
                    <table class="calendar-table">
                        <tr>
                            <th class="sunday">日</th>
                            <th>月</th>
                            <th>火</th>
                            <th>水</th>
                            <th>木</th>
                            <th>金</th>
                            <th class="saturday">土</th>
                        </tr>
            `;
            
            let day = 1;
            const startDay = firstDay.getDay();
            const endDay = lastDay.getDate();

            for (let i = 0; i < 6; i++) {
                html += '<tr>';
                for (let j = 0; j < 7; j++) {
                    if (i === 0 && j < startDay || day > endDay) {
                        html += '<td></td>';
                    } else if (day <= endDay) {
                        const isToday = day === today.getDate() && 
                                      month === today.getMonth() && 
                                      year === today.getFullYear();
                        const isSunday = j === 0;
                        const isSaturday = j === 6;
                        
                        let className = [];
                        if (isToday) className.push('today');
                        if (isSunday) className.push('sunday');
                        if (isSaturday) className.push('saturday');
                        
                        html += `<td class="${className.join(' ')}">${day}</td>`;
                        day++;
                    }
                }
                html += '</tr>';
                if (day > endDay) break;
            }
            
            html += '</table></div>';
            return html;
        }

        function showCalendars() {
            const container = document.getElementById('calendars');
            let html = '';
            for (let i = 0; i < 3; i++) {
                html += createCalendar(i);
            }
            container.innerHTML = html;
        }

        // エディタの自動保存機能
        const editor = document.getElementById('editor');
        
        function autoSave() {
            localStorage.setItem('editorContent', editor.value);
        }

        // 保存した内容の復元
        if (localStorage.getItem('editorContent')) {
            editor.value = localStorage.getItem('editorContent');
        }

        // 初期化
        renderLinkCards();
        showCalendars();

        // 自動保存と更新の設定
        setInterval(autoSave, 30000);
        setInterval(showCalendars, 3600000);
    </script>
</body>
</html>

意外な発見

開発中、最も印象的だったのは「制限が生むクリエイティビティ」です。

当初の「ブラウザ埋め込み」という案が潰れたことで、かえって使いやすいツールが生まれました。これって、デザインの世界でよくある話ですよね。

これから追加したい機能

すでに、いくつかの改善アイデアが浮かんでいます:

  • マークダウン対応

  • ショートカットキー

  • テーマカスタマイズ

  • タグ付けによるリンク整理

最後に

「理想のツールは、自分で作ればいい」

そんな当たり前のことを、今回の開発で実感しました。しかも、AIという強力な助っ人がいれば、それほど難しくありません。

次回は、このエディタにマークダウン機能を追加する予定です。 「また2時間でできるのか?」 それは、次回のお楽しみということで!

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