feat(chronicle): 設定パネルに GitHub PAT 欄を追加、ブラウザ直接 GitHub API 接続に変更

This commit is contained in:
posimai 2026-04-24 07:56:55 +09:00
parent e5df1c0240
commit ffc144fd31
1 changed files with 76 additions and 22 deletions

View File

@ -180,7 +180,23 @@
</button>
</div>
<div class="settings-panel-body">
<div class="settings-group-label">外観</div>
<div class="settings-group-label">GitHub 連携</div>
<div class="settings-item" style="flex-direction:column;align-items:flex-start;gap:6px">
<div class="settings-item-label">Personal Access Token</div>
<div style="font-size:11px;color:var(--text3);line-height:1.5">
github.com → Settings → Developer settings<br>
→ Fine-grained tokens → Generate new token<br>
Permissions: <strong style="color:var(--text2)">Contents: Read-only</strong>
</div>
<div style="display:flex;gap:6px;width:100%">
<input type="password" id="ghPatInput" placeholder="github_pat_..." autocomplete="off"
style="flex:1;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-family:inherit;font-size:12px;padding:7px 10px;min-width:0">
<button class="btn btn-ghost" id="ghPatSaveBtn" style="font-size:12px;padding:6px 12px;flex-shrink:0">保存</button>
</div>
<div id="ghPatStatus" style="font-size:11px;color:var(--text3)"></div>
</div>
<div class="settings-group-label" style="margin-top:16px">外観</div>
<div class="settings-item">
<div class="settings-item-label">テーマ</div>
<div class="theme-selector">
@ -487,47 +503,85 @@ document.getElementById('copyBtn').addEventListener('click', async () => {
}
});
// ── GitHub PAT 設定 ───────────────────
const GH_PAT_KEY = 'posimai-chronicle-gh-pat';
function getGhPat() { return localStorage.getItem(GH_PAT_KEY) || ''; }
(function initGhPatUI() {
const input = document.getElementById('ghPatInput');
const status = document.getElementById('ghPatStatus');
const pat = getGhPat();
if (pat) {
input.value = pat;
status.textContent = '設定済み';
status.style.color = 'var(--accent)';
}
document.getElementById('ghPatSaveBtn').addEventListener('click', () => {
const val = input.value.trim();
if (!val) {
localStorage.removeItem(GH_PAT_KEY);
status.textContent = '削除しました';
status.style.color = 'var(--text3)';
return;
}
localStorage.setItem(GH_PAT_KEY, val);
status.textContent = '保存しました';
status.style.color = 'var(--accent)';
});
})();
// ── コミット読み込み ──────────────────
document.getElementById('loadCommitsBtn').addEventListener('click', async () => {
const token = getToken();
if (!token) { showToast('ログインが必要です'); return; }
const ghPat = getGhPat();
if (!ghPat) {
showToast('設定パネルで GitHub PAT を設定してください');
document.getElementById('settingsBtn').click();
return;
}
const btn = document.getElementById('loadCommitsBtn');
btn.disabled = true;
btn.innerHTML = '<span class="spinner" style="border-color:rgba(0,0,0,.15);border-top-color:var(--text2)"></span> 読み込み中';
const since = new Date(Date.now() - activeDays * 86400000).toISOString();
try {
const resp = await fetch(`${API}/chronicle/activity?days=${activeDays}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const resp = await fetch(
'https://api.github.com/orgs/posimai/events?per_page=100',
{ headers: { 'Authorization': `Bearer ${ghPat}`, 'Accept': 'application/vnd.github+json' } }
);
if (resp.status === 401) throw new Error('PAT が無効です。設定を確認してください');
if (!resp.ok) throw new Error(`GitHub API ${resp.status}`);
if (resp.status === 503) {
showToast('VPS に CHRONICLE_GITHUB_TOKEN が未設定です');
return;
const events = await resp.json();
const commits = [];
const byRepo = {};
for (const ev of events) {
if (ev.type !== 'PushEvent') continue;
if (ev.created_at < since) continue;
const repo = ev.repo.name.replace('posimai/', '');
if (!byRepo[repo]) byRepo[repo] = [];
for (const c of ev.payload?.commits || []) {
const msg = c.message.split('\n')[0];
if (/^Merge\b/i.test(msg)) continue;
byRepo[repo].push(msg);
commits.push(msg);
}
}
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
if (!data.commits?.length) {
if (!commits.length) {
showToast('この期間にコミットが見つかりませんでした');
return;
}
const byRepo = {};
for (const c of data.commits) {
if (!byRepo[c.repo]) byRepo[c.repo] = [];
byRepo[c.repo].push(c.message);
}
const lines = [];
for (const [repo, messages] of Object.entries(byRepo)) {
lines.push(`【${repo}】`);
for (const m of messages) lines.push(`- ${m}`);
}
const area = document.getElementById('activities');
area.value = lines.join('\n');
showToast(`${data.commits.length} 件のコミットを読み込みました`);
document.getElementById('activities').value = lines.join('\n');
showToast(`${commits.length} 件のコミットを読み込みました`);
} catch (e) {
showToast(`読み込み失敗: ${e.message}`);
} finally {