diff --git a/index.html b/index.html index 86c971a..323dd13 100644 --- a/index.html +++ b/index.html @@ -232,6 +232,87 @@ } .md-body a:hover { opacity: .8; } + /* ── Diary panel ── */ + .diary-panel { + position: fixed; + top: 0; right: 0; + width: 380px; + height: 100dvh; + background: var(--surface); + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + z-index: 200; + transform: translateX(100%); + transition: transform .22s cubic-bezier(.4,0,.2,1); + } + .diary-panel.open { transform: translateX(0); } + + .diary-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 16px; + height: 48px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + } + .diary-panel-title { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + font-weight: 500; + letter-spacing: .1em; + color: var(--text1); + display: flex; + align-items: center; + gap: 8px; + } + .diary-status-dot { + width: 7px; height: 7px; + border-radius: 50%; + background: var(--text3); + transition: background .3s; + flex-shrink: 0; + } + .diary-status-dot.connected { background: var(--accent); } + .diary-status-dot.pending { background: #FCD34D; } + + .diary-textarea { + flex: 1; + width: 100%; + resize: none; + border: none; + outline: none; + background: transparent; + color: var(--text1); + font-family: 'JetBrains Mono', monospace; + font-size: 13px; + line-height: 1.75; + padding: 20px; + box-sizing: border-box; + } + .diary-textarea::placeholder { color: var(--text3); } + + .diary-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 16px; + border-top: 1px solid var(--border); + flex-shrink: 0; + } + .diary-sync-status { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: var(--text3); + } + .diary-sync-status.saved { color: var(--accent); } + .diary-sync-status.pending { color: #FCD34D; } + + @media (max-width: 640px) { + .diary-panel { width: 100%; } + } + /* ── Mobile ── */ @media (max-width: 640px) { .pane-wrap { grid-template-columns: 1fr; } @@ -291,9 +372,14 @@ posimai log - +
+ + +
@@ -328,6 +414,29 @@
+ + +
@@ -415,6 +524,104 @@ if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch(() => {}); } + + // ── Diary panel ────────────────────────────────────────── + const DIARY_URL = 'http://localhost:2626/diary'; + const DIARY_LS_KEY = 'posimai-log-diary'; + const diaryPanel = document.getElementById('diaryPanel'); + const diaryBtn = document.getElementById('diaryBtn'); + const diaryCloseBtn= document.getElementById('diaryCloseBtn'); + const diaryTextarea= document.getElementById('diaryTextarea'); + const diaryDot = document.getElementById('diaryDot'); + const diarySyncStatus = document.getElementById('diarySyncStatus'); + const diaryCharCount = document.getElementById('diaryCharCount'); + + let serverOnline = false; + let saveTimer = null; + + function setDiaryStatus(state, text) { + diarySyncStatus.className = 'diary-sync-status ' + state; + diarySyncStatus.textContent = text; + diaryDot.className = 'diary-status-dot ' + (serverOnline ? 'connected' : state === 'pending' ? 'pending' : ''); + } + + // サーバー接続確認 & 未同期ローカルデータのプッシュ + async function checkServer() { + try { + const res = await fetch(DIARY_URL, { signal: AbortSignal.timeout(800) }); + if (!res.ok) throw new Error(); + serverOnline = true; + diaryDot.className = 'diary-status-dot connected'; + // ローカルに保留中のデータがあればサーバーに送る + const local = localStorage.getItem(DIARY_LS_KEY); + if (local !== null) { + const serverData = await res.json(); + if (local !== serverData.content) { + await fetch(DIARY_URL, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ content: local }) }); + } + } + } catch { + serverOnline = false; + diaryDot.className = 'diary-status-dot' + (localStorage.getItem(DIARY_LS_KEY) ? ' pending' : ''); + } + } + + // パネルを開くときにコンテンツをロード + async function openDiary() { + diaryPanel.classList.add('open'); + diaryBtn.setAttribute('aria-expanded', 'true'); + setDiaryStatus('', 'connecting...'); + await checkServer(); + try { + if (serverOnline) { + const res = await fetch(DIARY_URL, { signal: AbortSignal.timeout(800) }); + const { content } = await res.json(); + diaryTextarea.value = content; + localStorage.setItem(DIARY_LS_KEY, content); + setDiaryStatus('saved', 'synced'); + } else { + diaryTextarea.value = localStorage.getItem(DIARY_LS_KEY) || ''; + setDiaryStatus('pending', 'offline — saved locally'); + } + } catch { + diaryTextarea.value = localStorage.getItem(DIARY_LS_KEY) || ''; + setDiaryStatus('pending', 'offline — saved locally'); + } + diaryCharCount.textContent = diaryTextarea.value.length + ' chars'; + diaryTextarea.focus(); + } + + function closeDiary() { + diaryPanel.classList.remove('open'); + diaryBtn.setAttribute('aria-expanded', 'false'); + } + + // 入力のたびに debounce 保存 (1s) + diaryTextarea.addEventListener('input', () => { + const content = diaryTextarea.value; + diaryCharCount.textContent = content.length + ' chars'; + localStorage.setItem(DIARY_LS_KEY, content); + setDiaryStatus('pending', 'saving...'); + clearTimeout(saveTimer); + saveTimer = setTimeout(async () => { + if (serverOnline) { + try { + await fetch(DIARY_URL, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ content }) }); + setDiaryStatus('saved', 'saved to diary.md'); + } catch { + serverOnline = false; + setDiaryStatus('pending', 'offline — saved locally'); + } + } else { + setDiaryStatus('pending', 'offline — saved locally'); + } + }, 1000); + }); + + diaryBtn.addEventListener('click', openDiary); + diaryCloseBtn.addEventListener('click', closeDiary); + // overlay クリックでも閉じる + document.getElementById('overlay').addEventListener('click', closeDiary); })();