512 lines
22 KiB
HTML
512 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-TW">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>VvvebJS 網頁管理器</title>
|
||
<meta name="description" content="使用 VvvebJS 管理您的靜態網站專案">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="/static/css/my-style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ── 頂部導覽 ────────────────────────────────────────────────── -->
|
||
<header class="topbar">
|
||
<div class="topbar-brand">
|
||
<svg class="brand-icon" viewBox="0 0 32 32" fill="none">
|
||
<rect width="32" height="32" rx="8" fill="url(#grad)"/>
|
||
<path d="M8 10h16M8 16h10M8 22h13" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
|
||
<defs>
|
||
<linearGradient id="grad" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
|
||
<stop stop-color="#6366f1"/>
|
||
<stop offset="1" stop-color="#8b5cf6"/>
|
||
</linearGradient>
|
||
</defs>
|
||
</svg>
|
||
<span class="brand-name">VvvebJS <em>Manager</em></span>
|
||
</div>
|
||
<div class="topbar-actions">
|
||
<span class="site-count" id="site-count">— 個專案</span>
|
||
<button class="btn btn-primary" id="new-project-btn">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||
</svg>
|
||
新增專案
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ── 主內容 ─────────────────────────────────────────────────── -->
|
||
<main class="main-content">
|
||
|
||
<!-- 搜尋列 -->
|
||
<div class="search-bar-wrap">
|
||
<div class="search-bar">
|
||
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
<input type="text" id="search-input" placeholder="搜尋專案名稱…" autocomplete="off">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 卡片網格 -->
|
||
<div class="projects-grid" id="projects-grid">
|
||
<!-- 由 JS 動態填入 -->
|
||
</div>
|
||
|
||
<!-- 空狀態 -->
|
||
<div class="empty-state" id="empty-state" style="display:none">
|
||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2">
|
||
<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/>
|
||
<line x1="12" y1="17" x2="12" y2="21"/>
|
||
</svg>
|
||
<h2>尚無專案</h2>
|
||
<p>點擊右上角「新增專案」開始建立您的第一個網站</p>
|
||
<button class="btn btn-primary" id="empty-new-btn">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||
</svg>
|
||
建立第一個專案
|
||
</button>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<!-- ── 新增專案 Modal ──────────────────────────────────────────── -->
|
||
<div class="modal-backdrop" id="create-modal-backdrop">
|
||
<div class="modal-box" id="create-modal" role="dialog" aria-labelledby="create-modal-title" aria-modal="true">
|
||
<div class="modal-header">
|
||
<h2 id="create-modal-title">新增專案</h2>
|
||
<button class="modal-close" id="create-modal-close" aria-label="關閉">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="new-project-name">專案名稱 <span class="required">*</span></label>
|
||
<input type="text" id="new-project-name" placeholder="例如:我的公司網站" autocomplete="off">
|
||
<span class="slug-preview" id="slug-preview"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="new-project-desc">描述(選填)</label>
|
||
<textarea id="new-project-desc" rows="3" placeholder="簡短說明這個網站的用途…"></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-ghost" id="create-cancel-btn">取消</button>
|
||
<button class="btn btn-primary" id="create-confirm-btn">建立專案</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 設定 Offcanvas ─────────────────────────────────────────── -->
|
||
<div class="offcanvas-backdrop" id="settings-backdrop">
|
||
<div class="offcanvas" id="settings-panel" role="dialog" aria-labelledby="settings-title">
|
||
<div class="offcanvas-header">
|
||
<h2 id="settings-title">專案設定</h2>
|
||
<button class="modal-close" id="settings-close" aria-label="關閉">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="offcanvas-body">
|
||
<input type="hidden" id="settings-slug">
|
||
<div class="form-group">
|
||
<label for="settings-name">專案名稱</label>
|
||
<input type="text" id="settings-name" placeholder="專案名稱">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="settings-desc">描述</label>
|
||
<textarea id="settings-desc" rows="4"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Slug(資料夾名稱)</label>
|
||
<input type="text" id="settings-slug-display" disabled class="input-disabled">
|
||
<p class="hint-text">Slug 為唯一識別碼,建立後無法更改</p>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<h3 class="section-label">頁面清單</h3>
|
||
<ul class="pages-list" id="settings-pages-list">
|
||
<li class="pages-loading">載入中…</li>
|
||
</ul>
|
||
<button class="btn btn-ghost btn-sm" id="add-page-btn" style="margin-top:0.75rem; width:100%">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||
</svg>
|
||
新增頁面
|
||
</button>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<div class="danger-zone">
|
||
<h3 class="section-label danger">危險操作</h3>
|
||
<button class="btn btn-danger" id="delete-project-btn">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/>
|
||
<path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/>
|
||
</svg>
|
||
刪除此專案
|
||
</button>
|
||
<p class="hint-text danger">此操作不可復原,將刪除所有頁面檔案</p>
|
||
</div>
|
||
</div>
|
||
<div class="offcanvas-footer">
|
||
<button class="btn btn-ghost" id="settings-cancel-btn">取消</button>
|
||
<button class="btn btn-primary" id="settings-save-btn">儲存設定</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 刪除確認 Modal ──────────────────────────────────────────── -->
|
||
<div class="modal-backdrop" id="delete-modal-backdrop">
|
||
<div class="modal-box modal-sm" role="dialog" aria-modal="true">
|
||
<div class="modal-header">
|
||
<h2>確認刪除</h2>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>確定要刪除專案 <strong id="delete-project-name"></strong> 嗎?<br>
|
||
此操作不可復原,所有頁面檔案都將被刪除。</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-ghost" id="delete-cancel-btn">取消</button>
|
||
<button class="btn btn-danger" id="delete-confirm-btn">確認刪除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Toast 通知 ─────────────────────────────────────────────── -->
|
||
<div class="toast-container" id="toast-container"></div>
|
||
|
||
<script>
|
||
// ── 狀態 ─────────────────────────────────────────────────────────
|
||
let allProjects = [];
|
||
let settingsCurrentSlug = '';
|
||
let deleteTargetSlug = '';
|
||
|
||
// ── API 輔助 ─────────────────────────────────────────────────────
|
||
async function apiFetch(url, opts = {}) {
|
||
const res = await fetch(url, {
|
||
headers: { 'Content-Type': 'application/json' },
|
||
...opts,
|
||
});
|
||
const data = await res.json().catch(() => ({}));
|
||
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
||
return data;
|
||
}
|
||
|
||
// ── Toast ─────────────────────────────────────────────────────────
|
||
function showToast(msg, type = 'success') {
|
||
const tc = document.getElementById('toast-container');
|
||
const t = document.createElement('div');
|
||
t.className = `toast toast-${type}`;
|
||
t.textContent = msg;
|
||
tc.appendChild(t);
|
||
requestAnimationFrame(() => t.classList.add('show'));
|
||
setTimeout(() => {
|
||
t.classList.remove('show');
|
||
setTimeout(() => t.remove(), 400);
|
||
}, 3000);
|
||
}
|
||
|
||
// ── Slug 預覽 ─────────────────────────────────────────────────────
|
||
function toSlug(s) {
|
||
return s.toLowerCase()
|
||
.replace(/[^\w\s-]/g, '')
|
||
.replace(/[\s_-]+/g, '-')
|
||
.replace(/^-+|-+$/g, '') || 'untitled';
|
||
}
|
||
|
||
// ── 渲染卡片 ─────────────────────────────────────────────────────
|
||
function renderProjects(projects) {
|
||
const grid = document.getElementById('projects-grid');
|
||
const empty = document.getElementById('empty-state');
|
||
const count = document.getElementById('site-count');
|
||
count.textContent = `${projects.length} 個專案`;
|
||
|
||
if (projects.length === 0) {
|
||
grid.innerHTML = '';
|
||
empty.style.display = 'flex';
|
||
return;
|
||
}
|
||
empty.style.display = 'none';
|
||
|
||
grid.innerHTML = projects.map(p => {
|
||
const mod = p.last_modified ? p.last_modified.slice(0, 10) : '—';
|
||
const pages = p.page_count ?? 0;
|
||
return `
|
||
<div class="project-card" data-slug="${p.slug}">
|
||
<div class="card-glow"></div>
|
||
<div class="card-header-row">
|
||
<div class="card-icon">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||
<rect x="2" y="3" width="20" height="14" rx="2"/>
|
||
<line x1="8" y1="21" x2="16" y2="21"/>
|
||
<line x1="12" y1="17" x2="12" y2="21"/>
|
||
</svg>
|
||
</div>
|
||
<div class="card-menu">
|
||
<button class="icon-btn settings-btn" data-slug="${p.slug}" title="設定">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="3"/>
|
||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-title">${escHtml(p.name)}</h3>
|
||
<p class="card-desc">${escHtml(p.description || '—')}</p>
|
||
</div>
|
||
<div class="card-meta">
|
||
<span class="meta-item">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||
<polyline points="14 2 14 8 20 8"/>
|
||
</svg>
|
||
${pages} 頁
|
||
</span>
|
||
<span class="meta-item">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
||
<line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/>
|
||
<line x1="3" y1="10" x2="21" y2="10"/>
|
||
</svg>
|
||
${mod}
|
||
</span>
|
||
</div>
|
||
<div class="card-actions">
|
||
<a href="/editor/${p.slug}" class="btn btn-primary btn-sm" id="edit-btn-${p.slug}">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||
</svg>
|
||
開啟編輯器
|
||
</a>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
|
||
// 綁定設定按鈕
|
||
grid.querySelectorAll('.settings-btn').forEach(btn => {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
openSettings(btn.dataset.slug);
|
||
});
|
||
});
|
||
}
|
||
|
||
function escHtml(s) {
|
||
return String(s)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ── 載入專案 ─────────────────────────────────────────────────────
|
||
async function loadProjects() {
|
||
try {
|
||
allProjects = await apiFetch('/api/projects');
|
||
renderProjects(allProjects);
|
||
} catch (e) {
|
||
showToast('載入專案失敗:' + e.message, 'error');
|
||
}
|
||
}
|
||
|
||
// ── 搜尋 ─────────────────────────────────────────────────────────
|
||
document.getElementById('search-input').addEventListener('input', function() {
|
||
const q = this.value.toLowerCase();
|
||
const filtered = allProjects.filter(p =>
|
||
p.name.toLowerCase().includes(q) ||
|
||
(p.description || '').toLowerCase().includes(q)
|
||
);
|
||
renderProjects(filtered);
|
||
});
|
||
|
||
// ── 新增專案 Modal ────────────────────────────────────────────────
|
||
function openCreateModal() {
|
||
document.getElementById('new-project-name').value = '';
|
||
document.getElementById('new-project-desc').value = '';
|
||
document.getElementById('slug-preview').textContent = '';
|
||
document.getElementById('create-modal-backdrop').classList.add('active');
|
||
setTimeout(() => document.getElementById('new-project-name').focus(), 50);
|
||
}
|
||
function closeCreateModal() {
|
||
document.getElementById('create-modal-backdrop').classList.remove('active');
|
||
}
|
||
|
||
document.getElementById('new-project-btn').addEventListener('click', openCreateModal);
|
||
document.getElementById('empty-new-btn').addEventListener('click', openCreateModal);
|
||
document.getElementById('create-modal-close').addEventListener('click', closeCreateModal);
|
||
document.getElementById('create-cancel-btn').addEventListener('click', closeCreateModal);
|
||
document.getElementById('create-modal-backdrop').addEventListener('click', e => {
|
||
if (e.target.id === 'create-modal-backdrop') closeCreateModal();
|
||
});
|
||
|
||
document.getElementById('new-project-name').addEventListener('input', function() {
|
||
const slug = toSlug(this.value);
|
||
const preview = document.getElementById('slug-preview');
|
||
preview.textContent = this.value ? `資料夾名稱:${slug}` : '';
|
||
});
|
||
|
||
document.getElementById('create-confirm-btn').addEventListener('click', async () => {
|
||
const name = document.getElementById('new-project-name').value.trim();
|
||
const desc = document.getElementById('new-project-desc').value.trim();
|
||
if (!name) {
|
||
showToast('請輸入專案名稱', 'error');
|
||
document.getElementById('new-project-name').focus();
|
||
return;
|
||
}
|
||
try {
|
||
const btn = document.getElementById('create-confirm-btn');
|
||
btn.disabled = true;
|
||
btn.textContent = '建立中…';
|
||
await apiFetch('/api/projects', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ name, description: desc }),
|
||
});
|
||
closeCreateModal();
|
||
showToast(`專案「${name}」建立成功!`);
|
||
await loadProjects();
|
||
} catch(e) {
|
||
showToast('建立失敗:' + e.message, 'error');
|
||
} finally {
|
||
const btn = document.getElementById('create-confirm-btn');
|
||
btn.disabled = false;
|
||
btn.textContent = '建立專案';
|
||
}
|
||
});
|
||
|
||
// ── 設定 Offcanvas ────────────────────────────────────────────────
|
||
async function openSettings(slug) {
|
||
settingsCurrentSlug = slug;
|
||
document.getElementById('settings-backdrop').classList.add('active');
|
||
document.getElementById('settings-slug').value = slug;
|
||
document.getElementById('settings-slug-display').value = slug;
|
||
document.getElementById('settings-pages-list').innerHTML = '<li class="pages-loading">載入中…</li>';
|
||
|
||
try {
|
||
const proj = await apiFetch(`/api/projects/${slug}`);
|
||
document.getElementById('settings-name').value = proj.name;
|
||
document.getElementById('settings-desc').value = proj.description || '';
|
||
|
||
const pages = proj.pages || [];
|
||
const listEl = document.getElementById('settings-pages-list');
|
||
if (pages.length === 0) {
|
||
listEl.innerHTML = '<li class="no-pages">尚無頁面</li>';
|
||
} else {
|
||
listEl.innerHTML = pages.map(p => `
|
||
<li class="page-item" style="justify-content: space-between; display: flex; align-items: center; margin-bottom: 0.4rem;">
|
||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--accent);">
|
||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||
<polyline points="14 2 14 8 20 8"/>
|
||
</svg>
|
||
<span>${escHtml(p)}</span>
|
||
</div>
|
||
<a href="/editor/${slug}?page=${escHtml(p)}" class="btn btn-primary btn-sm" style="padding: 0.2rem 0.6rem; font-size: 0.75rem; gap: 0.25rem; border-radius: var(--radius-sm);">
|
||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<path d="M11 4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||
</svg>
|
||
編輯
|
||
</a>
|
||
</li>`).join('');
|
||
}
|
||
} catch(e) {
|
||
showToast('載入設定失敗:' + e.message, 'error');
|
||
}
|
||
}
|
||
|
||
function closeSettings() {
|
||
document.getElementById('settings-backdrop').classList.remove('active');
|
||
}
|
||
|
||
document.getElementById('settings-close').addEventListener('click', closeSettings);
|
||
document.getElementById('settings-cancel-btn').addEventListener('click', closeSettings);
|
||
document.getElementById('settings-backdrop').addEventListener('click', e => {
|
||
if (e.target.id === 'settings-backdrop') closeSettings();
|
||
});
|
||
|
||
document.getElementById('settings-save-btn').addEventListener('click', async () => {
|
||
const name = document.getElementById('settings-name').value.trim();
|
||
const desc = document.getElementById('settings-desc').value.trim();
|
||
if (!name) {
|
||
showToast('名稱不能為空', 'error');
|
||
return;
|
||
}
|
||
try {
|
||
await apiFetch(`/api/projects/${settingsCurrentSlug}`, {
|
||
method: 'PUT',
|
||
body: JSON.stringify({ name, description: desc }),
|
||
});
|
||
closeSettings();
|
||
showToast('設定已儲存');
|
||
await loadProjects();
|
||
} catch(e) {
|
||
showToast('儲存失敗:' + e.message, 'error');
|
||
}
|
||
});
|
||
|
||
// 新增頁面
|
||
document.getElementById('add-page-btn').addEventListener('click', async () => {
|
||
const name = prompt('請輸入新頁面名稱(例如:about):');
|
||
if (!name) return;
|
||
try {
|
||
await apiFetch(`/api/projects/${settingsCurrentSlug}/pages`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ name }),
|
||
});
|
||
showToast(`頁面「${name}」建立成功`);
|
||
openSettings(settingsCurrentSlug); // 重新整理面板
|
||
} catch(e) {
|
||
showToast('建立頁面失敗:' + e.message, 'error');
|
||
}
|
||
});
|
||
|
||
// ── 刪除 ─────────────────────────────────────────────────────────
|
||
document.getElementById('delete-project-btn').addEventListener('click', () => {
|
||
deleteTargetSlug = settingsCurrentSlug;
|
||
const proj = allProjects.find(p => p.slug === settingsCurrentSlug);
|
||
document.getElementById('delete-project-name').textContent = proj ? proj.name : deleteTargetSlug;
|
||
document.getElementById('delete-modal-backdrop').classList.add('active');
|
||
});
|
||
|
||
document.getElementById('delete-cancel-btn').addEventListener('click', () => {
|
||
document.getElementById('delete-modal-backdrop').classList.remove('active');
|
||
});
|
||
|
||
document.getElementById('delete-confirm-btn').addEventListener('click', async () => {
|
||
try {
|
||
await apiFetch(`/api/projects/${deleteTargetSlug}`, { method: 'DELETE' });
|
||
document.getElementById('delete-modal-backdrop').classList.remove('active');
|
||
closeSettings();
|
||
showToast('專案已刪除');
|
||
await loadProjects();
|
||
} catch(e) {
|
||
showToast('刪除失敗:' + e.message, 'error');
|
||
}
|
||
});
|
||
|
||
document.getElementById('delete-modal-backdrop').addEventListener('click', e => {
|
||
if (e.target.id === 'delete-modal-backdrop')
|
||
document.getElementById('delete-modal-backdrop').classList.remove('active');
|
||
});
|
||
|
||
// ── Enter 鍵快速提交 ─────────────────────────────────────────────
|
||
document.getElementById('new-project-name').addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') document.getElementById('create-confirm-btn').click();
|
||
});
|
||
|
||
// ── 初始化 ────────────────────────────────────────────────────────
|
||
loadProjects();
|
||
</script>
|
||
</body>
|
||
</html>
|