diff --git a/index.html b/index.html index 323dd13..65da4fd 100644 --- a/index.html +++ b/index.html @@ -294,12 +294,36 @@ .diary-textarea::placeholder { color: var(--text3); } .diary-footer { + display: flex; + flex-direction: column; + border-top: 1px solid var(--border); + flex-shrink: 0; + } + .diary-apikey-row { + display: none; + align-items: center; + gap: 6px; + padding: 8px 16px; + border-bottom: 1px solid var(--border); + } + .diary-apikey-row.visible { display: flex; } + .diary-apikey-input { + flex: 1; + background: transparent; + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text1); + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + padding: 4px 8px; + outline: none; + } + .diary-apikey-input:focus { border-color: var(--accent); } + .diary-footer-main { 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; @@ -309,6 +333,23 @@ .diary-sync-status.saved { color: var(--accent); } .diary-sync-status.pending { color: #FCD34D; } + .diary-action-btn { + display: none; + align-items: center; + gap: 4px; + padding: 3px 8px; + border-radius: 4px; + border: 1px solid var(--border); + background: transparent; + color: var(--text3); + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + cursor: pointer; + transition: color .2s, border-color .2s; + } + .diary-action-btn:hover { color: var(--text1); border-color: var(--text2); } + .diary-action-btn.visible { display: inline-flex; } + @media (max-width: 640px) { .diary-panel { width: 100%; } } @@ -432,8 +473,20 @@ spellcheck="false" >
@@ -526,63 +579,65 @@ } // ── 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'); + const DIARY_GET_URL = 'https://api.soar-enrich.com/brain/api/site/config/public?user=maita'; + const DIARY_POST_URL = 'https://api.soar-enrich.com/brain/api/site/config/diary_content'; + const DIARY_LS_KEY = 'posimai-log-diary'; + const APIKEY_LS_KEY = 'posimai_api_key'; + + 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'); + const diaryReconnectBtn = document.getElementById('diaryReconnectBtn'); + const diaryApiKeyRow = document.getElementById('diaryApiKeyRow'); + const diaryApiKeyInput = document.getElementById('diaryApiKeyInput'); + const diaryApiKeySave = document.getElementById('diaryApiKeySave'); - let serverOnline = false; let saveTimer = null; + function getApiKey() { return localStorage.getItem(APIKEY_LS_KEY) || ''; } + function setDiaryStatus(state, text) { diarySyncStatus.className = 'diary-sync-status ' + state; diarySyncStatus.textContent = text; - diaryDot.className = 'diary-status-dot ' + (serverOnline ? 'connected' : state === 'pending' ? 'pending' : ''); + diaryDot.className = 'diary-status-dot' + + (state === 'saved' ? ' connected' : state === 'pending' ? ' pending' : ''); + diaryReconnectBtn.classList.toggle('visible', state === 'error'); + if (!getApiKey()) diaryApiKeyRow.classList.add('visible'); } - // サーバー接続確認 & 未同期ローカルデータのプッシュ - 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 fetchDiary() { + const res = await fetch(DIARY_GET_URL, { signal: AbortSignal.timeout(4000) }); + if (!res.ok) throw new Error('fetch failed'); + const data = await res.json(); + return data.config?.diary_content || ''; + } + + async function saveDiary(content) { + const key = getApiKey(); + if (!key) throw Object.assign(new Error('no key'), { code: 'nokey' }); + const res = await fetch(DIARY_POST_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + key }, + body: JSON.stringify({ value: content }), + signal: AbortSignal.timeout(5000) + }); + if (!res.ok) throw new Error('save failed'); } - // パネルを開くときにコンテンツをロード async function openDiary() { diaryPanel.classList.add('open'); diaryBtn.setAttribute('aria-expanded', 'true'); - setDiaryStatus('', 'connecting...'); - await checkServer(); + diaryApiKeyRow.classList.toggle('visible', !getApiKey()); + setDiaryStatus('', 'loading...'); 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'); - } + const content = await fetchDiary(); + diaryTextarea.value = content; + localStorage.setItem(DIARY_LS_KEY, content); + setDiaryStatus('saved', 'synced'); } catch { diaryTextarea.value = localStorage.getItem(DIARY_LS_KEY) || ''; setDiaryStatus('pending', 'offline — saved locally'); @@ -596,7 +651,6 @@ diaryBtn.setAttribute('aria-expanded', 'false'); } - // 入力のたびに debounce 保存 (1s) diaryTextarea.addEventListener('input', () => { const content = diaryTextarea.value; diaryCharCount.textContent = content.length + ' chars'; @@ -604,23 +658,53 @@ 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; + try { + await saveDiary(content); + setDiaryStatus('saved', 'saved'); + } catch (e) { + if (e.code === 'nokey') { + setDiaryStatus('', 'set API key to sync'); + diaryApiKeyRow.classList.add('visible'); + } else { setDiaryStatus('pending', 'offline — saved locally'); } - } else { - setDiaryStatus('pending', 'offline — saved locally'); } }, 1000); }); + diaryReconnectBtn.addEventListener('click', async () => { + setDiaryStatus('', 'retrying...'); + try { + const content = await fetchDiary(); + diaryTextarea.value = content; + localStorage.setItem(DIARY_LS_KEY, content); + setDiaryStatus('saved', 'synced'); + diaryCharCount.textContent = content.length + ' chars'; + } catch { + setDiaryStatus('error', 'still offline'); + } + }); + + diaryApiKeySave.addEventListener('click', async () => { + const val = diaryApiKeyInput.value.trim(); + if (!val) return; + localStorage.setItem(APIKEY_LS_KEY, val); + diaryApiKeyInput.value = ''; + diaryApiKeyRow.classList.remove('visible'); + setDiaryStatus('', 'connecting...'); + try { + const content = await fetchDiary(); + diaryTextarea.value = content; + localStorage.setItem(DIARY_LS_KEY, content); + setDiaryStatus('saved', 'synced'); + diaryCharCount.textContent = content.length + ' chars'; + } catch { + setDiaryStatus('pending', 'offline — saved locally'); + } + }); + diaryBtn.addEventListener('click', openDiary); diaryCloseBtn.addEventListener('click', closeDiary); - // overlay クリックでも閉じる document.getElementById('overlay').addEventListener('click', closeDiary); })();